X-Git-Url: http://git.imager.perl.org/imager.git/blobdiff_plain/faa9b3e727c7c57aa07598dc0ba2ada24eb3f9a8..f5bffc46655b035a176ebc0db5529c9c96892ef6:/jpeg.c diff --git a/jpeg.c b/jpeg.c index 6aba1252..4f2243f0 100644 --- a/jpeg.c +++ b/jpeg.c @@ -1,3 +1,25 @@ +/* +=head1 NAME + +jpeg.c - implement saving and loading JPEG images + +=head1 SYNOPSIS + + io_glue *ig; + if (!i_writejpeg_wiol(im, ig, quality)) { + .. error .. + } + im = i_readjpeg_wiol(ig, length, iptc_text, itlength); + +=head1 DESCRIPTION + +Reads and writes JPEG images + +=over + +=cut +*/ + #include #include #ifndef _MSC_VER @@ -6,13 +28,19 @@ #include #include "iolayer.h" -#include "image.h" +#include "imageri.h" #include "jpeglib.h" +#include "jerror.h" +#include +#ifdef IMEXIF_ENABLE +#include "imexif.h" +#endif #define JPEG_APP13 0xED /* APP13 marker code */ -#define JPGS 1024 +#define JPEG_APP1 (JPEG_APP0 + 1) +#define JPGS 16384 -unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI}; +static unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI}; /* Bad design right here */ @@ -73,7 +101,7 @@ wiol_fill_input_buffer(j_decompress_ptr cinfo) { wiol_src_ptr src = (wiol_src_ptr) cinfo->src; ssize_t nbytes; /* We assume that reads are "small" */ - mm_log((1,"wiol_fill_input_buffer(cinfo 0x%p)\n")); + mm_log((1,"wiol_fill_input_buffer(cinfo 0x%p)\n", cinfo)); nbytes = src->data->readcb(src->data, src->buffer, JPGS); @@ -177,14 +205,25 @@ wiol_init_destination (j_compress_ptr cinfo) { static boolean wiol_empty_output_buffer(j_compress_ptr cinfo) { wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest; - ssize_t nbytes = JPGS - dest->pub.free_in_buffer; ssize_t rc; + /* + Previously this code was checking free_in_buffer to see how much + needed to be written. This does not follow the documentation: - mm_log((1,"wiol_emtpy_output_buffer(cinfo 0x%p)\n")); - rc = dest->data->writecb(dest->data, dest->buffer, nbytes); - - if (rc != nbytes) { /* XXX: Should raise some jpeg error */ - mm_log((1, "wiol_empty_output_buffer: Error: nbytes = %d != rc = %d\n", nbytes, rc)); + "In typical applications, it should write out the + *entire* buffer (use the saved start address and buffer length; + ignore the current state of next_output_byte and free_in_buffer)." + + ssize_t nbytes = JPGS - dest->pub.free_in_buffer; + */ + + mm_log((1,"wiol_empty_output_buffer(cinfo 0x%p)\n", cinfo)); + rc = dest->data->writecb(dest->data, dest->buffer, JPGS); + + if (rc != JPGS) { /* XXX: Should raise some jpeg error */ + myfree(dest->buffer); + mm_log((1, "wiol_empty_output_buffer: Error: nbytes = %d != rc = %d\n", JPGS, rc)); + ERREXIT(cinfo, JERR_FILE_WRITE); } dest->pub.free_in_buffer = JPGS; dest->pub.next_output_byte = dest->buffer; @@ -194,8 +233,15 @@ wiol_empty_output_buffer(j_compress_ptr cinfo) { static void wiol_term_destination (j_compress_ptr cinfo) { wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest; - mm_log((1, "wiol_term_destination(cinfo %p)\n", cinfo)); - mm_log((1, "wiol_term_destination: dest %p\n", cinfo->dest)); + size_t nbytes = JPGS - dest->pub.free_in_buffer; + /* yes, this needs to flush the buffer */ + /* needs error handling */ + + if (dest->data->writecb(dest->data, dest->buffer, nbytes) != nbytes) { + myfree(dest->buffer); + ERREXIT(cinfo, JERR_FILE_WRITE); + } + if (dest != NULL) myfree(dest->buffer); } @@ -224,10 +270,6 @@ jpeg_wiol_dest(j_compress_ptr cinfo, io_glue *ig) { dest->pub.next_output_byte = dest->buffer; } - - - - LOCAL(unsigned int) jpeg_getc (j_decompress_ptr cinfo) /* Read next byte */ @@ -267,6 +309,8 @@ my_output_message (j_common_ptr cinfo) { /* Create the message */ (*cinfo->err->format_message) (cinfo, buffer); + i_push_error(0, buffer); + /* Send it to stderr, adding a newline */ mm_log((1, "%s\n", buffer)); } @@ -288,25 +332,74 @@ my_error_exit (j_common_ptr cinfo) { /* Always display the message. */ /* We could postpone this until after returning, if we chose. */ (*cinfo->err->output_message) (cinfo); - + /* Return control to the setjmp point */ longjmp(myerr->setjmp_buffer, 1); } +static void +transfer_cmyk_inverted(i_color *out, JSAMPARRAY in, int width) { + JSAMPROW inrow = *in; + while (width--) { + /* extract and convert to real CMYK */ + /* horribly enough this is correct given cmyk values are inverted */ + int c = *inrow++; + int m = *inrow++; + int y = *inrow++; + int k = *inrow++; + out->rgba.r = (c * k) / MAXJSAMPLE; + out->rgba.g = (m * k) / MAXJSAMPLE; + out->rgba.b = (y * k) / MAXJSAMPLE; + ++out; + } +} + +static void +transfer_rgb(i_color *out, JSAMPARRAY in, int width) { + JSAMPROW inrow = *in; + while (width--) { + out->rgba.r = *inrow++; + out->rgba.g = *inrow++; + out->rgba.b = *inrow++; + ++out; + } +} +static void +transfer_gray(i_color *out, JSAMPARRAY in, int width) { + JSAMPROW inrow = *in; + while (width--) { + out->gray.gray_color = *inrow++; + ++out; + } +} +typedef void (*transfer_function_t)(i_color *out, JSAMPARRAY in, int width); +/* +=item i_readjpeg_wiol(data, length, iptc_itext, itlength) +=cut +*/ i_img* i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) { - i_img *im; - + i_img * volatile im = NULL; +#ifdef IMEXIF_ENABLE + int seen_exif = 0; +#endif + i_color * volatile line_buffer = NULL; struct jpeg_decompress_struct cinfo; struct my_error_mgr jerr; JSAMPARRAY buffer; /* Output row buffer */ int row_stride; /* physical row width in output buffer */ + jpeg_saved_marker_ptr markerp; + transfer_function_t transfer_f; + int channels; + volatile int src_set = 0; - mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, iptc_itext)); + mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, length, iptc_itext)); + + i_clear_error(); iptc_text = iptc_itext; cinfo.err = jpeg_std_error(&jerr.pub); @@ -315,68 +408,217 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) { /* Set error handler */ if (setjmp(jerr.setjmp_buffer)) { + if (src_set) + wiol_term_source(&cinfo); jpeg_destroy_decompress(&cinfo); *iptc_itext=NULL; *itlength=0; + if (line_buffer) + myfree(line_buffer); + if (im) + i_img_destroy(im); return NULL; } jpeg_create_decompress(&cinfo); jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler); + jpeg_save_markers(&cinfo, JPEG_APP1, 0xFFFF); + jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); jpeg_wiol_src(&cinfo, data, length); + src_set = 1; (void) jpeg_read_header(&cinfo, TRUE); (void) jpeg_start_decompress(&cinfo); - im=i_img_empty_ch(NULL,cinfo.output_width,cinfo.output_height,cinfo.output_components); + + channels = cinfo.output_components; + switch (cinfo.out_color_space) { + case JCS_GRAYSCALE: + if (cinfo.output_components != 1) { + mm_log((1, "i_readjpeg: grayscale image with %d channels\n", cinfo.output_components)); + i_push_errorf(0, "grayscale image with invalid components %d", cinfo.output_components); + wiol_term_source(&cinfo); + jpeg_destroy_decompress(&cinfo); + return NULL; + } + transfer_f = transfer_gray; + break; + + case JCS_RGB: + transfer_f = transfer_rgb; + if (cinfo.output_components != 3) { + mm_log((1, "i_readjpeg: RGB image with %d channels\n", cinfo.output_components)); + i_push_errorf(0, "RGB image with invalid components %d", cinfo.output_components); + wiol_term_source(&cinfo); + jpeg_destroy_decompress(&cinfo); + return NULL; + } + break; + + case JCS_CMYK: + if (cinfo.output_components == 4) { + /* we treat the CMYK values as inverted, because that's what that + buggy photoshop does, and everyone has to follow the gorilla. + + Is there any app that still produces correct CMYK JPEGs? + */ + transfer_f = transfer_cmyk_inverted; + channels = 3; + } + else { + mm_log((1, "i_readjpeg: cmyk image with %d channels\n", cinfo.output_components)); + i_push_errorf(0, "CMYK image with invalid components %d", cinfo.output_components); + wiol_term_source(&cinfo); + jpeg_destroy_decompress(&cinfo); + return NULL; + } + break; + + default: + mm_log((1, "i_readjpeg: unknown color space %d\n", cinfo.out_color_space)); + i_push_errorf(0, "Unknown color space %d", cinfo.out_color_space); + wiol_term_source(&cinfo); + jpeg_destroy_decompress(&cinfo); + return NULL; + } + + if (!i_int_check_image_file_limits(cinfo.output_width, cinfo.output_height, + channels, sizeof(i_sample_t))) { + mm_log((1, "i_readjpeg: image size exceeds limits\n")); + wiol_term_source(&cinfo); + jpeg_destroy_decompress(&cinfo); + return NULL; + } + + im=i_img_empty_ch(NULL, cinfo.output_width, cinfo.output_height, channels); + if (!im) { + wiol_term_source(&cinfo); + jpeg_destroy_decompress(&cinfo); + return NULL; + } row_stride = cinfo.output_width * cinfo.output_components; buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); + line_buffer = mymalloc(sizeof(i_color) * cinfo.output_width); while (cinfo.output_scanline < cinfo.output_height) { (void) jpeg_read_scanlines(&cinfo, buffer, 1); - memcpy(im->idata+im->channels*im->xsize*(cinfo.output_scanline-1),buffer[0],row_stride); + transfer_f(line_buffer, buffer, cinfo.output_width); + i_plin(im, 0, cinfo.output_width, cinfo.output_scanline-1, line_buffer); + } + myfree(line_buffer); + line_buffer = NULL; + + /* check for APP1 marker and save */ + markerp = cinfo.marker_list; + while (markerp != NULL) { + if (markerp->marker == JPEG_COM) { + i_tags_add(&im->tags, "jpeg_comment", 0, (const char *)markerp->data, + markerp->data_length, 0); + } +#ifdef IMEXIF_ENABLE + else if (markerp->marker == JPEG_APP1 && !seen_exif) { + seen_exif = i_int_decode_exif(im, markerp->data, markerp->data_length); + } +#endif + + markerp = markerp->next; + } + + i_tags_addn(&im->tags, "jpeg_out_color_space", 0, cinfo.out_color_space); + i_tags_addn(&im->tags, "jpeg_color_space", 0, cinfo.jpeg_color_space); + + if (cinfo.saw_JFIF_marker) { + double xres = cinfo.X_density; + double yres = cinfo.Y_density; + + i_tags_addn(&im->tags, "jpeg_density_unit", 0, cinfo.density_unit); + switch (cinfo.density_unit) { + case 0: /* values are just the aspect ratio */ + i_tags_addn(&im->tags, "i_aspect_only", 0, 1); + i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "none", -1, 0); + break; + + case 1: /* per inch */ + i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "inch", -1, 0); + break; + + case 2: /* per cm */ + i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "centimeter", -1, 0); + xres *= 2.54; + yres *= 2.54; + break; + } + i_tags_set_float2(&im->tags, "i_xres", 0, xres, 6); + i_tags_set_float2(&im->tags, "i_yres", 0, yres, 6); } + (void) jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); *itlength=tlength; + + i_tags_add(&im->tags, "i_format", 0, "jpeg", 4, 0); + mm_log((1,"i_readjpeg_wiol -> (0x%x)\n",im)); return im; } +/* +=item i_writejpeg_wiol(im, ig, qfactor) - +=cut +*/ undef_int i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { - struct stat stbuf; JSAMPLE *image_buffer; int quality; + int got_xres, got_yres, aspect_only, resunit; + double xres, yres; + int comment_entry; + int want_channels = im->channels; struct jpeg_compress_struct cinfo; - struct jpeg_error_mgr jerr; + struct my_error_mgr jerr; JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ int row_stride; /* physical row width in image buffer */ + unsigned char * data = NULL; + i_color *line_buf = NULL; mm_log((1,"i_writejpeg(im %p, ig %p, qfactor %d)\n", im, ig, qfactor)); - if (!(im->channels==1 || im->channels==3)) { fprintf(stderr,"Unable to write JPEG, improper colorspace.\n"); exit(3); } - quality = qfactor; + i_clear_error(); + io_glue_commit_types(ig); - cinfo.err = jpeg_std_error(&jerr); + if (!(im->channels==1 || im->channels==3)) { + want_channels = im->channels - 1; + } + quality = qfactor; + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + jerr.pub.output_message = my_output_message; + jpeg_create_compress(&cinfo); - io_glue_commit_types(ig); + if (setjmp(jerr.setjmp_buffer)) { + jpeg_destroy_compress(&cinfo); + if (data) + myfree(data); + if (line_buf) + myfree(line_buf); + return 0; + } + jpeg_wiol_dest(&cinfo, ig); cinfo.image_width = im -> xsize; /* image width and height, in pixels */ cinfo.image_height = im -> ysize; - if (im->channels==3) { + if (want_channels==3) { cinfo.input_components = 3; /* # of color components per pixel */ cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ } - if (im->channels==1) { + if (want_channels==1) { cinfo.input_components = 1; /* # of color components per pixel */ cinfo.in_color_space = JCS_GRAYSCALE; /* colorspace of input image */ } @@ -384,11 +626,43 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE); /* limit to baseline-JPEG values */ + got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres); + got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres); + if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only)) + aspect_only = 0; + if (!i_tags_get_int(&im->tags, "jpeg_density_unit", 0, &resunit)) + resunit = 1; /* per inch */ + if (resunit < 0 || resunit > 2) /* default to inch if invalid */ + resunit = 1; + if (got_xres || got_yres) { + if (!got_xres) + xres = yres; + else if (!got_yres) + yres = xres; + if (aspect_only) + resunit = 0; /* standard tags override format tags */ + if (resunit == 2) { + /* convert to per cm */ + xres /= 2.54; + yres /= 2.54; + } + cinfo.density_unit = resunit; + cinfo.X_density = (int)(xres + 0.5); + cinfo.Y_density = (int)(yres + 0.5); + } + jpeg_start_compress(&cinfo, TRUE); + if (i_tags_find(&im->tags, "jpeg_comment", 0, &comment_entry)) { + jpeg_write_marker(&cinfo, JPEG_COM, + (const JOCTET *)im->tags.tags[comment_entry].data, + im->tags.tags[comment_entry].size); + } + row_stride = im->xsize * im->channels; /* JSAMPLEs per row in image_buffer */ - if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits) { + if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits + && im->channels == want_channels) { image_buffer=im->idata; while (cinfo.next_scanline < cinfo.image_height) { @@ -401,22 +675,26 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { } } else { - unsigned char *data = mymalloc(im->xsize * im->channels); + i_color bg; + + i_get_file_background(im, &bg); + data = mymalloc(im->xsize * im->channels); if (data) { while (cinfo.next_scanline < cinfo.image_height) { /* jpeg_write_scanlines expects an array of pointers to scanlines. * Here the array is only one element long, but you could pass * more than one scanline at a time if that's more convenient. */ - i_gsamp(im, 0, im->xsize, cinfo.next_scanline, data, - NULL, im->channels); + i_gsamp_bg(im, 0, im->xsize, cinfo.next_scanline, data, + want_channels, &bg); row_pointer[0] = data; (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); } + myfree(data); } else { - jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); + i_push_error(0, "out of memory"); return 0; /* out of memory? */ } } @@ -427,5 +705,21 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { jpeg_destroy_compress(&cinfo); + ig->closecb(ig); + return(1); } + +/* +=back + +=head1 AUTHOR + +Arnar M. Hrafnkelsson, addi@umich.edu + +=head1 SEE ALSO + +Imager(3) + +=cut +*/