mirror of
https://github.com/Team-Resurgent/XBMC4Xbox.git
synced 2026-08-15 11:18:13 -07:00
If ever adding the XBMC4Xbox source to GitHub use "git add -f -A" or it wont add all the files.
548 lines
15 KiB
C++
548 lines
15 KiB
C++
/*
|
|
* Copyright (C) 2005-2013 Team XBMC
|
|
* http://xbmc.org
|
|
*
|
|
* This Program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This Program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with XBMC; see the file COPYING. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Parts of this code taken from Guido Vollbeding <http://sylvana.net/jpegcrop/exif_orientation.html>
|
|
*
|
|
*/
|
|
|
|
#include "include.h"
|
|
#include "lib/libexif/libexif.h"
|
|
#include "settings/GUISettings.h"
|
|
#include "settings/Settings.h"
|
|
#include "../xbmc/FileSystem/File.h"
|
|
#include "utils/log.h"
|
|
#include "JpegIO.h"
|
|
#include "XBTF.h"
|
|
#include "utils/log.h"
|
|
|
|
#include <setjmp.h>
|
|
|
|
#define EXIF_TAG_ORIENTATION 0x0112
|
|
|
|
struct my_error_mgr
|
|
{
|
|
struct jpeg_error_mgr pub; // "public" fields
|
|
jmp_buf setjmp_buffer; // for return to caller
|
|
};
|
|
|
|
CJpegIO::CJpegIO()
|
|
{
|
|
m_width = 0;
|
|
m_height = 0;
|
|
m_originalwidth = 0;
|
|
m_originalheight = 0;
|
|
m_orientation = 0;
|
|
m_inputBuffSize = 0;
|
|
m_inputBuff = NULL;
|
|
m_texturePath = "";
|
|
}
|
|
|
|
CJpegIO::~CJpegIO()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
void CJpegIO::Close()
|
|
{
|
|
free(m_inputBuff);
|
|
m_inputBuff = NULL;
|
|
m_inputBuffSize = 0;
|
|
}
|
|
|
|
bool CJpegIO::Open(const CStdString &texturePath, unsigned int minx, unsigned int miny, bool read)
|
|
{
|
|
Close();
|
|
|
|
m_texturePath = texturePath;
|
|
|
|
XFILE::CFile file;
|
|
if (file.Open(m_texturePath.c_str(), READ_TRUNCATED))
|
|
{
|
|
/*
|
|
GetLength() will typically return values that fall into three cases:
|
|
1. The real filesize. This is the typical case.
|
|
2. Zero. This is the case for some http:// streams for example.
|
|
3. Some value smaller than the real filesize. This is the case for an expanding file.
|
|
|
|
In order to handle all three cases, we read the file in chunks, relying on Read()
|
|
returning 0 at EOF. To minimize (re)allocation of the buffer, the chunksize in
|
|
cases 1 and 3 is set to one byte larger** than the value returned by GetLength().
|
|
The chunksize in case 2 is set to the larger of 64k and GetChunkSize().
|
|
|
|
We fill the buffer entirely before reallocation. Thus, reallocation never occurs in case 1
|
|
as the buffer is larger than the file, so we hit EOF before we hit the end of buffer.
|
|
|
|
To minimize reallocation, we double the chunksize each time up to a maxchunksize of 2MB.
|
|
*/
|
|
unsigned int filesize = (unsigned int)file.GetLength();
|
|
unsigned int chunksize = filesize ? (filesize + 1) : std::max(65536U, (unsigned int)file.GetChunkSize());
|
|
unsigned int maxchunksize = 2048*1024U; /* max 2MB chunksize */
|
|
|
|
unsigned int total_read = 0, free_space = 0;
|
|
while (true)
|
|
{
|
|
if (!free_space)
|
|
{ // (re)alloc
|
|
m_inputBuffSize += chunksize;
|
|
m_inputBuff = (unsigned char *)realloc(m_inputBuff, m_inputBuffSize);
|
|
if (!m_inputBuff)
|
|
{
|
|
CLog::Log(LOGERROR, "%s unable to allocate buffer of size %u", __FUNCTION__, m_inputBuffSize);
|
|
return false;
|
|
}
|
|
free_space = chunksize;
|
|
chunksize = std::min(chunksize*2, maxchunksize);
|
|
}
|
|
unsigned int read = file.Read(m_inputBuff + total_read, free_space);
|
|
free_space -= read;
|
|
total_read += read;
|
|
if (!read)
|
|
break;
|
|
}
|
|
m_inputBuffSize = total_read;
|
|
file.Close();
|
|
|
|
if (m_inputBuffSize == 0)
|
|
return false;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
if (!read)
|
|
return true;
|
|
|
|
if (Read(m_inputBuff, m_inputBuffSize, minx, miny))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool CJpegIO::Read(unsigned char* buffer, unsigned int bufSize, unsigned int minx, unsigned int miny)
|
|
{
|
|
struct my_error_mgr jerr;
|
|
m_cinfo.err = jpeg_std_error(&jerr.pub);
|
|
jerr.pub.error_exit = jpeg_error_exit;
|
|
|
|
if (buffer == NULL || !bufSize )
|
|
return false;
|
|
|
|
jpeg_create_decompress(&m_cinfo);
|
|
tb_jpeg_mem_src(&m_cinfo, buffer, bufSize);
|
|
|
|
if (setjmp(jerr.setjmp_buffer))
|
|
{
|
|
tb_jpeg_destroy_decompress(&m_cinfo);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
tb_jpeg_save_markers(&m_cinfo, JPEG_APP0 + 1, 0xFFFF);
|
|
tb_jpeg_read_header(&m_cinfo, true);
|
|
|
|
if (m_cinfo.marker_list)
|
|
m_orientation = GetExifOrientation(m_cinfo.marker_list->data, m_cinfo.marker_list->data_length);
|
|
|
|
// fail on images with orientation (fall back to cximage)
|
|
if (g_guiSettings.GetBool("pictures.useexifrotation") && m_orientation > 1 )
|
|
{
|
|
CLog::Log(LOGDEBUG, "JpegIO::Read - Exif orientation > 1 so falling back to CXImage");
|
|
return false;
|
|
}
|
|
|
|
m_originalwidth = m_cinfo.image_width;
|
|
m_originalheight = m_cinfo.image_height;
|
|
|
|
/* libjpeg can scale the image for us if it is too big. It must be in the format
|
|
num/denom, where (for our purposes) that is [1-8]/8 where 8/8 is the unscaled image.
|
|
The only way to know how big a resulting image will be is to try a ratio and
|
|
test its resulting size.
|
|
If the res is greater than the one desired, use that one since there's no need
|
|
to decode a bigger one just to squish it back down. If the res is greater than
|
|
the gpu can hold, use the previous one.*/
|
|
if (minx == 0 || miny == 0)
|
|
{
|
|
minx = g_settings.m_ResInfo[g_guiSettings.m_LookAndFeelResolution].iWidth;
|
|
miny = g_settings.m_ResInfo[g_guiSettings.m_LookAndFeelResolution].iHeight;
|
|
}
|
|
|
|
/* override minx/miny values based on image aspect and area of requested minx/miny
|
|
so that tall/wide images come out larger (geometric mean) */
|
|
unsigned int rminx = minx;
|
|
unsigned int rminy = miny;
|
|
unsigned int area = minx * miny;
|
|
float aspect = ((float) m_originalwidth) / ((float) m_originalheight);
|
|
minx = (unsigned int)sqrt(area * aspect);
|
|
miny = (unsigned int)sqrt(area / aspect);
|
|
CLog::Log(LOGDEBUG, "JpegIO::Read - Requested minx x miny %u x %u - using minx x miny %u x %u", rminx, rminy, minx, miny);
|
|
|
|
m_cinfo.scale_denom = 8;
|
|
m_cinfo.out_color_space = JCS_RGB;
|
|
unsigned int maxtexsize = 4096;
|
|
for (unsigned int scale = 1; scale <= 8; scale++)
|
|
{
|
|
m_cinfo.scale_num = scale;
|
|
tb_jpeg_calc_output_dimensions(&m_cinfo);
|
|
if ((m_cinfo.output_width > maxtexsize) || (m_cinfo.output_height > maxtexsize))
|
|
{
|
|
m_cinfo.scale_num--;
|
|
break;
|
|
}
|
|
if (m_cinfo.output_width >= minx || m_cinfo.output_height >= miny)
|
|
break;
|
|
}
|
|
tb_jpeg_calc_output_dimensions(&m_cinfo);
|
|
m_width = m_cinfo.output_width;
|
|
m_height = m_cinfo.output_height;
|
|
CLog::Log(LOGDEBUG, "JpegIO::Read - Using scale_num of %i, %u x %u", m_cinfo.scale_num, m_width, m_height);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool CJpegIO::Decode(const unsigned char *pixels, unsigned int pitch, unsigned int format)
|
|
{
|
|
unsigned char *dst = (unsigned char*)pixels;
|
|
|
|
struct my_error_mgr jerr;
|
|
m_cinfo.err = tb_jpeg_std_error(&jerr.pub);
|
|
jerr.pub.error_exit = jpeg_error_exit;
|
|
|
|
if (setjmp(jerr.setjmp_buffer))
|
|
{
|
|
tb_jpeg_destroy_decompress(&m_cinfo);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
tb_jpeg_start_decompress(&m_cinfo);
|
|
|
|
if (format == XB_FMT_RGB8)
|
|
{
|
|
while (m_cinfo.output_scanline < m_height)
|
|
{
|
|
tb_jpeg_read_scanlines(&m_cinfo, &dst, 1);
|
|
dst += pitch;
|
|
}
|
|
}
|
|
else if (format == XB_FMT_A8R8G8B8)
|
|
{
|
|
unsigned char* row = new unsigned char[m_width * 3];
|
|
while (m_cinfo.output_scanline < m_height)
|
|
{
|
|
tb_jpeg_read_scanlines(&m_cinfo, &row, 1);
|
|
unsigned char *src2 = row;
|
|
unsigned char *dst2 = dst;
|
|
for (unsigned int x = 0; x < m_width; x++, src2 += 3)
|
|
{
|
|
*dst2++ = src2[2];
|
|
*dst2++ = src2[1];
|
|
*dst2++ = src2[0];
|
|
*dst2++ = 0xff;
|
|
}
|
|
dst += pitch;
|
|
}
|
|
delete[] row;
|
|
}
|
|
else
|
|
{
|
|
CLog::Log(LOGWARNING, "JpegIO: Incorrect output format specified");
|
|
tb_jpeg_destroy_decompress(&m_cinfo);
|
|
return false;
|
|
}
|
|
tb_jpeg_finish_decompress(&m_cinfo);
|
|
}
|
|
tb_jpeg_destroy_decompress(&m_cinfo);
|
|
return true;
|
|
}
|
|
|
|
bool CJpegIO::CreateThumbnail(const CStdString& sourceFile, const CStdString& destFile, int minx, int miny)
|
|
{
|
|
//Copy sourceFile to buffer, pass to CreateThumbnailFromMemory for decode+re-encode
|
|
if (!Open(sourceFile, minx, miny, false))
|
|
return false;
|
|
|
|
return CreateThumbnailFromMemory(m_inputBuff, m_inputBuffSize, destFile, minx, miny);
|
|
}
|
|
|
|
bool CJpegIO::CreateThumbnailFromMemory(unsigned char* buffer, unsigned int bufSize, const CStdString& destFile, unsigned int minx, unsigned int miny)
|
|
{
|
|
//Decode a jpeg residing in buffer, pass to CreateThumbnailFromSurface for re-encode
|
|
unsigned int pitch = 0;
|
|
unsigned char *sourceBuf = NULL;
|
|
|
|
if (!Read(buffer, bufSize, minx, miny))
|
|
return false;
|
|
pitch = Width() * 3;
|
|
sourceBuf = new unsigned char [Height() * pitch];
|
|
|
|
if (!Decode(sourceBuf,pitch,XB_FMT_RGB8))
|
|
{
|
|
delete [] sourceBuf;
|
|
return false;
|
|
}
|
|
if (!CreateThumbnailFromSurface(sourceBuf, Width(), Height() , XB_FMT_RGB8, pitch, destFile))
|
|
{
|
|
delete [] sourceBuf;
|
|
return false;
|
|
}
|
|
delete [] sourceBuf;
|
|
return true;
|
|
}
|
|
|
|
bool CJpegIO::CreateThumbnailFromSurface(unsigned char* buffer, unsigned int width, unsigned int height, unsigned int format, unsigned int pitch, const CStdString& destFile)
|
|
{
|
|
//Encode raw data from buffer, save to destFile
|
|
struct jpeg_compress_struct cinfo;
|
|
struct my_error_mgr jerr;
|
|
JSAMPROW row_pointer[1];
|
|
long unsigned int outBufSize = width * height;
|
|
unsigned char* result;
|
|
unsigned char* src = buffer;
|
|
unsigned char* rgbbuf, *src2, *dst2;
|
|
|
|
if (buffer == NULL)
|
|
{
|
|
CLog::Log(LOGERROR, "JpegIO::CreateThumbnailFromSurface no buffer");
|
|
return false;
|
|
}
|
|
|
|
result = (unsigned char*) malloc(outBufSize); //Initial buffer. Grows as-needed.
|
|
if (result == NULL)
|
|
{
|
|
CLog::Log(LOGERROR, "JpegIO::CreateThumbnailFromSurface error allocating memory for image buffer");
|
|
return false;
|
|
}
|
|
|
|
if(format == XB_FMT_RGB8)
|
|
{
|
|
rgbbuf = buffer;
|
|
}
|
|
else if(format == XB_FMT_A8R8G8B8)
|
|
{
|
|
// create a copy for bgra -> rgb.
|
|
rgbbuf = new unsigned char [(width * height * 3)];
|
|
unsigned char* dst = rgbbuf;
|
|
for (unsigned int y = 0; y < height; y++)
|
|
{
|
|
dst2 = dst;
|
|
src2 = src;
|
|
for (unsigned int x = 0; x < width; x++, src2 += 4)
|
|
{
|
|
*dst2++ = src2[2];
|
|
*dst2++ = src2[1];
|
|
*dst2++ = src2[0];
|
|
}
|
|
dst += width * 3;
|
|
src += pitch;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CLog::Log(LOGWARNING, "JpegIO::CreateThumbnailFromSurface Unsupported format");
|
|
free(result);
|
|
return false;
|
|
}
|
|
|
|
cinfo.err = tb_jpeg_std_error(&jerr.pub);
|
|
jerr.pub.error_exit = jpeg_error_exit;
|
|
jpeg_create_compress(&cinfo);
|
|
|
|
if (setjmp(jerr.setjmp_buffer))
|
|
{
|
|
tb_jpeg_destroy_compress(&cinfo);
|
|
delete [] result;
|
|
if(format != XB_FMT_RGB8)
|
|
delete [] rgbbuf;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
tb_jpeg_mem_dest(&cinfo, &result, &outBufSize);
|
|
|
|
cinfo.image_width = width;
|
|
cinfo.image_height = height;
|
|
cinfo.input_components = 3;
|
|
cinfo.in_color_space = JCS_RGB;
|
|
tb_jpeg_set_defaults(&cinfo);
|
|
tb_jpeg_set_quality(&cinfo, 75, TRUE);
|
|
tb_jpeg_start_compress(&cinfo, TRUE);
|
|
|
|
while (cinfo.next_scanline < cinfo.image_height)
|
|
{
|
|
row_pointer[0] = &rgbbuf[cinfo.next_scanline * width * 3];
|
|
tb_jpeg_write_scanlines(&cinfo, row_pointer, 1);
|
|
}
|
|
|
|
tb_jpeg_finish_compress(&cinfo);
|
|
tb_jpeg_destroy_compress(&cinfo);
|
|
}
|
|
if(format != XB_FMT_RGB8)
|
|
delete [] rgbbuf;
|
|
|
|
XFILE::CFile file;
|
|
if (file.OpenForWrite(destFile, true))
|
|
{
|
|
file.Write(result, outBufSize);
|
|
file.Close();
|
|
free(result);
|
|
return true;
|
|
}
|
|
free(result);
|
|
return false;
|
|
}
|
|
|
|
// override libjpeg's error function to avoid an exit() call
|
|
void CJpegIO::jpeg_error_exit(j_common_ptr cinfo)
|
|
{
|
|
CStdString msg;
|
|
msg.Format("Error %i: %s",cinfo->err->msg_code, cinfo->err->jpeg_message_table[cinfo->err->msg_code]);
|
|
CLog::Log(LOGWARNING, "JpegIO: %s", msg.c_str());
|
|
|
|
my_error_mgr *myerr = (my_error_mgr*)cinfo->err;
|
|
longjmp(myerr->setjmp_buffer, 1);
|
|
}
|
|
|
|
unsigned int CJpegIO::GetExifOrientation(unsigned char* exif_data, unsigned int exif_data_size)
|
|
{
|
|
unsigned int offset = 0;
|
|
unsigned int numberOfTags = 0;
|
|
unsigned int tagNumber = 0;
|
|
bool isMotorola = false;
|
|
unsigned const char ExifHeader[] = "Exif\0\0";
|
|
unsigned int orientation = 0;
|
|
|
|
// read exif head, check for "Exif"
|
|
// next we want to read to current offset + length
|
|
// check if buffer is big enough
|
|
if (exif_data_size && memcmp(exif_data, ExifHeader, 6) == 0)
|
|
{
|
|
//read exif body
|
|
exif_data += 6;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Discover byte order
|
|
if (exif_data[0] == 'I' && exif_data[1] == 'I')
|
|
isMotorola = false;
|
|
else if (exif_data[0] == 'M' && exif_data[1] == 'M')
|
|
isMotorola = true;
|
|
else
|
|
return 0;
|
|
|
|
// Check Tag Mark
|
|
if (isMotorola)
|
|
{
|
|
if (exif_data[2] != 0 || exif_data[3] != 0x2A)
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if (exif_data[3] != 0 || exif_data[2] != 0x2A)
|
|
return 0;
|
|
}
|
|
|
|
// Get first IFD offset (offset to IFD0)
|
|
if (isMotorola)
|
|
{
|
|
if (exif_data[4] != 0 || exif_data[5] != 0)
|
|
return 0;
|
|
offset = exif_data[6];
|
|
offset <<= 8;
|
|
offset += exif_data[7];
|
|
}
|
|
else
|
|
{
|
|
if (exif_data[7] != 0 || exif_data[6] != 0)
|
|
return 0;
|
|
offset = exif_data[5];
|
|
offset <<= 8;
|
|
offset += exif_data[4];
|
|
}
|
|
|
|
if (offset > exif_data_size - 2)
|
|
return 0; // check end of data segment
|
|
|
|
// Get the number of directory entries contained in this IFD
|
|
if (isMotorola)
|
|
{
|
|
numberOfTags = exif_data[offset];
|
|
numberOfTags <<= 8;
|
|
numberOfTags += exif_data[offset+1];
|
|
}
|
|
else
|
|
{
|
|
numberOfTags = exif_data[offset+1];
|
|
numberOfTags <<= 8;
|
|
numberOfTags += exif_data[offset];
|
|
}
|
|
|
|
if (numberOfTags == 0)
|
|
return 0;
|
|
offset += 2;
|
|
|
|
// Search for Orientation Tag in IFD0 - hey almost there! :D
|
|
while(1)//hopefully this jpeg has correct exif data...
|
|
{
|
|
if (offset > exif_data_size - 12)
|
|
return 0; // check end of data segment
|
|
|
|
// Get Tag number
|
|
if (isMotorola)
|
|
{
|
|
tagNumber = exif_data[offset];
|
|
tagNumber <<= 8;
|
|
tagNumber += exif_data[offset+1];
|
|
}
|
|
else
|
|
{
|
|
tagNumber = exif_data[offset+1];
|
|
tagNumber <<= 8;
|
|
tagNumber += exif_data[offset];
|
|
}
|
|
|
|
if (tagNumber == EXIF_TAG_ORIENTATION)
|
|
break; //found orientation tag
|
|
|
|
if ( --numberOfTags == 0)
|
|
return 0;//no orientation found
|
|
offset += 12;//jump to next tag
|
|
}
|
|
|
|
// Get the Orientation value
|
|
if (isMotorola)
|
|
{
|
|
if (exif_data[offset+8] != 0)
|
|
return 0;
|
|
orientation = exif_data[offset+9];
|
|
}
|
|
else
|
|
{
|
|
if (exif_data[offset+9] != 0)
|
|
return 0;
|
|
orientation = exif_data[offset+8];
|
|
}
|
|
if (orientation > 8)
|
|
{
|
|
orientation = 0;
|
|
return 0;
|
|
}
|
|
|
|
return orientation;//done
|
|
} |