From: Tony Cook Date: Fri, 14 Jul 2006 14:57:44 +0000 (+0000) Subject: CMYK jpeg images were being read as 4 channel images, even though they X-Git-Tag: Imager-0.52~18 X-Git-Url: http://git.imager.perl.org/imager.git/commitdiff_plain/02ea5e4726d139484708f05e1b309799a7b1d922?ds=sidebyside CMYK jpeg images were being read as 4 channel images, even though they have no alpha channel. The colors were being transferred directly from the JPEG image data, this only looked correct because of an old bug in photoshop, kept for compatibility in later versions. Photoshop inverts the ink values in the file, so max cyan coverage is stored at 0, and min as 255, and so on. CMYK jpegs are now read as 3 channel images. The colors are now converted adjusting for the photoshop bug, cmyk images from the only other source I have, corel draw 9, are inverted in the same way, presumably for compatibility with photoshop. If anyone has an application that produces technically correct CMYK jpegs, please provide a sample in jpeg and tiff form so I can attempt to deal with it. Fixes: http://rt.cpan.org/Ticket/Display.html?id=20416 --- diff --git a/MANIFEST b/MANIFEST index cd4a635f..6cfee799 100644 --- a/MANIFEST +++ b/MANIFEST @@ -294,7 +294,8 @@ testimg/penguin-base.ppm testimg/scale.gif testimg/scale.ppm testimg/scalei.gif -testimg/scmyk.tif Simple CMYK image +testimg/scmyk.tif Simple CMYK TIFF image +testimg/scmyk.jpg Simple CMYK JPEG image testimg/scmyka.tif CMYK with one alpha channel testimg/scmykaa.tif CMYK with 2 alpha channels testimg/screen2.gif diff --git a/jpeg.c b/jpeg.c index 3a78967f..ab32e3df 100644 --- a/jpeg.c +++ b/jpeg.c @@ -337,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) @@ -348,12 +387,14 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) { #ifdef IMEXIF_ENABLE int seen_exif = 0; #endif - + i_color *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; mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, length, iptc_itext)); @@ -369,6 +410,8 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) { jpeg_destroy_decompress(&cinfo); *iptc_itext=NULL; *itlength=0; + if (line_buffer) + myfree(line_buffer); return NULL; } @@ -380,14 +423,53 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) { (void) jpeg_read_header(&cinfo, TRUE); (void) jpeg_start_decompress(&cinfo); + + channels = cinfo.output_components; + switch (cinfo.out_color_space) { + case JCS_GRAYSCALE: + transfer_f = transfer_gray; + break; + + case JCS_RGB: + transfer_f = transfer_rgb; + 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, - cinfo.output_components, sizeof(i_sample_t))) { + 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,cinfo.output_components); + + im=i_img_empty_ch(NULL, cinfo.output_width, cinfo.output_height, channels); if (!im) { wiol_term_source(&cinfo); jpeg_destroy_decompress(&cinfo); @@ -395,10 +477,13 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) { } 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); /* check for APP1 marker and save */ markerp = cinfo.marker_list; @@ -416,6 +501,9 @@ i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) { 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; @@ -573,6 +661,7 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { row_pointer[0] = data; (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); } + myfree(data); } else { jpeg_destroy_compress(&cinfo); diff --git a/t/t101jpeg.t b/t/t101jpeg.t index 91f4bc4a..74c4885f 100644 --- a/t/t101jpeg.t +++ b/t/t101jpeg.t @@ -2,7 +2,7 @@ use strict; use lib 't'; use Imager qw(:all); -use Test::More tests => 71; +use Test::More tests => 86; init_log("testout/t101jpeg.log",1); @@ -30,7 +30,7 @@ if (!i_has_format("jpeg")) { $im = Imager->new(xsize=>2, ysize=>2); ok(!$im->write(file=>"testout/nojpeg.jpg"), "should fail to write jpeg"); cmp_ok($im->errstr, '=~', qr/format not supported/, "check no jpeg message"); - skip("no jpeg support", 67); + skip("no jpeg support", 82); } } else { open(FH,">testout/t101.jpg") || die "cannot open testout/t101.jpg for writing\n"; @@ -374,5 +374,34 @@ if (!i_has_format("jpeg")) { is($iptc{headln}, 'Dummy Headline!', 'check iptc headln'); is($iptc{credit}, 'No Credit Given', 'check iptc credit'); } + + { # handling of CMYK jpeg + # http://rt.cpan.org/Ticket/Display.html?id=20416 + my $im = Imager->new; + ok($im->read(file => 'testimg/scmyk.jpg'), 'read a CMYK jpeg'); + is($im->getchannels, 3, "check channel count"); + my $col = $im->getpixel(x => 0, 'y' => 0); + ok($col, "got the 'black' pixel"); + # this is jpeg, so we can't compare colors exactly + # older versions returned this pixel at a light color, but + # it's black in the image + my ($r, $g, $b) = $col->rgba; + cmp_ok($r, '<', 10, 'black - red low'); + cmp_ok($g, '<', 10, 'black - green low'); + cmp_ok($b, '<', 10, 'black - blue low'); + $col = $im->getpixel(x => 15, 'y' => 0); + ok($col, "got the dark blue"); + ($r, $g, $b) = $col->rgba; + cmp_ok($r, '<', 10, 'dark blue - red low'); + cmp_ok($g, '<', 10, 'dark blue - green low'); + cmp_ok($b, '>', 110, 'dark blue - blue middle (bottom)'); + cmp_ok($b, '<', 130, 'dark blue - blue middle (top)'); + $col = $im->getpixel(x => 0, 'y' => 15); + ok($col, "got the red"); + ($r, $g, $b) = $col->rgba; + cmp_ok($r, '>', 245, 'red - red high'); + cmp_ok($g, '<', 10, 'red - green low'); + cmp_ok($b, '<', 10, 'red - blue low'); + } } diff --git a/testimg/scmyk.jpg b/testimg/scmyk.jpg new file mode 100644 index 00000000..3886a7a2 Binary files /dev/null and b/testimg/scmyk.jpg differ