1

Our graphic artist is delivering image ready for print in CMYK colorspace JPG format, which is correct for printing. I am converting these for display on the web. I understand the "best" thing to do is convert the src.jpg to sRGB colorspace. I've tried both command line using convert and Imagick approach using transformImageColorspace() and find it works fine on my local machine (Fedora 32 Linux) but doesn't work on the production server (CentOS 7.3 Linux).

We've had this process working for a couple of years on production using the command line convert but we recently tightened security and disabled exec from PHP, so I'm trying to make the same process work via the Imagick bindings, but the resultant image doesn't display correctly. I don't know if the ICC color profile isn't being applied properly, or the conversion to sRGB is going wrong, or whatever else may be happening.

Local machine: Version: ImageMagick 6.9.10-86 Q16 x86_64 2020-01-31 https://imagemagick.org

Prod server: Version: ImageMagick 6.9.11-19 Q16 x86_64 2020-06-15 https://imagemagick.org

Both machines show 'lcms' is in the Delegates (built-in) list, which is one thing that may cause trouble but looks fine.

Source image: src.jpg in CMYK format, a mostly grey/white image.

Loading this image in Gnome's Image Viewer shows incorrect colours, a blueish tinge, but I think this is because Gnome doesn't handle the CMYK properly. Loading the same image into GIMP auto-converts it to sRGB and it shows grey/white properly. My aim is to produce output file src_srgb.jpg that displays grey/white when loaded in Image Viewer (so it clearly is a simple RGB profile image).

Source code:

class Logger {
    public function log ($str) {
        echo $str . "\n";
    }
}
$l = new Logger();
$master_filename_abs = 'srgb/src.jpg';

$icc_profile_file = 'AdobeRGB1998.icc';
$pathinfo = pathinfo($master_filename_abs);
// e.g. turn mickey.jpg into mickey_srgb.jpg
$preview_filename = $pathinfo['filename'] . '_srgb.' . $pathinfo['extension'];
$preview_filename_abs = $pathinfo['dirname'] . '/' . $preview_filename;

$useCmdLine = false; // use cmd line 'convert', or Imagick?

try {
  if ($useCmdLine) {
    $cmd = 'convert "' . $master_filename_abs . '" -verbose' .
      ' -profile "' . $icc_profile_file . '"' .
      ' -colorspace srgb' .
      ' -resize 800 "' . $preview_filename_abs . '" 2>&1';

      $l->log('executing "' . $cmd . '"...');
      exec($cmd, $output, $return_var);
      $l->log("exec returned $return_var with " . count($output) . ' lines of output:' .
        join("\n", $output));
  } else {
    $master_img = new Imagick();
    $master_img->readImage($master_filename_abs);

    $icc_profile_contents = file_get_contents($icc_profile_file);
    $l->log('About to profileImage()...');
    $r = $master_img->profileImage('icc', $icc_profile_contents);
    $l->log("profileImage returned $r " . ($r == TRUE ? 'true' : 'false'));

    $space = $master_img->getImageColorspace();
    $l->log("Colorspace starts as $space, CMYK is " . Imagick::COLORSPACE_CMYK);
    $r = $master_img->transformImageColorspace(imagick::COLORSPACE_SRGB);
    $l->log("transformImageColorspace returned $r " . ($r == TRUE ? 'true' : 'false'));
    $space = $master_img->getImageColorspace();
    $l->log("Colorspace after conversion is $space, SRGB is " . Imagick::COLORSPACE_SRGB);

    $master_img->scaleImage(800, 0);
    $l->log('Saving RGB profile version to ' . $preview_filename_abs);
    $master_img->writeImage($preview_filename_abs);
  }
} catch (Exception $ex) {
    $l->log('Caught ' . $ex);
}

Prerequisites: the src.jpg should be in 'srgb' directory, and AdobeRGB1998.icc profile should be in current directory.

As you can see there's both the command line attempt, and the Imagick attempt, in the script, with a $useCmdLine boolean that toggles between them.

Result on local machine: src_srgb.jpg is created and looks OK. Colours match the src.jpg image.

$ /opt/remi/php71/root/bin/php imagick_srgb.php About to profileImage()... profileImage returned 1 true Colorspace starts as 13, CMYK is 12 transformImageColorspace returned 1 true Colorspace after conversion is 13, SRGB is 13 Saving RGB profile version to srgb/src_srgb.jpg

identify -verbose srgb/src_srgb.jpg shows:

  Colorspace: sRGB
  Type: TrueColor
  ...
  Profiles:
    Profile-8bim: 6694 bytes
    Profile-exif: 4617 bytes
    Profile-icc: 560 bytes
    Profile-iptc: 17 bytes
      unknown[2,0]: 
      Image Name[2,5]: Print
    Profile-xmp: 16695 bytes

Result on prod server: src_srgb.jpg is created but looks blueish.

