From 66040006f320159168dff5eb70dba02ef218aa8a Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 3 Aug 2024 10:56:34 +0200 Subject: [PATCH] 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 @@ declare(strict_types=1); 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 @@ declare(strict_types=1); 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 @@ use Intervention\Image\Drivers\SpecializableEncoder; class PngEncoder extends SpecializableEncoder { - public function __construct(public bool $interlaced = false) + public function __construct(public bool $interlaced = false, public bool $indexed = false) { } }