/* * gd_jpeg.c: Read and write JPEG (JFIF) format image files using the * gd graphics library (http://www.boutell.com/gd/). * * This software is based in part on the work of the Independent JPEG * Group. For more information on the IJG JPEG software (and JPEG * documentation, etc.), see ftp://ftp.uu.net/graphics/jpeg/. * * NOTE: IJG 12-bit JSAMPLE (BITS_IN_JSAMPLE == 12) mode, although * theoretically supported in this code, has not really been tested. * Caveat emptor. * * Copyright 2000 Doug Becker, mailto:thebeckers@home.com * * Modification 4/18/00 TBB: JPEG_DEBUG rather than just DEBUG, * so VC++ builds don't spew to standard output, causing * major CGI brain damage */ #include #include #include #include #include /* 1.8.1: remove dependency on jinclude.h */ #include "jpeglib.h" #include "jerror.h" #include "gd.h" #include "gdhelpers.h" #ifdef HAVE_LIBJPEG static const char * const GD_JPEG_VERSION = "2.0"; typedef struct _jmpbuf_wrapper { jmp_buf jmpbuf; } jmpbuf_wrapper; /* Called by the IJG JPEG library upon encountering a fatal error */ static void fatal_jpeg_error(j_common_ptr cinfo) { jmpbuf_wrapper *jmpbufw; fprintf(stderr, "gd-jpeg: JPEG library reports unrecoverable error: "); (*cinfo->err->output_message)(cinfo); fflush(stderr); jmpbufw = (jmpbuf_wrapper *)cinfo->client_data; jpeg_destroy(cinfo); if (jmpbufw != 0) { longjmp(jmpbufw->jmpbuf, 1); fprintf(stderr, "gd-jpeg: EXTREMELY fatal error: longjmp" " returned control; terminating\n"); } else { fprintf(stderr, "gd-jpeg: EXTREMELY fatal error: jmpbuf" " unrecoverable; terminating\n"); } fflush(stderr); exit(99); } /* * Write IM to OUTFILE as a JFIF-formatted JPEG image, using quality * QUALITY. If QUALITY is in the range 0-100, increasing values * represent higher quality but also larger image size. If QUALITY is * negative, the IJG JPEG library's default quality is used (which * should be near optimal for many applications). See the IJG JPEG * library documentation for more details. */ void gdImageJpeg(gdImagePtr im, FILE *outFile, int quality) { gdIOCtx *out = gdNewFileCtx(outFile); gdImageJpegCtx(im, out, quality); out->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->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 so we can gdFree it on return from longjmp */ volatile JSAMPROW row = 0; JSAMPROW rowptr[1]; jmpbuf_wrapper jmpbufw; JDIMENSION nlines; char comment[255]; #ifdef JPEG_DEBUG printf("gd-jpeg: gd JPEG version %s\n", GD_JPEG_VERSION); printf("gd-jpeg: JPEG library version %d, %d-bit sample values\n", JPEG_LIB_VERSION, BITS_IN_JSAMPLE); for (i = 0; i < im->colorsTotal; i++) { if (!im->open[i]) printf("gd-jpeg: gd colormap index %d: (%d, %d, %d)\n", i, im->red[i], im->green[i], im->blue[i]); } #endif /* JPEG_DEBUG */ 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) { /* we're here courtesy of longjmp */ 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; /* # of color components per pixel */ cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ jpeg_set_defaults(&cinfo); if (quality >= 0) jpeg_set_quality(&cinfo, quality, TRUE); /* If user requests interlace, translate that to progressive JPEG */ if (gdImageGetInterlaced(im)) { #ifdef JPEG_DEBUG printf("gd-jpeg: interlace set, outputting progressive" " JPEG image\n"); #endif jpeg_simple_progression(&cinfo); } jpeg_gdIOCtx_dest(&cinfo, outfile); row = (JSAMPROW)gdCalloc(1, cinfo.image_width * cinfo.input_components * sizeof(JSAMPLE)); if (row == 0) { fprintf(stderr, "gd-jpeg: error: unable to allocate JPEG row " "structure: gdCalloc returns NULL\n"); jpeg_destroy_compress(&cinfo); return; } rowptr[0] = row; jpeg_start_compress(&cinfo, TRUE); sprintf(comment, "CREATOR: gd-jpeg v%s (using IJG JPEG v%d),", GD_JPEG_VERSION, JPEG_LIB_VERSION); if (quality >= 0) sprintf(comment + strlen(comment), " quality = %d\n", quality); else strcat(comment + strlen(comment), " default quality\n"); jpeg_write_marker(&cinfo, JPEG_COM, (unsigned char *)comment, (unsigned int)strlen(comment)); for (i = 0; i < im->sy; i++) { for (jidx = 0, j = 0; j < im->sx; j++) { int idx = im->pixels[i][j]; /* * NB: Although gd RGB values are ints, their max value is * 255 (see the documentation for gdImageColorAllocate()) * -- perfect for 8-bit JPEG encoding (which is the norm) */ #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) fprintf(stderr, "gd_jpeg: warning: jpeg_write_scanlines" " returns %u -- expected 1\n", nlines); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); gdFree(row); } gdImagePtr gdImageCreateFromJpeg(FILE *inFile) { gdImagePtr im; gdIOCtx *in = gdNewFileCtx(inFile); im = gdImageCreateFromJpegCtx(in); in->free(in); return im; } void jpeg_gdIOCtx_src (j_decompress_ptr cinfo, gdIOCtx *infile); /* * Create a gd-format image from the JPEG-format INFILE. Returns the * image, or NULL upon error. */ gdImagePtr gdImageCreateFromJpegCtx(gdIOCtx *infile) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; jmpbuf_wrapper jmpbufw; /* volatile so we can gdFree them after longjmp */ volatile JSAMPROW row = 0; volatile gdImagePtr im = 0; JSAMPROW rowptr[1]; int i, j, retval; JDIMENSION nrows; #ifdef JPEG_DEBUG printf("gd-jpeg: gd JPEG version %s\n", GD_JPEG_VERSION); printf("gd-jpeg: JPEG library version %d, %d-bit sample values\n", JPEG_LIB_VERSION, BITS_IN_JSAMPLE); #endif 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) { /* we're here courtesy of longjmp */ 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); retval = jpeg_read_header(&cinfo, TRUE); if (retval != JPEG_HEADER_OK) fprintf(stderr, "gd-jpeg: warning: jpeg_read_header returns" " %d, expected %d\n", retval, JPEG_HEADER_OK); if (cinfo.image_height > INT_MAX) fprintf(stderr, "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) fprintf(stderr, "gd-jpeg: warning: JPEG image width (%u) is" " greater than INT_MAX (%d) (and thus greater than" " gd can handle)\n", cinfo.image_width, INT_MAX); im = gdImageCreate((int)cinfo.image_width, (int)cinfo.image_height); if (im == 0) { fprintf(stderr, "gd-jpeg error: cannot allocate gdImage" " struct\n"); goto error; } /* * Have the JPEG library quantize the number of image colors to * 256 maximum; force into RGB colorspace */ cinfo.out_color_space = JCS_RGB; cinfo.quantize_colors = TRUE; cinfo.desired_number_of_colors = gdMaxColors; if (jpeg_start_decompress(&cinfo) != TRUE) fprintf(stderr, "gd-jpeg: warning: jpeg_start_decompress" " reports suspended data source\n"); #ifdef JPEG_DEBUG printf("gd-jpeg: JPEG image information:"); if (cinfo.saw_JFIF_marker) printf(" JFIF version %d.%.2d", (int)cinfo.JFIF_major_version, (int)cinfo.JFIF_minor_version); else if (cinfo.saw_Adobe_marker) printf(" Adobe format"); else printf(" UNKNOWN format"); printf(" %ux%u (raw) / %ux%u (scaled) %d-bit", cinfo.image_width, cinfo.image_height, cinfo.output_width, cinfo.output_height, cinfo.data_precision); printf(" %s", (cinfo.progressive_mode ? "progressive" : "baseline")); printf(" image, %d quantized colors, ", cinfo.actual_number_of_colors); switch (cinfo.jpeg_color_space) { case JCS_GRAYSCALE: printf("grayscale"); break; case JCS_RGB: printf("RGB"); break; case JCS_YCbCr: printf("YCbCr (a.k.a. YUV)"); break; case JCS_CMYK: printf("CMYK"); break; case JCS_YCCK: printf("YCbCrK"); break; default: printf("UNKNOWN (value: %d)", (int)cinfo.jpeg_color_space); break; } printf(" colorspace\n"); fflush(stdout); #endif /* JPEG_DEBUG */ gdImageInterlace(im, cinfo.progressive_mode != 0); im->colorsTotal = cinfo.actual_number_of_colors; if (cinfo.output_components != 1) { fprintf(stderr, "gd-jpeg: error: JPEG color quantization" " request resulted in output_components == %d" " (expected 1)\n", cinfo.output_components); goto error; } for (i = 0; i < im->colorsTotal; i++) { #if BITS_IN_JSAMPLE == 8 im->red[i] = cinfo.colormap[0][i]; im->green[i] = cinfo.colormap[1][i]; im->blue[i] = cinfo.colormap[2][i]; #elif BITS_IN_JSAMPLE == 12 im->red[i] = (cinfo.colormap[0][i] >> 4) & 0xff; im->green[i] = (cinfo.colormap[1][i] >> 4) & 0xff; im->blue[i] = (cinfo.colormap[2][i] >> 4) & 0xff; #else #error IJG JPEG library BITS_IN_JSAMPLE value must be 8 or 12 #endif im->open[i] = 0; #ifdef JPEG_DEBUG printf("gd-jpeg: gd colormap index %d set to (%d, %d, %d)\n", i, im->red[i], im->green[i], im->blue[i]); #endif } row = gdCalloc(cinfo.output_width, sizeof(JSAMPLE)); if (row == 0) { fprintf(stderr, "gd-jpeg: error: unable to allocate row for" " JPEG scanline: gdCalloc returns NULL\n"); goto error; } rowptr[0] = row; for (i = 0; i < cinfo.output_height; i++) { nrows = jpeg_read_scanlines(&cinfo, rowptr, 1); if (nrows != 1) { fprintf(stderr, "gd-jpeg: error: jpeg_read_scanlines" " returns %u, expected 1\n", nrows); goto error; } for (j = 0; j < cinfo.output_width; j++) im->pixels[i][j] = row[j]; } if (jpeg_finish_decompress(&cinfo) != TRUE) fprintf(stderr, "gd-jpeg: warning: jpeg_finish_decompress" " reports suspended data source\n"); jpeg_destroy_decompress(&cinfo); gdFree(row); return im; error: jpeg_destroy_decompress(&cinfo); if (row) gdFree(row); if (im) gdImageDestroy(im); return 0; } /* * * gdIOCtx JPEG data sources and sinks, T. Boutell * almost a simple global replace from T. Lane's stdio versions. * */ /* Different versions of libjpeg use either 'jboolean' or 'boolean', and some platforms define 'boolean', and so forth. Deal with this madness by typedeffing 'safeboolean' to 'boolean' if HAVE_BOOLEAN is already set, because this is the test that libjpeg uses. Otherwise, typedef it to int, because that's what libjpeg does if HAVE_BOOLEAN is not defined. -TBB */ #ifdef HAVE_BOOLEAN typedef boolean safeboolean; #else typedef int safeboolean; #endif /* HAVE_BOOLEAN */ /* Expanded data source object for gdIOCtx input */ typedef struct { struct jpeg_source_mgr pub; /* public fields */ gdIOCtx *infile; /* source stream */ unsigned char * buffer; /* start of buffer */ safeboolean start_of_file; /* have we gotten any data yet? */ } my_source_mgr; typedef my_source_mgr * my_src_ptr; #define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ /* * Initialize source --- called by jpeg_read_header * before any data is actually read. */ void init_source (j_decompress_ptr cinfo) { my_src_ptr src = (my_src_ptr) cinfo->src; /* We reset the empty-input-file flag for each image, * but we don't clear the input buffer. * This is correct behavior for reading a series of images from one source. */ src->start_of_file = TRUE; } /* * Fill the input buffer --- called whenever buffer is emptied. * * In typical applications, this should read fresh data into the buffer * (ignoring the current state of next_input_byte & bytes_in_buffer), * reset the pointer & count to the start of the buffer, and return TRUE * indicating that the buffer has been reloaded. It is not necessary to * fill the buffer entirely, only to obtain at least one more byte. * * There is no such thing as an EOF return. If the end of the file has been * reached, the routine has a choice of ERREXIT() or inserting fake data into * the buffer. In most cases, generating a warning message and inserting a * fake EOI marker is the best course of action --- this will allow the * decompressor to output however much of the image is there. However, * the resulting error message is misleading if the real problem is an empty * input file, so we handle that case specially. * * In applications that need to be able to suspend compression due to input * not being available yet, a FALSE return indicates that no more data can be * obtained right now, but more may be forthcoming later. In this situation, * the decompressor will return to its caller (with an indication of the * number of scanlines it has read, if any). The application should resume * decompression after it has loaded more data into the input buffer. Note * that there are substantial restrictions on the use of suspension --- see * the documentation. * * When suspending, the decompressor will back up to a convenient restart point * (typically the start of the current MCU). next_input_byte & bytes_in_buffer * indicate where the restart point will be if the current call returns FALSE. * Data beyond this point must be rescanned after resumption, so move it to * the front of the buffer rather than discarding it. */ #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; size_t nbytes = 0; /* size_t got; */ /* char *s; */ 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)) { /* EOF or error. If we got any data, don't worry about it. If we didn't, then this is unexpected. */ if (!nbytes) { nbytes = -1; } break; } nbytes += got; } if (nbytes <= 0) { if (src->start_of_file) /* Treat empty input file as fatal error */ ERREXIT(cinfo, JERR_INPUT_EMPTY); WARNMS(cinfo, JWRN_JPEG_EOF); /* Insert a fake EOI marker */ 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; } /* * Skip data --- used to skip over a potentially large amount of * uninteresting data (such as an APPn marker). * * Writers of suspendable-input applications must note that skip_input_data * is not granted the right to give a suspension return. If the skip extends * beyond the data currently in the buffer, the buffer can be marked empty so * that the next read will cause a fill_input_buffer call that can suspend. * Arranging for additional bytes to be discarded before reloading the input * buffer is the application writer's problem. */ void skip_input_data (j_decompress_ptr cinfo, long num_bytes) { my_src_ptr src = (my_src_ptr) cinfo->src; /* Just a dumb implementation for now. Not clear that being smart is worth * any trouble anyway --- large skips are infrequent. */ 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); /* note we assume that fill_input_buffer will never return FALSE, * so suspension need not be handled. */ } src->pub.next_input_byte += (size_t) num_bytes; src->pub.bytes_in_buffer -= (size_t) num_bytes; } } /* * An additional method that can be provided by data source modules is the * resync_to_restart method for error recovery in the presence of RST markers. * For the moment, this source module just uses the default resync method * provided by the JPEG library. That method assumes that no backtracking * is possible. */ /* * Terminate source --- called by jpeg_finish_decompress * after all data has been read. Often a no-op. * * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding * application must deal with any cleanup that should happen even * for error exit. */ void term_source (j_decompress_ptr cinfo) { #if 0 /* never used */ my_src_ptr src = (my_src_ptr) cinfo->src; #endif } /* * Prepare for input from a gdIOCtx stream. * The caller must have already opened the stream, and is responsible * for closing it after finishing decompression. */ void jpeg_gdIOCtx_src (j_decompress_ptr cinfo, gdIOCtx *infile) { my_src_ptr src; /* The source object and input buffer are made permanent so that a series * of JPEG images can be read from the same file by calling jpeg_gdIOCtx_src * only before the first one. (If we discarded the buffer at the end of * one image, we'd likely lose the start of the next one.) * This makes it unsafe to use this manager and a different source * manager serially with the same JPEG object. Caveat programmer. */ if (cinfo->src == NULL) { /* first time for this JPEG object? */ 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; /* use default method */ src->pub.term_source = term_source; src->infile = infile; src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ src->pub.next_input_byte = NULL; /* until buffer loaded */ } /* Expanded data destination object for stdio output */ typedef struct { struct jpeg_destination_mgr pub; /* public fields */ gdIOCtx * outfile; /* target stream */ unsigned char * buffer; /* start of buffer */ } my_destination_mgr; typedef my_destination_mgr * my_dest_ptr; #define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ /* * Initialize destination --- called by jpeg_start_compress * before any data is actually written. */ void init_destination (j_compress_ptr cinfo) { my_dest_ptr dest = (my_dest_ptr) cinfo->dest; /* Allocate the output buffer --- it will be released when done with image */ 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; } /* * Empty the output buffer --- called whenever buffer fills up. * * In typical applications, this should write the entire output buffer * (ignoring the current state of next_output_byte & free_in_buffer), * reset the pointer & count to the start of the buffer, and return TRUE * indicating that the buffer has been dumped. * * In applications that need to be able to suspend compression due to output * overrun, a FALSE return indicates that the buffer cannot be emptied now. * In this situation, the compressor will return to its caller (possibly with * an indication that it has not accepted all the supplied scanlines). The * application should resume compression after it has made more room in the * output buffer. Note that there are substantial restrictions on the use of * suspension --- see the documentation. * * When suspending, the compressor will back up to a convenient restart point * (typically the start of the current MCU). next_output_byte & free_in_buffer * indicate where the restart point will be if the current call returns FALSE. * Data beyond this point will be regenerated after resumption, so do not * write it out when emptying the buffer externally. */ 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; } /* * Terminate destination --- called by jpeg_finish_compress * after all data has been written. Usually needs to flush buffer. * * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding * application must deal with any cleanup that should happen even * for error exit. */ 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; /* Write any data remaining in the buffer */ if (datacount > 0) { if (gdPutBuf(dest->buffer, datacount, dest->outfile) != datacount) ERREXIT(cinfo, JERR_FILE_WRITE); } } /* * Prepare for output to a stdio stream. * The caller must have already opened the stream, and is responsible * for closing it after finishing compression. */ void jpeg_gdIOCtx_dest (j_compress_ptr cinfo, gdIOCtx * outfile) { my_dest_ptr dest; /* The destination object is made permanent so that multiple JPEG images * can be written to the same file without re-executing jpeg_stdio_dest. * This makes it dangerous to use this manager and a different destination * manager serially with the same JPEG object, because their private object * sizes may be different. Caveat programmer. */ if (cinfo->dest == NULL) { /* first time for this JPEG object? */ 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 /* HAVE_JPEG */