php-imagine / Imagine

PHP Object Oriented image manipulation library
https://imagine.readthedocs.io
Other
4.42k stars 530 forks source link

Support for CMYK ? #830

Closed xkzl closed 1 year ago

xkzl commented 2 years ago

Hello,

I have a CMYK image and GD is getting me hard time. Is there a solution to avoid it ? https://github.com/php-imagine/Imagine/blob/9b9aacbffadce8f19abeb992b8d8d3a90cc2a52a/src/Gd/DriverInfo.php#L154

xkzl commented 2 years ago

My images are opened as Gd/Image object (ImageInterface). So I guess I could either convert it into RGB, or find a way to avoid Gd/Image..

ausi commented 2 years ago

You could use a different driver, both Gmagick as well as Imagick should support CMYK.

Also, how did you open the image? I was not able to reproduce your issue. Can you post a stack trace that shows where the mentioned NotSupportedException was thrown?

xkzl commented 2 years ago

I have developed a function where I check if the image is CMYK palette: When you load with GD an image which uses a CMYK palette, GD load it as RGB palette. Which makes the image using wrong colours.

$image = $this->imagine->open($path);
$image->usePalette(is_cmyk($path) ? new CMYK() : new RGB()); 
ausi commented 2 years ago

OK, so opening the image works, but the call to the usePalette() method breaks? This is the expected behavior of GD I think, because it doesn’t support color profile management.

xkzl commented 2 years ago

I get it :)

But GD still opens a RGB palette by default although it is CMYK. This is actually very confusion. Shouldn't GD just trigger an exception when trying to open an invalid image (with CMYK palette) or try to convert to RGB before ? I actually don't mind if GD says that my image is RGB as long as it transformed properly prior final opening

xkzl commented 1 year ago

Hello, I just jumped back into this issue and found a partial solution to my issue. The issue was due to the use of CMYK custom profiles. It seems that the use of ImageInterface::useProfile (or ::usePalette including a profile) is not working. I have done some tests and the resulting picture returns only a generic CMYK profile.

Here is a method to bypass the issue. However this is only a partial answer because this applies after saving the image. When I am converting my picture into a Webp format or any other compressed format using profiles, I fear that webp is wrongly compress and therefore changing the profile afterward will result in a wrongly colored picture.

if(is_cmyk($input))
    write_cmyk_profiles($output, cmyk_profiles($input));

Here is a sample of some homemade functions used to get proper ICC profile. This is using Imagick only, and not imagine/imagine package.

    /**
     * Check if a JPEG image file uses the CMYK colour space.
     * @param string $path The path to the file.
     * return bool
     */
    function is_cmyk(string $path)
    {

        if(!$path || !file_exists($path))

            return false;

        $imagesize = @getimagesize($path);

        if($imagesize === false) return false;

        return array_key_exists('mime', $imagesize) && 'image/jpeg' == $imagesize['mime'] &&

               array_key_exists('channels', $imagesize) && 4 == $imagesize['channels'];

    }

    function is_rgb(string $path)
    {
        if(!$path || !file_exists($path))

            return false;

        $imagesize = @getimagesize($path);

        return array_key_exists('channels', $imagesize) && 3 == $imagesize['channels'];

    }

    function cmyk_profile(string $path, string $name)

    {

        $image = new \Imagick($path); // load image

        return $image->getImageProfiles($name, true)[$name] ?? null; // get profiles

    }

    function write_cmyk_profiles(string $path, array $profiles)

    {

        $image = new \Imagick($path); // load image

        foreach($profiles as $name => $data)

            $image->profileImage($name, $data);

        $image->setImageColorSpace(is_cmyk($path) ? Imagick::COLORSPACE_CMYK : Imagick::COLORSPACE_RGB);

        $image->writeImage($path);

    }

    function write_cmyk_profile(string $path, string $name, $data)

    {

        $image = new \Imagick($path); // load image

        $image->profileImage($name, $data);

        $image->setImageColorSpace(is_cmyk($path) ? Imagick::COLORSPACE_CMYK : Imagick::COLORSPACE_RGB);

        $image->writeImage($path);

    }

    function cmyk_profiles(string $path)

    {

        $image = new \Imagick($path); // load image

        return $image->getImageProfiles('*', true); // get profiles

    }

    function cmyk_icc_profile(string $path)
    {

        $image = new \Imagick($path); // load image

        return $image->getImageProfiles('icc', true)['icc'] ?? null; // get profiles

    }

    function cmyk2rgb(string $path) // Not working... color are distorted if no icc profile

    {

        if(is_rgb($path)) return $path;

        if(!class_exists("Imagick"))

            throw new Exception(__FUNCTION__."(): Imagick driver not found.");

        $image = new \Imagick($path);

        $image->transformImageColorspace(\Imagick::COLORSPACE_SRGB);

        $image->writeImage($path);

        $image->destroy();

    }

Any idea or support would be very welcome!

ausi commented 1 year ago

Any idea or support would be very welcome!

I’m not sure how I can help. GD does not support color profiles, so using Imagine\Gd just strips the profile and fully ignores it. There is a ticket about this issue in GD itself: https://github.com/libgd/libgd/issues/136

But if you can use Imagick on your server, use Imagine\Imagick that fully supports working with color profiles and just don’t use Imagine\Gd at all.

xkzl commented 1 year ago

Please have a look closely this is not using GD library. This is pure Imagick solution, I had to bypass Imagine\Imagick.

ausi commented 1 year ago

Using the following code should work with all images and convert them to an sRGB image:

$imagine = new \Imagine\Imagick\Imagine();
$image = $imagine->open($path);
$image->usePalette(new \Imagine\Image\Palette\RGB());
$image->strip();
$image->save($path);
xkzl commented 1 year ago

This is what I was doing, but I have issue with CMYK pictures including custom profile. Only one profile is available, but the metadata contains multiple formats: icc,8bim..

^ ImagickException {#6464
  #message: "Unable to set image alpha channel"
  #code: 1
  #file: "./vendor/imagine/imagine/src/Imagick/Image.php"
  #line: 988
  trace: {
    ./vendor/imagine/imagine/src/Imagick/Image.php:988 { …}
    ./vendor/imagine/imagine/src/Imagick/Image.php:700 { …}
    [...]
    }
  }
}
xkzl commented 1 year ago

I have investigated this issue I had in the past again.

This is actually not related to image alpha channel or palette.., but an imagick ressource exhausted. These errors seem to not be cacheable. Keeping CMYK palette prevent high consumption, so that's also why my solution works.

Thank you for your reply, increasing resource disk (on the fly without reboot) in /etc/ImageMagick-6/policy.xml from 1GiB to 4GiB fixed my issue.

<policy domain="resource" name="disk" value="4GiB"/>