From 6ebdf8a96fc993eceda2a8bcd859cc47c374d4eb Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sun, 5 Nov 2023 14:02:19 +0100 Subject: [PATCH] Fix transparency issues with gd driver resizing --- .../Rgb/Decoders/TransparentColorDecoder.php | 2 +- src/Drivers/Gd/Modifiers/CropModifier.php | 33 +++++---------- src/Drivers/Gd/Modifiers/FitModifier.php | 33 +++++---------- src/Drivers/Gd/Modifiers/PadModifier.php | 40 ++++++++++++------- src/Drivers/Gd/Modifiers/ResizeModifier.php | 19 +-------- tests/Drivers/Gd/InputHandlerTest.php | 2 +- tests/Drivers/Imagick/InputHandlerTest.php | 2 +- 7 files changed, 48 insertions(+), 83 deletions(-) diff --git a/src/Colors/Rgb/Decoders/TransparentColorDecoder.php b/src/Colors/Rgb/Decoders/TransparentColorDecoder.php index 1474987c..e5248879 100644 --- a/src/Colors/Rgb/Decoders/TransparentColorDecoder.php +++ b/src/Colors/Rgb/Decoders/TransparentColorDecoder.php @@ -18,6 +18,6 @@ class TransparentColorDecoder extends HexColorDecoder throw new DecoderException('Unable to decode input'); } - return parent::decode('#00000000'); + return parent::decode('#ff00ff00'); } } diff --git a/src/Drivers/Gd/Modifiers/CropModifier.php b/src/Drivers/Gd/Modifiers/CropModifier.php index b81c570a..f992ca65 100644 --- a/src/Drivers/Gd/Modifiers/CropModifier.php +++ b/src/Drivers/Gd/Modifiers/CropModifier.php @@ -7,9 +7,12 @@ use Intervention\Image\Interfaces\FrameInterface; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ModifierInterface; use Intervention\Image\Interfaces\SizeInterface; +use Intervention\Image\Traits\CanBuildNewImage; class CropModifier implements ModifierInterface { + use CanBuildNewImage; + public function __construct( protected int $width, protected int $height, @@ -36,30 +39,28 @@ class CropModifier implements ModifierInterface protected function cropFrame(FrameInterface $frame, SizeInterface $resizeTo): void { // create new image - $modified = imagecreatetruecolor( + $modified = $this->imageFactory()->newCore( $resizeTo->width(), $resizeTo->height() ); - // get current image - $current = $frame->core(); + // get original image + $original = $frame->core(); // preserve transparency - imagealphablending($modified, false); - $transIndex = imagecolortransparent($current); + $transIndex = imagecolortransparent($original); if ($transIndex != -1) { $rgba = imagecolorsforindex($modified, $transIndex); $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); imagefill($modified, 0, 0, $transColor); - } else { - imagesavealpha($modified, true); + imagecolortransparent($modified, $transColor); } // copy content from resource imagecopyresampled( $modified, - $current, + $original, 0, 0, $resizeTo->pivot()->x() + $this->offset_x, @@ -70,22 +71,6 @@ class CropModifier implements ModifierInterface $resizeTo->height(), ); - if ($transIndex != -1) { // @todo refactor because of duplication - imagecolortransparent($modified, $transIndex); - for ($y = 0; $y < $resizeTo->height(); ++$y) { - for ($x = 0; $x < $resizeTo->width(); ++$x) { - if (((imagecolorat($modified, $x, $y) >> 24) & 0x7F) >= 100) { - imagesetpixel( - $modified, - $x, - $y, - $transIndex - ); - } - } - } - } - // set new content as recource $frame->setCore($modified); } diff --git a/src/Drivers/Gd/Modifiers/FitModifier.php b/src/Drivers/Gd/Modifiers/FitModifier.php index 90a6a19d..7dd567c7 100644 --- a/src/Drivers/Gd/Modifiers/FitModifier.php +++ b/src/Drivers/Gd/Modifiers/FitModifier.php @@ -7,9 +7,12 @@ use Intervention\Image\Interfaces\FrameInterface; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ModifierInterface; use Intervention\Image\Interfaces\SizeInterface; +use Intervention\Image\Traits\CanBuildNewImage; class FitModifier extends AbstractFitModifier implements ModifierInterface { + use CanBuildNewImage; + public function apply(ImageInterface $image): ImageInterface { $crop = $this->getCropSize($image); @@ -25,30 +28,28 @@ class FitModifier extends AbstractFitModifier implements ModifierInterface protected function modifyFrame(FrameInterface $frame, SizeInterface $crop, SizeInterface $resize): void { // create new image - $modified = imagecreatetruecolor( + $modified = $this->imageFactory()->newCore( $resize->width(), $resize->height() ); - // get current image - $current = $frame->core(); + // get original image + $original = $frame->core(); // preserve transparency - imagealphablending($modified, false); - $transIndex = imagecolortransparent($current); + $transIndex = imagecolortransparent($original); if ($transIndex != -1) { $rgba = imagecolorsforindex($modified, $transIndex); $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); imagefill($modified, 0, 0, $transColor); - } else { - imagesavealpha($modified, true); + imagecolortransparent($modified, $transColor); } // copy content from resource imagecopyresampled( $modified, - $current, + $original, 0, 0, $crop->pivot()->x(), @@ -59,22 +60,6 @@ class FitModifier extends AbstractFitModifier implements ModifierInterface $crop->height() ); - if ($transIndex != -1) { // @todo refactor because of duplication - imagecolortransparent($modified, $transIndex); - for ($y = 0; $y < $resize->height(); ++$y) { - for ($x = 0; $x < $resize->width(); ++$x) { - if (((imagecolorat($modified, $x, $y) >> 24) & 0x7F) >= 100) { - imagesetpixel( - $modified, - $x, - $y, - $transIndex - ); - } - } - } - } - // set new content as resource $frame->setCore($modified); } diff --git a/src/Drivers/Gd/Modifiers/PadModifier.php b/src/Drivers/Gd/Modifiers/PadModifier.php index a15083ca..f892c889 100644 --- a/src/Drivers/Gd/Modifiers/PadModifier.php +++ b/src/Drivers/Gd/Modifiers/PadModifier.php @@ -4,22 +4,25 @@ namespace Intervention\Image\Drivers\Gd\Modifiers; use Intervention\Image\Drivers\Abstract\Modifiers\AbstractPadModifier; use Intervention\Image\Drivers\Gd\Traits\CanHandleColors; +use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\FrameInterface; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ModifierInterface; use Intervention\Image\Interfaces\SizeInterface; +use Intervention\Image\Traits\CanBuildNewImage; use Intervention\Image\Traits\CanHandleInput; class PadModifier extends AbstractPadModifier implements ModifierInterface { use CanHandleInput; use CanHandleColors; + use CanBuildNewImage; public function apply(ImageInterface $image): ImageInterface { $crop = $this->getCropSize($image); $resize = $this->getResizeSize($image); - $background = $this->colorToInteger($this->handleInput($this->background)); + $background = $this->handleInput($this->background); foreach ($image as $frame) { $this->modify($frame, $crop, $resize, $background); @@ -32,27 +35,34 @@ class PadModifier extends AbstractPadModifier implements ModifierInterface FrameInterface $frame, SizeInterface $crop, SizeInterface $resize, - int $background + ColorInterface $background ): void { - // create new image - $modified = imagecreatetruecolor( + // create new gd image + $modified = $this->imageFactory()->newCore( $resize->width(), - $resize->height() + $resize->height(), + $background ); - imagefill($modified, 0, 0, $background); + // make image area transparent to keep transparency + // even if background-color is set + $transparent = imagecolorallocatealpha($modified, 255, 0, 255, 127); + imagealphablending($modified, false); // do not blend / just overwrite + imagecolortransparent($modified, $transparent); + imagefilledrectangle( + $modified, + $crop->pivot()->x(), + $crop->pivot()->y(), + $crop->pivot()->x() + $crop->width() - 1, + $crop->pivot()->y() + $crop->height() - 1, + $transparent + ); - // get current image - $current = $frame->core(); - - // preserve transparency - imagealphablending($modified, false); - imagesavealpha($modified, true); - - // copy content from resource + // copy image from original with blending alpha + imagealphablending($modified, true); imagecopyresampled( $modified, - $current, + $frame->core(), $crop->pivot()->x(), $crop->pivot()->y(), 0, diff --git a/src/Drivers/Gd/Modifiers/ResizeModifier.php b/src/Drivers/Gd/Modifiers/ResizeModifier.php index 6f3f3905..c0f0ab36 100644 --- a/src/Drivers/Gd/Modifiers/ResizeModifier.php +++ b/src/Drivers/Gd/Modifiers/ResizeModifier.php @@ -37,14 +37,15 @@ class ResizeModifier implements ModifierInterface $current = $frame->core(); // preserve transparency - imagealphablending($modified, false); $transIndex = imagecolortransparent($current); if ($transIndex != -1) { $rgba = imagecolorsforindex($modified, $transIndex); $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); imagefill($modified, 0, 0, $transColor); + imagecolortransparent($modified, $transColor); } else { + imagealphablending($modified, false); imagesavealpha($modified, true); } @@ -62,22 +63,6 @@ class ResizeModifier implements ModifierInterface $frame->size()->height() ); - if ($transIndex != -1) { // @todo refactor because of duplication - imagecolortransparent($modified, $transIndex); - for ($y = 0; $y < $resizeTo->height(); ++$y) { - for ($x = 0; $x < $resizeTo->width(); ++$x) { - if (((imagecolorat($modified, $x, $y) >> 24) & 0x7F) >= 100) { - imagesetpixel( - $modified, - $x, - $y, - $transIndex - ); - } - } - } - } - // set new content as recource $frame->setCore($modified); } diff --git a/tests/Drivers/Gd/InputHandlerTest.php b/tests/Drivers/Gd/InputHandlerTest.php index bdcc58fc..3f4780fd 100644 --- a/tests/Drivers/Gd/InputHandlerTest.php +++ b/tests/Drivers/Gd/InputHandlerTest.php @@ -120,6 +120,6 @@ class GdInputHandlerTest extends TestCase $input = 'transparent'; $result = $handler->handle($input); $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([0, 0, 0, 0], $result->toArray()); + $this->assertEquals([255, 0, 255, 0], $result->toArray()); } } diff --git a/tests/Drivers/Imagick/InputHandlerTest.php b/tests/Drivers/Imagick/InputHandlerTest.php index d75c48c4..0fe5628c 100644 --- a/tests/Drivers/Imagick/InputHandlerTest.php +++ b/tests/Drivers/Imagick/InputHandlerTest.php @@ -120,6 +120,6 @@ class InputHandlerTest extends TestCase $input = 'transparent'; $result = $handler->handle($input); $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([0, 0, 0, 0], $result->toArray()); + $this->assertEquals([255, 0, 255, 0], $result->toArray()); } }