irrlicht/source/Irrlicht/CImageLoaderJPG.cpp
cutealien 76d013d9d6 Fix crash with large jpg files.
Based somewhat on a patch in Minetest from sfan5 594de99153
There might be more problems which may be the reason they checked for other values in Minetest, but don't have more info for now and so far this works.
Forum: https://irrlicht.sourceforge.io/forum/viewtopic.php?f=2&t=52819&p=306518

git-svn-id: svn://svn.code.sf.net/p/irrlicht/code/trunk@6385 dfc29bdd-3216-0410-991c-e03cc46cb475
2022-05-06 16:00:26 +00:00

315 lines
7.6 KiB
C++

// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CImageLoaderJPG.h"
#ifdef _IRR_COMPILE_WITH_JPG_LOADER_
#include "IReadFile.h"
#include "CImage.h"
#include "os.h"
#include "irrString.h"
namespace irr
{
namespace video
{
//! constructor
CImageLoaderJPG::CImageLoaderJPG()
{
#ifdef _DEBUG
setDebugName("CImageLoaderJPG");
#endif
}
//! destructor
CImageLoaderJPG::~CImageLoaderJPG()
{
}
//! returns true if the file maybe is able to be loaded by this class
//! based on the file extension (e.g. ".tga")
bool CImageLoaderJPG::isALoadableFileExtension(const io::path& filename) const
{
return core::hasFileExtension ( filename, "jpg", "jpeg" );
}
#ifdef _IRR_COMPILE_WITH_LIBJPEG_
// struct for handling jpeg errors
struct irr_jpeg_error_mgr
{
// public jpeg error fields
struct jpeg_error_mgr pub;
// for longjmp, to return to caller on a fatal error
jmp_buf setjmp_buffer;
// for having access to the filename when printing the error messages
core::stringc* filename;
};
void CImageLoaderJPG::init_source (j_decompress_ptr cinfo)
{
// DO NOTHING
}
boolean CImageLoaderJPG::fill_input_buffer (j_decompress_ptr cinfo)
{
// DO NOTHING
return TRUE;
}
void CImageLoaderJPG::skip_input_data (j_decompress_ptr cinfo, long count)
{
jpeg_source_mgr * src = cinfo->src;
if(count > 0)
{
src->bytes_in_buffer -= count;
src->next_input_byte += count;
}
}
void CImageLoaderJPG::term_source (j_decompress_ptr cinfo)
{
// DO NOTHING
}
void CImageLoaderJPG::error_exit (j_common_ptr cinfo)
{
// unfortunately we need to use a goto rather than throwing an exception
// as gcc crashes under linux crashes when using throw from within
// extern c code
// Always display the message
(*cinfo->err->output_message) (cinfo);
// cinfo->err really points to a irr_error_mgr struct
irr_jpeg_error_mgr *myerr = (irr_jpeg_error_mgr*) cinfo->err;
longjmp(myerr->setjmp_buffer, 1);
}
void CImageLoaderJPG::output_message(j_common_ptr cinfo)
{
// display the error message.
c8 temp1[JMSG_LENGTH_MAX];
(*cinfo->err->format_message)(cinfo, temp1);
core::stringc errMsg("JPEG FATAL ERROR in ");
irr_jpeg_error_mgr* myerr = (irr_jpeg_error_mgr*)cinfo->err;
errMsg += *myerr->filename;
os::Printer::log(errMsg.c_str(),temp1, ELL_ERROR);
}
#endif // _IRR_COMPILE_WITH_LIBJPEG_
//! returns true if the file maybe is able to be loaded by this class
bool CImageLoaderJPG::isALoadableFileFormat(io::IReadFile* file) const
{
#ifndef _IRR_COMPILE_WITH_LIBJPEG_
return false;
#else
if (!(file && file->seek(0)))
return false;
unsigned char header[3];
size_t headerLen = file->read(header, sizeof(header));
return headerLen >= 3 && !memcmp(header, "\xFF\xD8\xFF", 3);
#endif
}
//! creates a surface from the file
IImage* CImageLoaderJPG::loadImage(io::IReadFile* file) const
{
#ifndef _IRR_COMPILE_WITH_LIBJPEG_
os::Printer::log("Can't load as not compiled with _IRR_COMPILE_WITH_LIBJPEG_:", file->getFileName(), ELL_DEBUG);
return 0;
#else
if (!file)
return 0;
core::stringc filename = file->getFileName();
long fileSize = file->getSize();
if ( fileSize < 3 )
return 0;
u8 **rowPtr=0;
u8* input = new u8[fileSize];
file->read(input, fileSize);
// allocate and initialize JPEG decompression object
struct jpeg_decompress_struct cinfo;
struct irr_jpeg_error_mgr jerr;
//We have to set up the error handler first, in case the initialization
//step fails. (Unlikely, but it could happen if you are out of memory.)
//This routine fills in the contents of struct jerr, and returns jerr's
//address which we place into the link field in cinfo.
cinfo.err = jpeg_std_error(&jerr.pub);
cinfo.err->error_exit = error_exit;
cinfo.err->output_message = output_message;
jerr.filename = &filename;
// compatibility fudge:
// we need to use setjmp/longjmp for error handling as gcc-linux
// crashes when throwing within external c code
if (setjmp(jerr.setjmp_buffer))
{
// If we get here, the JPEG code has signaled an error.
// We need to clean up the JPEG object and return.
jpeg_destroy_decompress(&cinfo);
delete [] input;
delete [] rowPtr;
// return null pointer
return 0;
}
// Now we can initialize the JPEG decompression object.
jpeg_create_decompress(&cinfo);
// specify data source
jpeg_source_mgr jsrc;
// Set up data pointer
jsrc.bytes_in_buffer = fileSize;
jsrc.next_input_byte = (JOCTET*)input;
cinfo.src = &jsrc;
jsrc.init_source = init_source;
jsrc.fill_input_buffer = fill_input_buffer;
jsrc.skip_input_data = skip_input_data;
jsrc.resync_to_restart = jpeg_resync_to_restart;
jsrc.term_source = term_source;
// Decodes JPG input from whatever source
// Does everything AFTER jpeg_create_decompress
// and BEFORE jpeg_destroy_decompress
// Caller is responsible for arranging these + setting up cinfo
// read file parameters with jpeg_read_header()
jpeg_read_header(&cinfo, TRUE);
bool useCMYK=false;
if (cinfo.jpeg_color_space==JCS_CMYK)
{
cinfo.out_color_space=JCS_CMYK;
cinfo.out_color_components=4;
useCMYK=true;
}
else
{
cinfo.out_color_space=JCS_RGB;
cinfo.out_color_components=3;
}
cinfo.output_gamma=2.2;
cinfo.do_fancy_upsampling=FALSE;
// Start decompressor
jpeg_start_decompress(&cinfo);
// Get image data
u32 rowspan = cinfo.image_width * cinfo.out_color_components;
u32 width = cinfo.image_width;
u32 height = cinfo.image_height;
if ( width > JPEG_MAX_DIMENSION || height > JPEG_MAX_DIMENSION )
{
os::Printer::log("Image dimensions too large for JPG in file", filename, ELL_WARNING);
longjmp(jerr.setjmp_buffer, 1);
}
// Allocate memory for buffer
u8* output = new u8[rowspan * height];
// Here we use the library's state variable cinfo.output_scanline as the
// loop counter, so that we don't have to keep track ourselves.
// Create array of row pointers for lib
rowPtr = new u8* [height];
for( size_t i = 0; i < height; i++ )
rowPtr[i] = &output[ i * rowspan ];
u32 rowsRead = 0;
while( cinfo.output_scanline < cinfo.output_height )
rowsRead += jpeg_read_scanlines( &cinfo, &rowPtr[rowsRead], cinfo.output_height - rowsRead );
delete [] rowPtr;
rowPtr = 0;
// Finish decompression
jpeg_finish_decompress(&cinfo);
// Release JPEG decompression object
// This is an important step since it will release a good deal of memory.
jpeg_destroy_decompress(&cinfo);
// convert image
IImage* image = 0;
if (useCMYK)
{
image = new CImage(ECF_R8G8B8,
core::dimension2d<u32>(width, height));
const u32 size = 3*width*height;
u8* data = (u8*)image->getData();
if (data)
{
for (u32 i=0,j=0; i<size; i+=3, j+=4)
{
// Also works without K, but has more contrast with K multiplied in
// data[i+0] = output[j+2];
// data[i+1] = output[j+1];
// data[i+2] = output[j+0];
data[i+0] = (char)(output[j+2]*(output[j+3]/255.f));
data[i+1] = (char)(output[j+1]*(output[j+3]/255.f));
data[i+2] = (char)(output[j+0]*(output[j+3]/255.f));
}
}
delete [] output;
}
else
image = new CImage(ECF_R8G8B8,
core::dimension2d<u32>(width, height), output);
delete [] input;
return image;
#endif
}
//! creates a loader which is able to load jpeg images
IImageLoader* createImageLoaderJPG()
{
return new CImageLoaderJPG();
}
} // end namespace video
} // end namespace irr
#endif