$ /opt/cpanel/ea-php73/root/usr/bin/php imagick_srgb.php 
About to profileImage() with 1 bytes of profile data...
profileImage returned 1 true
Colorspace starts as 12, CMYK is 12
transformImageColorspace returned 1 true
Colorspace after conversion is 13, SRGB is 13
Saving RGB profile version to srgb/src_srgb.jpg

identify -verbose srgb/src_srgb.jpg shows:

  Colorspace: sRGB
  Type: TrueColor
  ...
  Profiles:
    Profile-8bim: 6694 bytes
    Profile-exif: 4617 bytes
    Profile-iptc: 17 bytes
      unknown[2,0]: 
      Image Name[2,5]: Print
    Profile-xmp: 16695 bytes

I see the 'good' version on local machine has Profile-icc in the list of profiles, whereas the 'bad' version from prod does not. Could this be connected to the root cause of the problem, and if so, what can I do about it? I believe exif, iptc and xmp profiles are metadata and can be safely ignored for the purpose of this test.

From a little trial and error I think I've got the transformImageColorspace and profileImage calls in the right order.

Advice would be hugely appreciated.

I cannot share the full src.jpg but here is a cropped part of it (cropped with imagemagick convert to retain its colour profile etc) enter image description here

Edit Reading PHP Imagick won't add ICC color profile I thought I'd demonstrate production server's lcms presence:

$ convert -list configure | grep DELEGATES
DELEGATES      bzlib djvu mpeg fftw fontconfig freetype gslib heic jbig jng jpeg lcms lzma openexr openjp2 pango png raqm raw rsvg tiff webp wmf x xml zlib zstd
DELEGATES      bzlib cairo djvu fftw fontconfig freetype gslib gvc heic jbig jng jp2 jpeg lcms ltdl lzma openexr pangocairo png ps raqm raw rsvg tiff webp wmf x xml zlib
$ rpm -qa | grep cms
lcms2-devel-2.6-3.el7.x86_64
lcms2-2.6-3.el7.x86_64

Edit 2 There is something weird about imagemagick on the two systems. My local machine reports an icc profile on src.jpg, but production does not, for exactly the same image. I tried downloading my own attachment to this post as SO.jpg to check, copied it to prod, and diffed the output of identify -verbose SO.jpg:

$ diff so_local so_prod
1c1,2
< Image: SO.jpg
---
> Image:
>   Filename: srgb/SO.jpg
12c13
<   Endianess: Undefined
---
>   Endianness: Undefined
83,84d83
<   Profiles:
<     Profile-icc: 557168 bytes
86,89c85,86
<     date:create: 2020-06-17T06:14:10+00:00
<     date:modify: 2020-06-17T06:14:10+00:00
<     icc:copyright: Copyright 2000 Adobe Systems, Inc.
<     icc:description: U.S. Web Coated (SWOP) v2
---
>     date:create: 2020-06-17T06:14:59+00:00
>     date:modify: 2020-06-17T06:14:59+00:00
94c91
<     filename: SO.jpg
---
>     filename: srgb/SO.jpg
99c96
<   Pixels per second: 25.3889MB
---
>   Pixels per second: 27.3785MB
102c99
<   Version: ImageMagick 6.9.10-86 Q16 x86_64 2020-01-31 https://imagemagick.org
---
>   Version: ImageMagick 6.9.11-19 Q16 x86_64 2020-06-15 https://imagemagick.org

This really stinks for me, with little understanding of imagemagick. I will look into downgrading ImageMagick packages on production to match local but we're getting into daytime operating hours on a production server so I may not be able to.

Edit 3 Going back over our historical jobs, it seems to have stopped working on June 1 2020, and on that day I see that ImageMagick-6.9.11.14-1.el7.remi.x86_64 was upgraded to ImageMagick-6.9.11.16-1.el7.remi.x86_64 (and the -libs and -devel packages too). So, it looks like this breaking change was introduced between 6.9.11.14-1 and 6.9.11.16-1. The remi repo we get them from only seems to hold two most recent, 6.9.11.18-1 and 6.9.11.19-1 and downgrading to 6.9.11.18-1 does not fix it for me.

Edit 4 May have got PHP Imagick bindings to do the conversion successfully. From a comment at https://www.imagemagick.org/discourse-server/viewtopic.php?p=67114#p67114 "you should define the colorspace CMYK before , apply whatever operation you need and then define the RGB colorspace" with the example convert -colorspace CMYK mona_lisa.cmyk.jpg -modulate 110 -colorspace RGB monalisa_rgb.jpg .. so I tried it in a desperate attempt (first setting profile to null to remove any existing ones):

$r = $master_img->profileImage('icc', null);

$r = $master_img->profileImage('icc', file_get_contents($cmyk_icc_profile_file));

$r = $master_img->profileImage('icc', file_get_contents($srgb_icc_profile_file));

$r = $master_img->transformImageColorspace(imagick::COLORSPACE_SRGB);

