#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <limits.h>
#include <string.h>
#include "gd.h"
#ifdef HAVE_LIBJPEG
#include "gdhelpers.h"
#undef HAVE_STDLIB_H
#include "jpeglib.h"
#include "jerror.h"
static const char *const GD_JPEG_VERSION = "1.0";
typedef struct _jmpbuf_wrapper
{
jmp_buf jmpbuf;
int ignore_warning;
} jmpbuf_wrapper;
static long php_jpeg_emit_message(j_common_ptr jpeg_info, int level)
{
char message[JMSG_LENGTH_MAX];
jmpbuf_wrapper *jmpbufw;
int ignore_warning = 0;
jmpbufw = (jmpbuf_wrapper *) jpeg_info->client_data;
if (jmpbufw != 0) {
ignore_warning = jmpbufw->ignore_warning;
}
(jpeg_info->err->format_message)(jpeg_info,message);
if (level < 0) {
if ((jpeg_info->err->num_warnings == 0) || (jpeg_info->err->trace_level >= 3)) {
php_gd_error_ex(ignore_warning ? E_NOTICE : E_WARNING, "gd-jpeg, libjpeg: recoverable error: %s\n", message);
}
jpeg_info->err->num_warnings++;
} else {
if (jpeg_info->err->trace_level >= level) {
php_gd_error_ex(E_NOTICE, "gd-jpeg, libjpeg: strace message: %s\n", message);
}
}
return 1;
}
static void fatal_jpeg_error (j_common_ptr cinfo)
{
jmpbuf_wrapper *jmpbufw;
php_gd_error("gd-jpeg: JPEG library reports unrecoverable error: ");
(*cinfo->err->output_message) (cinfo);
jmpbufw = (jmpbuf_wrapper *) cinfo->client_data;
jpeg_destroy (cinfo);
if (jmpbufw != 0) {
longjmp (jmpbufw->jmpbuf, 1);
php_gd_error_ex(E_ERROR, "gd-jpeg: EXTREMELY fatal error: longjmp returned control; terminating");
} else {
php_gd_error_ex(E_ERROR, "gd-jpeg: EXTREMELY fatal error: jmpbuf unrecoverable; terminating");
}
exit (99);
}
int gdJpegGetVersionInt()
{
return JPEG_LIB_VERSION;
}
const char * gdJpegGetVersionString()
{
switch(JPEG_LIB_VERSION) {
case 62:
return "6b";
break;
case 70:
return "7";
break;
case 80:
return "8";
break;
case 90:
return "9 compatible";
break;
default:
return "unknown";
}
}
void gdImageJpeg (gdImagePtr im, FILE * outFile, int quality)
{
gdIOCtx *out = gdNewFileCtx (outFile);
gdImageJpegCtx (im, out, quality);
out->gd_free (out);
}
void *gdImageJpegPtr (gdImagePtr im, int *size, int quality)
{
void *rv;
gdIOCtx *out = gdNewDynamicCtx (2048, NULL);
gdImageJpegCtx (im, out, quality);
rv = gdDPExtractData (out, size);
out->gd_free (out);
return rv;
}
void jpeg_gdIOCtx_dest (j_compress_ptr cinfo, gdIOCtx * outfile);
void gdImageJpegCtx (gdImagePtr im, gdIOCtx * outfile, int quality)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
int i, j, jidx;
volatile JSAMPROW row = 0;
JSAMPROW rowptr[1];
jmpbuf_wrapper jmpbufw;
JDIMENSION nlines;
char comment[255];
memset (&cinfo, 0, sizeof (cinfo));
memset (&jerr, 0, sizeof (jerr));
cinfo.err = jpeg_std_error (&jerr);
cinfo.client_data = &jmpbufw;
if (setjmp (jmpbufw.jmpbuf) != 0) {
if (row) {
gdFree (row);
}
return;
}
cinfo.err->error_exit = fatal_jpeg_error;
jpeg_create_compress (&cinfo);
cinfo.image_width = im->sx;
cinfo.image_height = im->sy;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults (&cinfo);
if (quality >= 0) {
jpeg_set_quality (&cinfo, quality, TRUE);
}
if (gdImageGetInterlaced (im)) {
jpeg_simple_progression (&cinfo);
}
jpeg_gdIOCtx_dest (&cinfo, outfile);
row = (JSAMPROW) safe_emalloc(cinfo.image_width * cinfo.input_components, sizeof(JSAMPLE), 0);
memset(row, 0, cinfo.image_width * cinfo.input_components * sizeof(JSAMPLE));
rowptr[0] = row;
jpeg_start_compress (&cinfo, TRUE);
if (quality >= 0) {
snprintf(comment, sizeof(comment)-1, "CREATOR: gd-jpeg v%s (using IJG JPEG v%d), quality = %d\n", GD_JPEG_VERSION, JPEG_LIB_VERSION, quality);
} else {
snprintf(comment, sizeof(comment)-1, "CREATOR: gd-jpeg v%s (using IJG JPEG v%d), default quality\n", GD_JPEG_VERSION, JPEG_LIB_VERSION);
}
jpeg_write_marker (&cinfo, JPEG_COM, (unsigned char *) comment, (unsigned int) strlen (comment));
if (im->trueColor) {
#if BITS_IN_JSAMPLE == 12
php_gd_error("gd-jpeg: error: jpeg library was compiled for 12-bit precision. This is mostly useless, because JPEGs on the web are 8-bit and such versions of the jpeg library won't read or write them. GD doesn't support these unusual images. Edit your jmorecfg.h file to specify the correct precision and completely 'make clean' and 'make install' libjpeg again. Sorry");
goto error;
#endif
for (i = 0; i < im->sy; i++) {
for (jidx = 0, j = 0; j < im->sx; j++) {
int val = im->tpixels[i][j];
row[jidx++] = gdTrueColorGetRed (val);
row[jidx++] = gdTrueColorGetGreen (val);
row[jidx++] = gdTrueColorGetBlue (val);
}
nlines = jpeg_write_scanlines (&cinfo, rowptr, 1);
if (nlines != 1) {
php_gd_error_ex(E_WARNING, "gd_jpeg: warning: jpeg_write_scanlines returns %u -- expected 1", nlines);
}
}
} else {
for (i = 0; i < im->sy; i++) {
for (jidx = 0, j = 0; j < im->sx; j++) {
int idx = im->pixels[i][j];
#if BITS_IN_JSAMPLE == 8
row[jidx++] = im->red[idx];
row[jidx++] = im->green[idx];
row[jidx++] = im->blue[idx];
#elif BITS_IN_JSAMPLE == 12
row[jidx++] = im->red[idx] << 4;
row[jidx++] = im->green[idx] << 4;
row[jidx++] = im->blue[idx] << 4;
#else
#error IJG JPEG library BITS_IN_JSAMPLE value must be 8 or 12
#endif
}
nlines = jpeg_write_scanlines (&cinfo, rowptr, 1);
if (nlines != 1) {
php_gd_error_ex(E_WARNING, "gd_jpeg: warning: jpeg_write_scanlines returns %u -- expected 1", nlines);
}
}
}
jpeg_finish_compress (&cinfo);
jpeg_destroy_compress (&cinfo);
gdFree (row);
}
gdImagePtr gdImageCreateFromJpeg (FILE * inFile)
{
return gdImageCreateFromJpegEx(inFile, 1);
}
gdImagePtr gdImageCreateFromJpegEx (FILE * inFile, int ignore_warning)
{
gdImagePtr im;
gdIOCtx *in = gdNewFileCtx(inFile);
im = gdImageCreateFromJpegCtxEx(in, ignore_warning);
in->gd_free (in);
return im;
}
gdImagePtr gdImageCreateFromJpegPtr (int size, void *data)
{
return gdImageCreateFromJpegPtrEx(size, data, 1);
}
gdImagePtr gdImageCreateFromJpegPtrEx (int size, void *data, int ignore_warning)
{
gdImagePtr im;
gdIOCtx *in = gdNewDynamicCtxEx(size, data, 0);
im = gdImageCreateFromJpegCtxEx(in, ignore_warning);
in->gd_free(in);
return im;
}
void jpeg_gdIOCtx_src (j_decompress_ptr cinfo, gdIOCtx * infile);
static int CMYKToRGB(int c, int m, int y, int k, int inverted);
gdImagePtr gdImageCreateFromJpegCtx (gdIOCtx * infile)
{
return gdImageCreateFromJpegCtxEx(infile, 1);
}
gdImagePtr gdImageCreateFromJpegCtxEx (gdIOCtx * infile, int ignore_warning)
{
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
jmpbuf_wrapper jmpbufw;
volatile JSAMPROW row = 0;
volatile gdImagePtr im = 0;
JSAMPROW rowptr[1];
unsigned int i, j;
int retval;
JDIMENSION nrows;
int channels = 3;
int inverted = 0;
memset (&cinfo, 0, sizeof (cinfo));
memset (&jerr, 0, sizeof (jerr));
jmpbufw.ignore_warning = ignore_warning;
cinfo.err = jpeg_std_error (&jerr);
cinfo.client_data = &jmpbufw;
cinfo.err->emit_message = (void (*)(j_common_ptr,int)) php_jpeg_emit_message;
if (setjmp (jmpbufw.jmpbuf) != 0) {
if (row) {
gdFree (row);
}
if (im) {
gdImageDestroy (im);
}
return 0;
}
cinfo.err->error_exit = fatal_jpeg_error;
jpeg_create_decompress (&cinfo);
jpeg_gdIOCtx_src (&cinfo, infile);
jpeg_save_markers(&cinfo, JPEG_APP0 + 14, 256);
retval = jpeg_read_header (&cinfo, TRUE);
if (retval != JPEG_HEADER_OK) {
php_gd_error_ex(E_WARNING, "gd-jpeg: warning: jpeg_read_header returned %d, expected %d", retval, JPEG_HEADER_OK);
}
if (cinfo.image_height > INT_MAX) {
php_gd_error_ex(E_WARNING, "gd-jpeg: warning: JPEG image height (%u) is greater than INT_MAX (%d) (and thus greater than gd can handle)", cinfo.image_height, INT_MAX);
}
if (cinfo.image_width > INT_MAX) {
php_gd_error_ex(E_WARNING, "gd-jpeg: warning: JPEG image width (%u) is greater than INT_MAX (%d) (and thus greater than gd can handle)", cinfo.image_width, INT_MAX);
}
im = gdImageCreateTrueColor ((int) cinfo.image_width, (int) cinfo.image_height);
if (im == 0) {
php_gd_error("gd-jpeg error: cannot allocate gdImage struct");
goto error;
}
if ((cinfo.jpeg_color_space == JCS_CMYK) || (cinfo.jpeg_color_space == JCS_YCCK)) {
cinfo.out_color_space = JCS_CMYK;
} else {
cinfo.out_color_space = JCS_RGB;
}
if (jpeg_start_decompress (&cinfo) != TRUE) {
php_gd_error("gd-jpeg: warning: jpeg_start_decompress reports suspended data source");
}
#if 0
gdImageInterlace (im, cinfo.progressive_mode != 0);
#endif
if (cinfo.out_color_space == JCS_RGB) {
if (cinfo.output_components != 3) {
php_gd_error_ex(E_WARNING, "gd-jpeg: error: JPEG color quantization request resulted in output_components == %d (expected 3 for RGB)", cinfo.output_components);
goto error;
}
channels = 3;
} else if (cinfo.out_color_space == JCS_CMYK) {
jpeg_saved_marker_ptr marker;
if (cinfo.output_components != 4) {
php_gd_error_ex(E_WARNING, "gd-jpeg: error: JPEG color quantization request resulted in output_components == %d (expected 4 for CMYK)", cinfo.output_components);
goto error;
}
channels = 4;
marker = cinfo.marker_list;
while (marker) {
if ((marker->marker == (JPEG_APP0 + 14)) && (marker->data_length >= 12) && (!strncmp((const char *) marker->data, "Adobe", 5))) {
inverted = 1;
break;
}
marker = marker->next;
}
} else {
php_gd_error_ex(E_WARNING, "gd-jpeg: error: unexpected colorspace.");
goto error;
}
#if BITS_IN_JSAMPLE == 12
php_gd_error("gd-jpeg: error: jpeg library was compiled for 12-bit precision. This is mostly useless, because JPEGs on the web are 8-bit and such versions of the jpeg library won't read or write them. GD doesn't support these unusual images. Edit your jmorecfg.h file to specify the correct precision and completely 'make clean' and 'make install' libjpeg again. Sorry.");
goto error;
#endif
row = safe_emalloc(cinfo.output_width * channels, sizeof(JSAMPLE), 0);
memset(row, 0, cinfo.output_width * channels * sizeof(JSAMPLE));
rowptr[0] = row;
if (cinfo.out_color_space == JCS_CMYK) {
for (i = 0; i < cinfo.output_height; i++) {
register JSAMPROW currow = row;
register int *tpix = im->tpixels[i];
nrows = jpeg_read_scanlines (&cinfo, rowptr, 1);
if (nrows != 1) {
php_gd_error_ex(E_WARNING, "gd-jpeg: error: jpeg_read_scanlines returns %u, expected 1", nrows);
goto error;
}
for (j = 0; j < cinfo.output_width; j++, currow += 4, tpix++) {
*tpix = CMYKToRGB (currow[0], currow[1], currow[2], currow[3], inverted);
}
}
} else {
for (i = 0; i < cinfo.output_height; i++) {
register JSAMPROW currow = row;
register int *tpix = im->tpixels[i];
nrows = jpeg_read_scanlines (&cinfo, rowptr, 1);
if (nrows != 1) {
php_gd_error_ex(E_WARNING, "gd-jpeg: error: jpeg_read_scanlines returns %u, expected 1", nrows);
goto error;
}
for (j = 0; j < cinfo.output_width; j++, currow += 3, tpix++) {
*tpix = gdTrueColor (currow[0], currow[1], currow[2]);
}
}
}
if (jpeg_finish_decompress (&cinfo) != TRUE) {
php_gd_error("gd-jpeg: warning: jpeg_finish_decompress reports suspended data source");
}
if (!ignore_warning) {
if (cinfo.err->num_warnings > 0) {
goto error;
}
}
jpeg_destroy_decompress (&cinfo);
gdFree (row);
return im;
error:
jpeg_destroy_decompress (&cinfo);
if (row) {
gdFree (row);
}
if (im) {
gdImageDestroy (im);
}
return 0;
}
static int CMYKToRGB(int c, int m, int y, int k, int inverted)
{
if (inverted) {
c = 255 - c;
m = 255 - m;
y = 255 - y;
k = 255 - k;
}
return gdTrueColor((255 - c) * (255 - k) / 255, (255 - m) * (255 - k) / 255, (255 - y) * (255 - k) / 255);
}
#ifdef HAVE_BOOLEAN
typedef boolean safeboolean;
#else
typedef int safeboolean;
#endif
typedef struct
{
struct jpeg_source_mgr pub;
gdIOCtx *infile;
unsigned char *buffer;
safeboolean start_of_file;
} my_source_mgr;
typedef my_source_mgr *my_src_ptr;
#define INPUT_BUF_SIZE 4096
void init_source (j_decompress_ptr cinfo)
{
my_src_ptr src = (my_src_ptr) cinfo->src;
src->start_of_file = TRUE;
}
#define END_JPEG_SEQUENCE "\r\n[*]--:END JPEG:--[*]\r\n"
safeboolean fill_input_buffer (j_decompress_ptr cinfo)
{
my_src_ptr src = (my_src_ptr) cinfo->src;
ssize_t nbytes = 0;
memset(src->buffer, 0, INPUT_BUF_SIZE);
while (nbytes < INPUT_BUF_SIZE) {
int got = gdGetBuf(src->buffer + nbytes, INPUT_BUF_SIZE - nbytes, src->infile);
if (got == EOF || got == 0) {
if (!nbytes) {
nbytes = -1;
}
break;
}
nbytes += got;
}
if (nbytes <= 0) {
if (src->start_of_file) {
ERREXIT (cinfo, JERR_INPUT_EMPTY);
}
WARNMS (cinfo, JWRN_JPEG_EOF);
src->buffer[0] = (unsigned char) 0xFF;
src->buffer[1] = (unsigned char) JPEG_EOI;
nbytes = 2;
}
src->pub.next_input_byte = src->buffer;
src->pub.bytes_in_buffer = nbytes;
src->start_of_file = FALSE;
return TRUE;
}
void skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
my_src_ptr src = (my_src_ptr) cinfo->src;
if (num_bytes > 0) {
while (num_bytes > (long) src->pub.bytes_in_buffer) {
num_bytes -= (long) src->pub.bytes_in_buffer;
(void) fill_input_buffer (cinfo);
}
src->pub.next_input_byte += (size_t) num_bytes;
src->pub.bytes_in_buffer -= (size_t) num_bytes;
}
}
void term_source (j_decompress_ptr cinfo)
{
#if 0
* never used */
my_src_ptr src = (my_src_ptr) cinfo->src;
#endif
}
void jpeg_gdIOCtx_src (j_decompress_ptr cinfo, gdIOCtx * infile)
{
my_src_ptr src;
if (cinfo->src == NULL) {
cinfo->src = (struct jpeg_source_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof (my_source_mgr));
src = (my_src_ptr) cinfo->src;
src->buffer = (unsigned char *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, INPUT_BUF_SIZE * sizeof (unsigned char));
}
src = (my_src_ptr) cinfo->src;
src->pub.init_source = init_source;
src->pub.fill_input_buffer = fill_input_buffer;
src->pub.skip_input_data = skip_input_data;
src->pub.resync_to_restart = jpeg_resync_to_restart;
src->pub.term_source = term_source;
src->infile = infile;
src->pub.bytes_in_buffer = 0;
src->pub.next_input_byte = NULL;
}
typedef struct
{
struct jpeg_destination_mgr pub;
gdIOCtx *outfile;
unsigned char *buffer;
} my_destination_mgr;
typedef my_destination_mgr *my_dest_ptr;
#define OUTPUT_BUF_SIZE 4096
void init_destination (j_compress_ptr cinfo)
{
my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
dest->buffer = (unsigned char *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, OUTPUT_BUF_SIZE * sizeof (unsigned char));
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
}
safeboolean empty_output_buffer (j_compress_ptr cinfo)
{
my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
if (gdPutBuf (dest->buffer, OUTPUT_BUF_SIZE, dest->outfile) != (size_t) OUTPUT_BUF_SIZE) {
ERREXIT (cinfo, JERR_FILE_WRITE);
}
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
return TRUE;
}
void term_destination (j_compress_ptr cinfo)
{
my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer;
if (datacount > 0 && ((size_t)gdPutBuf (dest->buffer, datacount, dest->outfile) != datacount)) {
ERREXIT (cinfo, JERR_FILE_WRITE);
}
}
void jpeg_gdIOCtx_dest (j_compress_ptr cinfo, gdIOCtx * outfile)
{
my_dest_ptr dest;
if (cinfo->dest == NULL) {
cinfo->dest = (struct jpeg_destination_mgr *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof (my_destination_mgr));
}
dest = (my_dest_ptr) cinfo->dest;
dest->pub.init_destination = init_destination;
dest->pub.empty_output_buffer = empty_output_buffer;
dest->pub.term_destination = term_destination;
dest->outfile = outfile;
}
#endif