From 66040006f320159168dff5eb70dba02ef218aa8a Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 3 Aug 2024 10:56:34 +0200 Subject: [PATCH 01/10] Implement 'indexed' option in PNG encoders --- src/Drivers/Gd/Encoders/PngEncoder.php | 60 ++++++++++++++++-- src/Drivers/Imagick/Encoders/PngEncoder.php | 69 ++++++++++++++++++--- src/Encoders/PngEncoder.php | 2 +- 3 files changed, 116 insertions(+), 15 deletions(-) diff --git a/src/Drivers/Gd/Encoders/PngEncoder.php b/src/Drivers/Gd/Encoders/PngEncoder.php index 6b20ea91..bd6a90c0 100644 --- a/src/Drivers/Gd/Encoders/PngEncoder.php +++ b/src/Drivers/Gd/Encoders/PngEncoder.php @@ -4,22 +4,72 @@ namespace Intervention\Image\Drivers\Gd\Encoders; +use GdImage; +use Intervention\Image\Drivers\Gd\Cloner; use Intervention\Image\EncodedImage; use Intervention\Image\Encoders\PngEncoder as GenericPngEncoder; +use Intervention\Image\Exceptions\AnimationException; +use Intervention\Image\Exceptions\ColorException; +use Intervention\Image\Exceptions\RuntimeException; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\SpecializedInterface; class PngEncoder extends GenericPngEncoder implements SpecializedInterface { + /** + * {@inheritdoc} + * + * @see EncoderInterface::encode() + */ public function encode(ImageInterface $image): EncodedImage { - $gd = $image->core()->native(); - $data = $this->buffered(function () use ($gd) { - imageinterlace($gd, $this->interlaced); - imagepng($gd, null, -1); - imageinterlace($gd, false); + $output = $this->prepareOutput($image); + + // encode + $data = $this->buffered(function () use ($output) { + imageinterlace($output, $this->interlaced); + imagepng($output, null, -1); }); return new EncodedImage($data, 'image/png'); } + + /** + * Prepare given image instance for PNG format output according to encoder settings + * + * @param ImageInterface $image + * @param bool $indexed + * @throws RuntimeException + * @throws ColorException + * @throws AnimationException + * @return GdImage + */ + private function prepareOutput(ImageInterface $image): GdImage + { + if ($this->indexed === false) { + return Cloner::clone($image->core()->native()); + } + + // get blending color + $blendingColor = $this->driver()->colorProcessor($image->colorspace())->colorToNative( + $this->driver()->handleInput($this->driver()->config()->blendingColor) + ); + + // clone output instance + $output = Cloner::cloneEmpty($image->core()->native()); + + // fill with blending color + imagefill($output, 0, 0, $blendingColor); + + // set transparency + imagecolortransparent($output, $blendingColor); + + // copy original into output + imagecopy($output, $image->core()->native(), 0, 0, 0, 0, imagesx($output), imagesy($output)); + + // reduce to indexed color palette + imagetruecolortopalette($output, true, 255); + + return $output; + } } diff --git a/src/Drivers/Imagick/Encoders/PngEncoder.php b/src/Drivers/Imagick/Encoders/PngEncoder.php index 79ef2412..06a7a03d 100644 --- a/src/Drivers/Imagick/Encoders/PngEncoder.php +++ b/src/Drivers/Imagick/Encoders/PngEncoder.php @@ -5,28 +5,79 @@ namespace Intervention\Image\Drivers\Imagick\Encoders; use Imagick; +use ImagickException; use Intervention\Image\EncodedImage; use Intervention\Image\Encoders\PngEncoder as GenericPngEncoder; +use Intervention\Image\Exceptions\AnimationException; +use Intervention\Image\Exceptions\RuntimeException; +use Intervention\Image\Exceptions\ColorException; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\SpecializedInterface; class PngEncoder extends GenericPngEncoder implements SpecializedInterface { + /** + * {@inheritdoc} + * + * @see EncoderInterface::encode() + */ public function encode(ImageInterface $image): EncodedImage { - $format = 'PNG'; - $compression = Imagick::COMPRESSION_ZIP; + $output = $this->prepareOutput($image); - $imagick = $image->core()->native(); - $imagick->setFormat($format); - $imagick->setImageFormat($format); - $imagick->setCompression($compression); - $imagick->setImageCompression($compression); + $output->setCompression(Imagick::COMPRESSION_ZIP); + $output->setImageCompression(Imagick::COMPRESSION_ZIP); if ($this->interlaced) { - $imagick->setInterlaceScheme(Imagick::INTERLACE_LINE); + $output->setInterlaceScheme(Imagick::INTERLACE_LINE); } - return new EncodedImage($imagick->getImagesBlob(), 'image/png'); + return new EncodedImage($output->getImagesBlob(), 'image/png'); + } + + /** + * Prepare given image instance for PNG format output according to encoder settings + * + * @param ImageInterface $image + * @throws AnimationException + * @throws RuntimeException + * @throws ColorException + * @throws ImagickException + * @return Imagick + */ + private function prepareOutput(ImageInterface $image): Imagick + { + if ($this->indexed === false) { + $output = clone $image->core()->native(); + + // ensure to encode PNG image type 6 true color alpha + $output->setFormat('PNG32'); + $output->setImageFormat('PNG32'); + + return $output; + } + + // get blending color + $blendingColor = $this->driver()->colorProcessor($image->colorspace())->colorToNative( + $this->driver()->handleInput($this->driver()->config()->blendingColor) + ); + + // create new image with blending color as background + $output = new Imagick(); + $output->newImage($image->width(), $image->height(), $blendingColor, 'PNG'); + + // set transparency of original image + $output->compositeImage($image->core()->native(), Imagick::COMPOSITE_DSTIN, 0, 0); + $output->transparentPaintImage('#000000', 0, 0, false); + + // copy original and create indexed color palette version + $output->compositeImage($image->core()->native(), Imagick::COMPOSITE_DEFAULT, 0, 0); + $output->quantizeImage(255, $output->getImageColorSpace(), 0, false, false); + + // ensure to encode PNG image type 3 (indexed) + $output->setFormat('PNG8'); + $output->setImageFormat('PNG8'); + + return $output; } } diff --git a/src/Encoders/PngEncoder.php b/src/Encoders/PngEncoder.php index b6bb6cb5..7b2df853 100644 --- a/src/Encoders/PngEncoder.php +++ b/src/Encoders/PngEncoder.php @@ -8,7 +8,7 @@ class PngEncoder extends SpecializableEncoder { - public function __construct(public bool $interlaced = false) + public function __construct(public bool $interlaced = false, public bool $indexed = false) { } } From 460b20568781c1fc83aa23f1fea532d1730b134a Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 3 Aug 2024 11:03:32 +0200 Subject: [PATCH 02/10] Rename class --- .../{CanDetectInterlacedPng.php => CanInspectPngFormat.php} | 2 +- tests/Unit/Drivers/Gd/Encoders/PngEncoderTest.php | 4 ++-- tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename tests/Traits/{CanDetectInterlacedPng.php => CanInspectPngFormat.php} (94%) diff --git a/tests/Traits/CanDetectInterlacedPng.php b/tests/Traits/CanInspectPngFormat.php similarity index 94% rename from tests/Traits/CanDetectInterlacedPng.php rename to tests/Traits/CanInspectPngFormat.php index 19fffe46..510466f7 100644 --- a/tests/Traits/CanDetectInterlacedPng.php +++ b/tests/Traits/CanInspectPngFormat.php @@ -6,7 +6,7 @@ use Intervention\Image\Traits\CanBuildFilePointer; -trait CanDetectInterlacedPng +trait CanInspectPngFormat { use CanBuildFilePointer; diff --git a/tests/Unit/Drivers/Gd/Encoders/PngEncoderTest.php b/tests/Unit/Drivers/Gd/Encoders/PngEncoderTest.php index 9c96591a..69cc7d12 100644 --- a/tests/Unit/Drivers/Gd/Encoders/PngEncoderTest.php +++ b/tests/Unit/Drivers/Gd/Encoders/PngEncoderTest.php @@ -8,14 +8,14 @@ use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Encoders\PngEncoder; use Intervention\Image\Tests\GdTestCase; -use Intervention\Image\Tests\Traits\CanDetectInterlacedPng; +use Intervention\Image\Tests\Traits\CanInspectPngFormat; #[RequiresPhpExtension('gd')] #[CoversClass(\Intervention\Image\Encoders\PngEncoder::class)] #[CoversClass(\Intervention\Image\Drivers\Gd\Encoders\PngEncoder::class)] final class PngEncoderTest extends GdTestCase { - use CanDetectInterlacedPng; + use CanInspectPngFormat; public function testEncode(): void { diff --git a/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php b/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php index 503320b7..268ac830 100644 --- a/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php +++ b/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php @@ -8,14 +8,14 @@ use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Encoders\PngEncoder; use Intervention\Image\Tests\ImagickTestCase; -use Intervention\Image\Tests\Traits\CanDetectInterlacedPng; +use Intervention\Image\Tests\Traits\CanInspectPngFormat; #[RequiresPhpExtension('imagick')] #[CoversClass(\Intervention\Image\Encoders\PngEncoder::class)] #[CoversClass(\Intervention\Image\Drivers\Imagick\Encoders\PngEncoder::class)] final class PngEncoderTest extends ImagickTestCase { - use CanDetectInterlacedPng; + use CanInspectPngFormat; public function testEncode(): void { From 2ee997d98fd08a5a4a54adc5bf0726bc2b563e12 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 3 Aug 2024 11:04:30 +0200 Subject: [PATCH 03/10] Add method to detect PNG color types --- tests/Traits/CanInspectPngFormat.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/Traits/CanInspectPngFormat.php b/tests/Traits/CanInspectPngFormat.php index 510466f7..c09a4f7f 100644 --- a/tests/Traits/CanInspectPngFormat.php +++ b/tests/Traits/CanInspectPngFormat.php @@ -24,4 +24,29 @@ private function isInterlacedPng(string $imagedata): bool return ord($contents[28]) != 0; } + + /** + * Try to detect PNG color type from given binary data + * + * @param string $data + * @return string + */ + private function pngColorType(string $data): string + { + if (substr($data, 1, 3) !== 'PNG') { + return 'unkown'; + } + + $pos = strpos($data, 'IHDR'); + $type = substr($data, $pos + 13, 1); + + return match (unpack('C', $type)[1]) { + 0 => 'grayscale', + 2 => 'truecolor', + 3 => 'indexed', + 4 => 'grayscale-alpha', + 6 => 'truecolor-alpha', + default => 'unknown', + }; + } } From 63990a8fb3352f69ade4fdffbf680043cd02183a Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 3 Aug 2024 11:15:29 +0200 Subject: [PATCH 04/10] Add tests for PNG indexed options --- src/Drivers/Gd/Encoders/PngEncoder.php | 3 +- src/Drivers/Imagick/Encoders/PngEncoder.php | 2 +- tests/BaseTestCase.php | 2 +- tests/GdTestCase.php | 8 +-- tests/ImagickTestCase.php | 8 +-- .../Drivers/Gd/Encoders/PngEncoderTest.php | 57 +++++++++++++++++++ .../Imagick/Encoders/PngEncoderTest.php | 57 +++++++++++++++++++ 7 files changed, 125 insertions(+), 12 deletions(-) diff --git a/src/Drivers/Gd/Encoders/PngEncoder.php b/src/Drivers/Gd/Encoders/PngEncoder.php index bd6a90c0..8368b7a6 100644 --- a/src/Drivers/Gd/Encoders/PngEncoder.php +++ b/src/Drivers/Gd/Encoders/PngEncoder.php @@ -38,7 +38,6 @@ public function encode(ImageInterface $image): EncodedImage * Prepare given image instance for PNG format output according to encoder settings * * @param ImageInterface $image - * @param bool $indexed * @throws RuntimeException * @throws ColorException * @throws AnimationException @@ -51,7 +50,7 @@ private function prepareOutput(ImageInterface $image): GdImage } // get blending color - $blendingColor = $this->driver()->colorProcessor($image->colorspace())->colorToNative( + $blendingColor = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->driver()->config()->blendingColor) ); diff --git a/src/Drivers/Imagick/Encoders/PngEncoder.php b/src/Drivers/Imagick/Encoders/PngEncoder.php index 06a7a03d..c3889b18 100644 --- a/src/Drivers/Imagick/Encoders/PngEncoder.php +++ b/src/Drivers/Imagick/Encoders/PngEncoder.php @@ -58,7 +58,7 @@ private function prepareOutput(ImageInterface $image): Imagick } // get blending color - $blendingColor = $this->driver()->colorProcessor($image->colorspace())->colorToNative( + $blendingColor = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->driver()->config()->blendingColor) ); diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index 41caa2aa..9aa89c0c 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -25,7 +25,7 @@ public static function getTestResourceData($filename = 'test.jpg'): string return file_get_contents(self::getTestResourcePath($filename)); } - public function getTestResourcePointer($filename = 'test.jpg') + public static function getTestResourcePointer($filename = 'test.jpg') { $pointer = fopen('php://temp', 'rw'); fputs($pointer, self::getTestResourceData($filename)); diff --git a/tests/GdTestCase.php b/tests/GdTestCase.php index 2cfa9527..ebe98a01 100644 --- a/tests/GdTestCase.php +++ b/tests/GdTestCase.php @@ -12,14 +12,14 @@ abstract class GdTestCase extends BaseTestCase { - public function readTestImage($filename = 'test.jpg'): Image + public static function readTestImage($filename = 'test.jpg'): Image { return (new Driver())->specialize(new FilePathImageDecoder())->decode( - $this->getTestResourcePath($filename) + static::getTestResourcePath($filename) ); } - public function createTestImage(int $width, int $height): Image + public static function createTestImage(int $width, int $height): Image { $gd = imagecreatetruecolor($width, $height); imagefill($gd, 0, 0, imagecolorallocate($gd, 255, 0, 0)); @@ -32,7 +32,7 @@ public function createTestImage(int $width, int $height): Image ); } - public function createTestAnimation(): Image + public static function createTestAnimation(): Image { $gd1 = imagecreatetruecolor(3, 2); imagefill($gd1, 0, 0, imagecolorallocate($gd1, 255, 0, 0)); diff --git a/tests/ImagickTestCase.php b/tests/ImagickTestCase.php index cbd24dcc..bef7b674 100644 --- a/tests/ImagickTestCase.php +++ b/tests/ImagickTestCase.php @@ -13,14 +13,14 @@ abstract class ImagickTestCase extends BaseTestCase { - public function readTestImage($filename = 'test.jpg'): Image + public static function readTestImage($filename = 'test.jpg'): Image { return (new Driver())->specialize(new FilePathImageDecoder())->decode( - $this->getTestResourcePath($filename) + static::getTestResourcePath($filename) ); } - public function createTestImage(int $width, int $height): Image + public static function createTestImage(int $width, int $height): Image { $background = new ImagickPixel('rgb(255, 0, 0)'); $imagick = new Imagick(); @@ -36,7 +36,7 @@ public function createTestImage(int $width, int $height): Image ); } - public function createTestAnimation(): Image + public static function createTestAnimation(): Image { $imagick = new Imagick(); $imagick->setFormat('gif'); diff --git a/tests/Unit/Drivers/Gd/Encoders/PngEncoderTest.php b/tests/Unit/Drivers/Gd/Encoders/PngEncoderTest.php index 69cc7d12..d9bf1da4 100644 --- a/tests/Unit/Drivers/Gd/Encoders/PngEncoderTest.php +++ b/tests/Unit/Drivers/Gd/Encoders/PngEncoderTest.php @@ -7,8 +7,10 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Encoders\PngEncoder; +use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Tests\GdTestCase; use Intervention\Image\Tests\Traits\CanInspectPngFormat; +use PHPUnit\Framework\Attributes\DataProvider; #[RequiresPhpExtension('gd')] #[CoversClass(\Intervention\Image\Encoders\PngEncoder::class)] @@ -34,4 +36,59 @@ public function testEncodeInterlaced(): void $this->assertMediaType('image/png', (string) $result); $this->assertTrue($this->isInterlacedPng((string) $result)); } + + #[DataProvider('indexedDataProvider')] + public function testEncoderIndexed(ImageInterface $image, PngEncoder $encoder, string $result): void + { + $this->assertEquals( + $result, + $this->pngColorType((string) $encoder->encode($image)), + ); + } + + public static function indexedDataProvider(): array + { + return [ + [ + static::createTestImage(3, 2), // new + new PngEncoder(indexed: false), + 'truecolor-alpha', + ], + [ + static::createTestImage(3, 2), // new + new PngEncoder(indexed: true), + 'indexed', + ], + [ + static::readTestImage('circle.png'), // truecolor-alpha + new PngEncoder(indexed: false), + 'truecolor-alpha', + ], + [ + static::readTestImage('circle.png'), // indexedcolor-alpha + new PngEncoder(indexed: true), + 'indexed', + ], + [ + static::readTestImage('tile.png'), // indexed + new PngEncoder(indexed: false), + 'truecolor-alpha', + ], + [ + static::readTestImage('tile.png'), // indexed + new PngEncoder(indexed: true), + 'indexed', + ], + [ + static::readTestImage('test.jpg'), // jpeg + new PngEncoder(indexed: false), + 'truecolor-alpha', + ], + [ + static::readTestImage('test.jpg'), // jpeg + new PngEncoder(indexed: true), + 'indexed', + ], + ]; + } } diff --git a/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php b/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php index 268ac830..b905bd28 100644 --- a/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php +++ b/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php @@ -7,8 +7,10 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Encoders\PngEncoder; +use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Tests\ImagickTestCase; use Intervention\Image\Tests\Traits\CanInspectPngFormat; +use PHPUnit\Framework\Attributes\DataProvider; #[RequiresPhpExtension('imagick')] #[CoversClass(\Intervention\Image\Encoders\PngEncoder::class)] @@ -34,4 +36,59 @@ public function testEncodeInterlaced(): void $this->assertMediaType('image/png', (string) $result); $this->assertTrue($this->isInterlacedPng((string) $result)); } + + #[DataProvider('indexedDataProvider')] + public function testEncoderIndexed(ImageInterface $image, PngEncoder $encoder, string $result): void + { + $this->assertEquals( + $result, + $this->pngColorType((string) $encoder->encode($image)), + ); + } + + public static function indexedDataProvider(): array + { + return [ + [ + static::createTestImage(3, 2), // new + new PngEncoder(indexed: false), + 'truecolor-alpha', + ], + [ + static::createTestImage(3, 2), // new + new PngEncoder(indexed: true), + 'indexed', + ], + [ + static::readTestImage('circle.png'), // truecolor-alpha + new PngEncoder(indexed: false), + 'truecolor-alpha', + ], + [ + static::readTestImage('circle.png'), // indexedcolor-alpha + new PngEncoder(indexed: true), + 'indexed', + ], + [ + static::readTestImage('tile.png'), // indexed + new PngEncoder(indexed: false), + 'truecolor-alpha', + ], + [ + static::readTestImage('tile.png'), // indexed + new PngEncoder(indexed: true), + 'indexed', + ], + [ + static::readTestImage('test.jpg'), // jpeg + new PngEncoder(indexed: false), + 'truecolor-alpha', + ], + [ + static::readTestImage('test.jpg'), // jpeg + new PngEncoder(indexed: true), + 'indexed', + ], + ]; + } } From 2adc3b45dec8f8f2469d1559738867e186a29620 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 3 Aug 2024 17:04:44 +0200 Subject: [PATCH 05/10] Fix blending problem in GD's PngEncoder --- src/Drivers/Gd/Encoders/PngEncoder.php | 30 +++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Drivers/Gd/Encoders/PngEncoder.php b/src/Drivers/Gd/Encoders/PngEncoder.php index 8368b7a6..3e4fdec8 100644 --- a/src/Drivers/Gd/Encoders/PngEncoder.php +++ b/src/Drivers/Gd/Encoders/PngEncoder.php @@ -5,6 +5,7 @@ namespace Intervention\Image\Drivers\Gd\Encoders; use GdImage; +use Intervention\Image\Colors\Rgb\Color; use Intervention\Image\Drivers\Gd\Cloner; use Intervention\Image\EncodedImage; use Intervention\Image\Encoders\PngEncoder as GenericPngEncoder; @@ -49,19 +50,32 @@ private function prepareOutput(ImageInterface $image): GdImage return Cloner::clone($image->core()->native()); } - // get blending color - $blendingColor = $this->driver()->colorProcessor($image->colorspace())->colorToNative( - $this->driver()->handleInput($this->driver()->config()->blendingColor) - ); - // clone output instance $output = Cloner::cloneEmpty($image->core()->native()); + /** + * Decode configured blending color + * + * @var Color + */ + $blendingColor = $this->driver()->handleInput($this->driver()->config()->blendingColor); + + // allocate blending color with slighty different alpha value + // to avoid "overwriting" pixels with the same color in the + // original image with transprency + $blendingIndex = imagecolorallocatealpha( + $output, + $blendingColor->red()->value(), + $blendingColor->green()->value(), + $blendingColor->blue()->value(), + 1, + ); + // fill with blending color - imagefill($output, 0, 0, $blendingColor); + imagefill($output, 0, 0, $blendingIndex); - // set transparency - imagecolortransparent($output, $blendingColor); + // define blending index as transparent + imagecolortransparent($output, $blendingIndex); // copy original into output imagecopy($output, $image->core()->native(), 0, 0, 0, 0, imagesx($output), imagesy($output)); From 68479181fc880d624013b0ad657233d66606cd07 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sun, 4 Aug 2024 08:41:09 +0200 Subject: [PATCH 06/10] Reactivate blending color methods on ImageInterface --- src/Drivers/Gd/Encoders/PngEncoder.php | 8 ++------ src/Interfaces/ImageInterface.php | 2 -- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Drivers/Gd/Encoders/PngEncoder.php b/src/Drivers/Gd/Encoders/PngEncoder.php index 3e4fdec8..b4600a83 100644 --- a/src/Drivers/Gd/Encoders/PngEncoder.php +++ b/src/Drivers/Gd/Encoders/PngEncoder.php @@ -53,12 +53,8 @@ private function prepareOutput(ImageInterface $image): GdImage // clone output instance $output = Cloner::cloneEmpty($image->core()->native()); - /** - * Decode configured blending color - * - * @var Color - */ - $blendingColor = $this->driver()->handleInput($this->driver()->config()->blendingColor); + // Decode configured blending color + $blendingColor = $image->blendingColor(); // allocate blending color with slighty different alpha value // to avoid "overwriting" pixels with the same color in the diff --git a/src/Interfaces/ImageInterface.php b/src/Interfaces/ImageInterface.php index 726c5207..efd9e221 100644 --- a/src/Interfaces/ImageInterface.php +++ b/src/Interfaces/ImageInterface.php @@ -254,7 +254,6 @@ public function pickColors(int $x, int $y): CollectionInterface; * Return color that is mixed with transparent areas when converting to a format which * does not support transparency. * - * @deprecated Use configuration options of image manager instead * @throws RuntimeException * @return ColorInterface */ @@ -264,7 +263,6 @@ public function blendingColor(): ColorInterface; * Set blending color will have no effect unless image is converted into a format * which does not support transparency. * - * @deprecated Use configuration options of image manager instead * @param mixed $color * @throws RuntimeException * @return ImageInterface From 50feb11dced7d619e163757c95ddbb9e3b3a25dc Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sun, 4 Aug 2024 08:44:12 +0200 Subject: [PATCH 07/10] Refactor code --- src/Drivers/Gd/Encoders/PngEncoder.php | 10 ++++++---- src/Drivers/Imagick/Encoders/PngEncoder.php | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Drivers/Gd/Encoders/PngEncoder.php b/src/Drivers/Gd/Encoders/PngEncoder.php index b4600a83..0c3f810b 100644 --- a/src/Drivers/Gd/Encoders/PngEncoder.php +++ b/src/Drivers/Gd/Encoders/PngEncoder.php @@ -5,7 +5,9 @@ namespace Intervention\Image\Drivers\Gd\Encoders; use GdImage; -use Intervention\Image\Colors\Rgb\Color; +use Intervention\Image\Colors\Rgb\Channels\Blue; +use Intervention\Image\Colors\Rgb\Channels\Green; +use Intervention\Image\Colors\Rgb\Channels\Red; use Intervention\Image\Drivers\Gd\Cloner; use Intervention\Image\EncodedImage; use Intervention\Image\Encoders\PngEncoder as GenericPngEncoder; @@ -61,9 +63,9 @@ private function prepareOutput(ImageInterface $image): GdImage // original image with transprency $blendingIndex = imagecolorallocatealpha( $output, - $blendingColor->red()->value(), - $blendingColor->green()->value(), - $blendingColor->blue()->value(), + $blendingColor->channel(Red::class)->value(), + $blendingColor->channel(Green::class)->value(), + $blendingColor->channel(Blue::class)->value(), 1, ); diff --git a/src/Drivers/Imagick/Encoders/PngEncoder.php b/src/Drivers/Imagick/Encoders/PngEncoder.php index c3889b18..8dd89dac 100644 --- a/src/Drivers/Imagick/Encoders/PngEncoder.php +++ b/src/Drivers/Imagick/Encoders/PngEncoder.php @@ -50,7 +50,7 @@ private function prepareOutput(ImageInterface $image): Imagick if ($this->indexed === false) { $output = clone $image->core()->native(); - // ensure to encode PNG image type 6 true color alpha + // ensure to encode PNG image type 6 (true color alpha) $output->setFormat('PNG32'); $output->setImageFormat('PNG32'); From 568444c939dc4f47545d2ebd7e11643aa0054fd4 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sun, 11 Aug 2024 11:19:47 +0200 Subject: [PATCH 08/10] Change logic of encoding indexed PNG format --- src/Drivers/Imagick/Encoders/PngEncoder.php | 41 +++++++++------------ 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/src/Drivers/Imagick/Encoders/PngEncoder.php b/src/Drivers/Imagick/Encoders/PngEncoder.php index 8dd89dac..8b0fe7ab 100644 --- a/src/Drivers/Imagick/Encoders/PngEncoder.php +++ b/src/Drivers/Imagick/Encoders/PngEncoder.php @@ -47,36 +47,31 @@ public function encode(ImageInterface $image): EncodedImage */ private function prepareOutput(ImageInterface $image): Imagick { - if ($this->indexed === false) { - $output = clone $image->core()->native(); + $output = clone $image; - // ensure to encode PNG image type 6 (true color alpha) - $output->setFormat('PNG32'); - $output->setImageFormat('PNG32'); + if ($this->indexed) { + // reduce colors + $output->reduceColors(256); - return $output; - } + $output = $output->core()->native(); - // get blending color - $blendingColor = $this->driver()->colorProcessor($image->colorspace())->colorToNative( - $this->driver()->handleInput($this->driver()->config()->blendingColor) - ); + $output->setFormat('PNG'); + $output->setImageFormat('PNG'); - // create new image with blending color as background - $output = new Imagick(); - $output->newImage($image->width(), $image->height(), $blendingColor, 'PNG'); + // $output->setImageBackgroundColor(new \ImagickPixel('#00ff00')); + // $output->setImageProperty(); - // set transparency of original image - $output->compositeImage($image->core()->native(), Imagick::COMPOSITE_DSTIN, 0, 0); - $output->transparentPaintImage('#000000', 0, 0, false); + // $output->setType(Imagick::IMGTYPE_PALETTEMATTE); + // $output->setOption('png:bit-depth', '8'); + // $output->setOption('png:color-type', '4'); - // copy original and create indexed color palette version - $output->compositeImage($image->core()->native(), Imagick::COMPOSITE_DEFAULT, 0, 0); - $output->quantizeImage(255, $output->getImageColorSpace(), 0, false, false); + return $output; + } - // ensure to encode PNG image type 3 (indexed) - $output->setFormat('PNG8'); - $output->setImageFormat('PNG8'); + // ensure to encode PNG image type 6 (true color alpha) + $output = clone $image->core()->native(); + $output->setFormat('PNG32'); + $output->setImageFormat('PNG32'); return $output; } From 78534a3b0c57efcc0cb09d5203450427377da3bd Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sun, 11 Aug 2024 13:24:31 +0200 Subject: [PATCH 09/10] Let gd's PngEncoder encode binary transparency in indexed mode --- src/Drivers/Gd/Encoders/PngEncoder.php | 41 ++++---------------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/src/Drivers/Gd/Encoders/PngEncoder.php b/src/Drivers/Gd/Encoders/PngEncoder.php index 0c3f810b..ebd2d50d 100644 --- a/src/Drivers/Gd/Encoders/PngEncoder.php +++ b/src/Drivers/Gd/Encoders/PngEncoder.php @@ -5,9 +5,6 @@ namespace Intervention\Image\Drivers\Gd\Encoders; use GdImage; -use Intervention\Image\Colors\Rgb\Channels\Blue; -use Intervention\Image\Colors\Rgb\Channels\Green; -use Intervention\Image\Colors\Rgb\Channels\Red; use Intervention\Image\Drivers\Gd\Cloner; use Intervention\Image\EncodedImage; use Intervention\Image\Encoders\PngEncoder as GenericPngEncoder; @@ -48,39 +45,13 @@ public function encode(ImageInterface $image): EncodedImage */ private function prepareOutput(ImageInterface $image): GdImage { - if ($this->indexed === false) { - return Cloner::clone($image->core()->native()); - } - - // clone output instance - $output = Cloner::cloneEmpty($image->core()->native()); - - // Decode configured blending color - $blendingColor = $image->blendingColor(); - - // allocate blending color with slighty different alpha value - // to avoid "overwriting" pixels with the same color in the - // original image with transprency - $blendingIndex = imagecolorallocatealpha( - $output, - $blendingColor->channel(Red::class)->value(), - $blendingColor->channel(Green::class)->value(), - $blendingColor->channel(Blue::class)->value(), - 1, - ); + if ($this->indexed) { + $output = clone $image; + $output->reduceColors(255); - // fill with blending color - imagefill($output, 0, 0, $blendingIndex); - - // define blending index as transparent - imagecolortransparent($output, $blendingIndex); - - // copy original into output - imagecopy($output, $image->core()->native(), 0, 0, 0, 0, imagesx($output), imagesy($output)); - - // reduce to indexed color palette - imagetruecolortopalette($output, true, 255); + return $output->core()->native(); + } - return $output; + return Cloner::clone($image->core()->native()); } } From e7554ec04828a565a0c6b9dec99fe6fad6742293 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sun, 11 Aug 2024 13:27:46 +0200 Subject: [PATCH 10/10] Allow png results to be 'grayscale' with 'indexed' option --- tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php b/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php index b905bd28..d4908fac 100644 --- a/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php +++ b/tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php @@ -59,6 +59,12 @@ public static function indexedDataProvider(): array new PngEncoder(indexed: true), 'indexed', ], + + [ + static::createTestImage(3, 2)->fill('ccc'), // new grayscale + new PngEncoder(indexed: true), + 'grayscale', // result should be 'indexed' but there seems to be no way to force this with imagick + ], [ static::readTestImage('circle.png'), // truecolor-alpha new PngEncoder(indexed: false), @@ -67,7 +73,7 @@ public static function indexedDataProvider(): array [ static::readTestImage('circle.png'), // indexedcolor-alpha new PngEncoder(indexed: true), - 'indexed', + 'grayscale-alpha', // result should be 'indexed' but there seems to be no way to force this with imagick ], [ static::readTestImage('tile.png'), // indexed