X-Git-Url: http://git.imager.perl.org/imager.git/blobdiff_plain/f873cb01c3143636827b388f613ab9cc3bffafb8..f5bffc46655b035a176ebc0db5529c9c96892ef6:/jpeg.c diff --git a/jpeg.c b/jpeg.c index 86fc7cfb..4f2243f0 100644 --- a/jpeg.c +++ b/jpeg.c @@ -28,15 +28,19 @@ Reads and writes JPEG images #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 */ @@ -97,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); @@ -213,12 +217,13 @@ wiol_empty_output_buffer(j_compress_ptr cinfo) { ssize_t nbytes = JPGS - dest->pub.free_in_buffer; */ - mm_log((1,"wiol_empty_output_buffer(cinfo 0x%p)\n")); + 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); + ERREXIT(cinfo, JERR_FILE_WRITE); } dest->pub.free_in_buffer = JPGS; dest->pub.next_output_byte = dest->buffer; @@ -231,13 +236,12 @@ wiol_term_destination (j_compress_ptr cinfo) { 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); } - - - mm_log((1, "wiol_term_destination(cinfo %p)\n", cinfo)); - mm_log((1, "wiol_term_destination: dest %p\n", cinfo->dest)); + if (dest != NULL) myfree(dest->buffer); } @@ -333,6 +337,45 @@ my_error_exit (j_common_ptr cinfo) { 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) @@ -340,14 +383,21 @@ my_error_exit (j_common_ptr cinfo) { */ 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(); @@ -358,28 +408,154 @@ 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; } @@ -392,9 +568,12 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) { 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 my_error_mgr jerr; @@ -402,14 +581,15 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { 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)); i_clear_error(); + io_glue_commit_types(ig); if (!(im->channels==1 || im->channels==3)) { - i_push_error(0, "only 1 or 3 channels images can be saved as JPEG"); - return 0; + want_channels = im->channels - 1; } quality = qfactor; @@ -423,21 +603,22 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { jpeg_destroy_compress(&cinfo); if (data) myfree(data); + if (line_buf) + myfree(line_buf); return 0; } - io_glue_commit_types(ig); 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 */ } @@ -445,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) { @@ -462,6 +675,9 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { } } else { + i_color bg; + + i_get_file_background(im, &bg); data = mymalloc(im->xsize * im->channels); if (data) { while (cinfo.next_scanline < cinfo.image_height) { @@ -469,11 +685,12 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { * 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_destroy_compress(&cinfo); @@ -488,6 +705,8 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { jpeg_destroy_compress(&cinfo); + ig->closecb(ig); + return(1); }