From 1e8fcfe7c97d18f3dc5ade9f288f012160290adc Mon Sep 17 00:00:00 2001 From: Nicolas Lemoine Date: Thu, 4 Jun 2026 11:22:09 +0200 Subject: [PATCH 1/2] Add test showing the Imagick grayscale effect drops transparency effects()->grayscale() on the Imagick driver switches the image to IMGTYPE_GRAYSCALE, an alpha-less type: ImageMagick deactivates the alpha channel as part of the type switch, so every transparent pixel is encoded as opaque gray. The test saves and reloads the image because the defect only shows at encode time: in-memory pixel reads still expose the stored alpha values. --- tests/tests/Effects/AbstractEffectsTest.php | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/tests/Effects/AbstractEffectsTest.php b/tests/tests/Effects/AbstractEffectsTest.php index d6a2f7c3..4e173d93 100644 --- a/tests/tests/Effects/AbstractEffectsTest.php +++ b/tests/tests/Effects/AbstractEffectsTest.php @@ -13,6 +13,7 @@ use Imagine\Driver\Info; use Imagine\Driver\InfoProvider; +use Imagine\Exception\NotSupportedException; use Imagine\Image\Box; use Imagine\Image\Palette\RGB; use Imagine\Image\Point; @@ -117,6 +118,35 @@ public function testGrayscale() $this->assertEquals($greyG, $greyB); } + public function testGrayscalePreservesTransparency() + { + if (!$this->getDriverInfo()->hasFeature(Info::FEATURE_GRAYSCALEEFFECT)) { + $this->isGoingToThrowException('Imagine\Exception\NotSupportedException'); + } else { + try { + $this->getDriverInfo()->requireFeature(Info::FEATURE_TRANSPARENCY); + } catch (NotSupportedException $x) { + $this->markTestSkipped($x->getMessage()); + } + } + $palette = new RGB(); + $imagine = $this->getImagine(); + + // Grayscaling must desaturate the color channels without dropping the + // alpha channel (on the Imagick driver, IMGTYPE_GRAYSCALE used to + // deactivate it, so every transparent pixel was encoded as opaque gray). + // The image is saved and reloaded because the defect only shows at + // encode time: in-memory pixel reads still expose the stored alpha. + $image = $imagine->create(new Box(20, 20), $palette->color('f00', 0)); + $image->effects() + ->grayscale(); + + $reloaded = $imagine->load($image->get('png')); + $pixel = $reloaded->getColorAt(new Point(10, 10)); + + $this->assertSame(0, $pixel->getAlpha()); + } + public function brightnessProvider() { $color = '#145af0'; From df5cd59f30ccdfd1f9cf5e440fcc85d1ef3bd6cb Mon Sep 17 00:00:00 2001 From: Nicolas Lemoine Date: Thu, 4 Jun 2026 11:26:10 +0200 Subject: [PATCH 2/2] Preserve transparency in the Imagick grayscale effect effects()->grayscale() switched the image to IMGTYPE_GRAYSCALE, an alpha-less image type: ImageMagick deactivates the alpha channel as part of the type switch, so transparent pixels were encoded as opaque gray in the saved file, whatever the output format. Switch to the alpha-preserving grayscale type instead, picked through the same constant fallback chain the driver already uses in Image::setColorspace() (IMGTYPE_GRAYSCALEALPHA since ImageMagick 7 / Imagick 3.4.3, previously IMGTYPE_GRAYSCALEMATTE, hard-coded 3 when neither is defined). The gray rendition is unchanged: both types go through the same colorspace transform, only the alpha trait differs. The GD driver is unaffected (IMG_FILTER_GRAYSCALE keeps alpha); the Gmagick driver does not support transparency at all, so the shared test is skipped there. --- CHANGELOG.md | 1 + src/Imagick/Effects.php | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e03a6e6b..55d1f44d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG ### NEXT (YYYY-MM-DD) +- Fix the Imagick driver deactivating the alpha channel in `effects()->grayscale()` (transparent pixels were encoded as opaque gray); it now uses the alpha-preserving grayscale image type, like `usePalette()` (#880, @nlemoine) ### 1.5.3 (2026-06-03) - Fix the Imagick driver painting a "black box" when pasting an image with transparent areas at an alpha lower than 100; the opacity now scales the existing per-pixel alpha instead of overwriting it (#878, @nlemoine) diff --git a/src/Imagick/Effects.php b/src/Imagick/Effects.php index 1a1846ee..e9ad166f 100644 --- a/src/Imagick/Effects.php +++ b/src/Imagick/Effects.php @@ -95,7 +95,20 @@ public function grayscale() { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_GRAYSCALEEFFECT); try { - $this->imagick->setImageType(\Imagick::IMGTYPE_GRAYSCALE); + // IMGTYPE_GRAYSCALE is an alpha-less image type: ImageMagick deactivates the alpha + // channel when switching to it, so transparent pixels would be encoded as opaque + // gray. Use the alpha-preserving variant instead, as Image::setColorspace() already + // does (the constant was named IMGTYPE_GRAYSCALEMATTE before ImageMagick 7 / Imagick + // 3.4.3, and some combinations of Imagick and ImageMagick versions define neither, + // hence the hard-coded fallback value). + if (defined('\Imagick::IMGTYPE_GRAYSCALEALPHA')) { + $grayscaleType = \Imagick::IMGTYPE_GRAYSCALEALPHA; + } elseif (defined('\Imagick::IMGTYPE_GRAYSCALEMATTE')) { + $grayscaleType = \Imagick::IMGTYPE_GRAYSCALEMATTE; + } else { + $grayscaleType = 3; + } + $this->imagick->setImageType($grayscaleType); } catch (\ImagickException $e) { throw new RuntimeException('Failed to grayscale the image', $e->getCode(), $e); }