To my great surprise, with my local version 6.9.10-86 and old version 6.9.11-14 on prod, the output file appears correct in Gnome Image Viewer and web browser, so I think that's one problem solved. I've still not figured out why the latest version of ImageMagick 6.9.11-19 doesn't seem to work, will keep at it.

Edit 5 The ImageMagick guys accepted and fixed my bug report :) https://github.com/ImageMagick/ImageMagick6/issues/83

Neek
  • 7,181
  • 3
  • 37
  • 47
  • Usually you do conversion on your machine (or better in a machine with good calibrate screen) and then you export with your expected colour space, and to the required size/resolution (to avoid unnecessary traffic, but also processing on both ends). -- the rest is an interesting question, but possibly not the ideal one for you. Or you need to process many images? (considering a special server for such transformations (and possibly in batch, to avoid DoS) – Giacomo Catenazzi Jun 17 '20 at 09:48
  • @GiacomoCatenazzi we have a kind of media ingestion admin web GUI that our graphic artist uses to provide artwork, sometimes several times a day. He produces work in Photoshop (or whatever) ready for print in CMYK colourspace. This is converted to web-friendly format (convert to sRGB and scaled down) for immediate presentation to our customers for approval in a fully automated process that is not suited to manual batch operation. So, we really need it to work on this troublesome production server :) – Neek Jun 18 '20 at 05:03
  • Ok. In any case, forget about adding ICC. If a client supports ICC properly, it should also support CMYK. Considering it is mission critical, you should check on relaxing the security rules on that server or to use a second server which will do just this conversion (a very simple form + output, which is then read by your main production server). I'm not sure workarounds will help on long term (and jpeg are a mess: standard over standard over standard, ...so we need good tools). Note: sRGB is the default profile, so no need to include (it will just confuse some old tools). – Giacomo Catenazzi Jun 18 '20 at 07:19
  • Are you saying that an 'icc' profile is not required, so one need not call `profileImage()` when converting to sRGB colour space? Testing on command line shows me that `convert src.jpg -colorspace srgb out.jpg` produces an image with bad colours, but `convert src.jpg -profile AdobeRGB1998.icc -colorspace srgb out.jpg` produces an image with good colours, and an 'icc' profile visible in `identify -verbose` output. I'm still playing with different ImageMagick versions, and cmd line vs PHP Imagick, to try to nail this down. – Neek Jun 19 '20 at 02:23
  • I've done some testing with and without the `-profile` switch on command line, and it definitely seems to be required, see the older version 6.9.11.14 at https://github.com/ImageMagick/ImageMagick6/issues/83#issuecomment-646402346 .. maybe I'm tricking myself somehow and am missing a step that would allow me to omit the use of the profile. – Neek Jun 19 '20 at 03:13
  • For anyone else bumping into this thread, exec was possibly accidentally disabled on our server by our web host re-enabling php-fpm on our server (so this is very specific to apache with php-fpm), which apparently resets the disabled_functions config, see https://forums.cpanel.net/threads/exec-disabled-when-php-fpm-enabled.586831/ .. so in our specific case this was config in /opt/cpanel/ea-php73/root/etc/php-fpm.d/our.host.name.conf under `php_admin_value[disable_functions]` which supercedes php.ini, remove `exec` from the list, and restart php-fpm service to reload config. – Neek Jun 19 '20 at 03:52
  • On embedded jpg, it is not necessary to embed any ICC profiles (sRGB is the default), ICC are much more modern than JPEG older standard. You should check what it is the original profile of the image (not using AdobeRGB as default, graphic designers may choose others). It is confusing, but you have primaries colours (sRGB, AdobeRGB usually) and how to encode the colours (RGB [seldom used], YCC [normally], CMYK [seldom], monochome, ...]. Check the final example and comments in section `https://imagemagick.org/script/command-line-options.php#profile` – Giacomo Catenazzi Jun 19 '20 at 06:48
  • Thanks @GiacomoCatenazzi .. see my original post, and comment above - our graphic artist is producing CMYK colourspace images ready for print, i.e. they are fed directly into our huge inkjet based printing machine which sprays ink directly onto products. CMYK is the right colourspace for him to create this print-ready artwork in, I am in that 'seldom' case you mention :) That link is interesting, see my Edit 4 in the post above, I also discovered if I apply a CMYK icc profile, then RGB, it seems to produce a good looking file. Very weird to me and rather counter intuitive. – Neek Jun 23 '20 at 03:04
  • Not so weird: to get the correct transformation you need to specify the original state and destination state, By giving CMYK and sRGB, `convert` knows which transformation to use. Note: it is good to prepare for CMYK, but JPEG with CMYK is tricky (it could be just an "intention", not real change on data [the same with RGB: default and common JPEG has a different colour model internally, in order to compress and use JPEG algorithms properly). – Giacomo Catenazzi Jun 23 '20 at 06:51

0 Answers0