Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
15 changes: 14 additions & 1 deletion src/Imagick/Effects.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
30 changes: 30 additions & 0 deletions tests/tests/Effects/AbstractEffectsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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';
Expand Down
Loading