]> git.imager.perl.org - imager.git/commitdiff
CMYK jpeg images were being read as 4 channel images, even though they
authorTony Cook <tony@develop=help.com>
Fri, 14 Jul 2006 14:57:44 +0000 (14:57 +0000)
committerTony Cook <tony@develop=help.com>
Fri, 14 Jul 2006 14:57:44 +0000 (14:57 +0000)
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
MANIFEST
jpeg.c
t/t101jpeg.t
testimg/scmyk.jpg [new file with mode: 0644]

index cd4a635f27c984ee81e7f84220dadf4a01aa02e3..6cfee799277f139ee6cea78b7380e4c414496ad0 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -294,7 +294,8 @@ testimg/penguin-base.ppm
 testimg/scale.gif
 testimg/scale.ppm
 testimg/scalei.gif
 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
 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 3a78967fb39bbe70be4dfca22d54b97e98649d9d..ab32e3df2c674e3cdeeff584d5780c9332cc8689 100644 (file)
--- a/jpeg.c
+++ b/jpeg.c
@@ -337,6 +337,45 @@ my_error_exit (j_common_ptr cinfo) {
   longjmp(myerr->setjmp_buffer, 1);
 }
 
   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)
 
 /*
 =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
 #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;
   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));
 
 
   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;
     jpeg_destroy_decompress(&cinfo); 
     *iptc_itext=NULL;
     *itlength=0;
+    if (line_buffer)
+      myfree(line_buffer);
     return NULL;
   }
   
     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);
 
   (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,
   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;
   }
     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);
   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);
   }
   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);
   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;
 
   /* 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;
   }
 
     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;
   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);
       }
         row_pointer[0] = data;
         (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
       }
+      myfree(data);
     }
     else {
       jpeg_destroy_compress(&cinfo);
     }
     else {
       jpeg_destroy_compress(&cinfo);
index 91f4bc4a1420b7d570d2895111063219600b710b..74c4885f6de088cdbe2f8d48ae5034496f0c3482 100644 (file)
@@ -2,7 +2,7 @@
 use strict;
 use lib 't';
 use Imager qw(:all);
 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);
 
 
 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");
     $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";
   }
 } 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');
   }
     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 (file)
index 0000000..3886a7a
Binary files /dev/null and b/testimg/scmyk.jpg differ