Files
Rocky5 921f9fe9f8 Clean Copy
If ever adding the XBMC4Xbox source to GitHub use "git add -f -A" or it wont add all the files.
2024-01-08 19:01:29 +00:00

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
}