From a0337c2dc498603676af34865ad814562f843d56 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Mon, 8 Jan 2024 12:13:18 +0100 Subject: [PATCH] Add background color parameter to CropModifier --- src/Drivers/Gd/Modifiers/CropModifier.php | 41 ++++++++++++-- .../Imagick/Modifiers/CropModifier.php | 54 +++++++++++++++++++ src/Image.php | 3 +- src/Interfaces/ImageInterface.php | 2 + src/Modifiers/CropModifier.php | 1 + .../Drivers/Gd/Modifiers/CropModifierTest.php | 14 ++++- .../Imagick/Modifiers/CropModifierTest.php | 14 ++++- 7 files changed, 122 insertions(+), 7 deletions(-) diff --git a/src/Drivers/Gd/Modifiers/CropModifier.php b/src/Drivers/Gd/Modifiers/CropModifier.php index 4132a080..a1d40f0c 100644 --- a/src/Drivers/Gd/Modifiers/CropModifier.php +++ b/src/Drivers/Gd/Modifiers/CropModifier.php @@ -2,8 +2,12 @@ namespace Intervention\Image\Drivers\Gd\Modifiers; +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\Drivers\Gd\SpecializedModifier; +use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\FrameInterface; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\SizeInterface; @@ -12,6 +16,7 @@ use Intervention\Image\Interfaces\SizeInterface; * @method SizeInterface crop(ImageInterface $image) * @property int $offset_x * @property int $offset_y + * @property mixed $background */ class CropModifier extends SpecializedModifier { @@ -19,18 +24,23 @@ class CropModifier extends SpecializedModifier { $originalSize = $image->size(); $crop = $this->crop($image); + $background = $this->driver()->handleInput($this->background); foreach ($image as $frame) { - $this->cropFrame($frame, $originalSize, $crop); + $this->cropFrame($frame, $originalSize, $crop, $background); } return $image; } - protected function cropFrame(FrameInterface $frame, SizeInterface $originalSize, SizeInterface $resizeTo): void - { + protected function cropFrame( + FrameInterface $frame, + SizeInterface $originalSize, + SizeInterface $resizeTo, + ColorInterface $background + ): void { // create new image - $modified = Cloner::cloneEmpty($frame->native(), $resizeTo); + $modified = Cloner::cloneEmpty($frame->native(), $resizeTo, $background); // define offset $offset_x = ($resizeTo->pivot()->x() + $this->offset_x); @@ -42,6 +52,27 @@ class CropModifier extends SpecializedModifier $targetWidth = $targetWidth < $originalSize->width() ? $targetWidth + $offset_x : $targetWidth; $targetHeight = $targetHeight < $originalSize->height() ? $targetHeight + $offset_y : $targetHeight; + // make image area transparent to keep transparency + // even if background-color is set + $transparent = imagecolorallocatealpha( + $modified, + $background->channel(Red::class)->value(), + $background->channel(Green::class)->value(), + $background->channel(Blue::class)->value(), + 127, + ); + + imagealphablending($modified, false); // do not blend / just overwrite + // imagecolortransparent($modified, $transparent); + imagefilledrectangle( + $modified, + $offset_x * -1, + $offset_y * -1, + $targetWidth, + $targetHeight, + $transparent + ); + // copy content from resource imagecopyresampled( $modified, @@ -56,6 +87,8 @@ class CropModifier extends SpecializedModifier $targetHeight ); + imagealphablending($modified, true); + // set new content as recource $frame->setNative($modified); } diff --git a/src/Drivers/Imagick/Modifiers/CropModifier.php b/src/Drivers/Imagick/Modifiers/CropModifier.php index a4becc1b..823367e8 100644 --- a/src/Drivers/Imagick/Modifiers/CropModifier.php +++ b/src/Drivers/Imagick/Modifiers/CropModifier.php @@ -2,6 +2,7 @@ namespace Intervention\Image\Drivers\Imagick\Modifiers; +use ImagickDraw; use Intervention\Image\Drivers\DriverSpecializedModifier; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\SizeInterface; @@ -10,20 +11,73 @@ use Intervention\Image\Interfaces\SizeInterface; * @method SizeInterface crop(ImageInterface $image) * @property int $offset_x * @property int $offset_y + * @property mixed $background */ class CropModifier extends DriverSpecializedModifier { public function apply(ImageInterface $image): ImageInterface { + $originalSize = $image->size(); $crop = $this->crop($image); + $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( + $this->driver()->handleInput($this->background) + ); + + $draw = new ImagickDraw(); + $draw->setFillColor($background); foreach ($image as $frame) { + // crop image $frame->native()->extentImage( $crop->width(), $crop->height(), $crop->pivot()->x() + $this->offset_x, $crop->pivot()->y() + $this->offset_y ); + + // cover the possible newly created areas with background color + if ($crop->width() > $originalSize->width()) { + $draw->rectangle( + $originalSize->width() + ($this->offset_x * -1), + 0, + $crop->width(), + $crop->height() + ); + $frame->native()->drawImage($draw); + } + + // cover the possible newly created areas with background color + if ($crop->height() > $originalSize->height()) { + $draw->rectangle( + 0, + $originalSize->height() + ($this->offset_y * -1), + $crop->width(), + $crop->height() + ); + $frame->native()->drawImage($draw); + } + + // cover the possible newly created areas with background color + if ($this->offset_x < 0) { + $draw->rectangle( + 0, + 0, + ($this->offset_x * -1) - 1, + $originalSize->height() + ($this->offset_y * -1) + ); + $frame->native()->drawImage($draw); + } + + // cover the possible newly created areas with background color + if ($this->offset_y < 0) { + $draw->rectangle( + 0, + 0, + $crop->width(), + ($this->offset_y * -1) - 1, + ); + $frame->native()->drawImage($draw); + } } return $image; diff --git a/src/Image.php b/src/Image.php index cc5c2a69..ecac3fd4 100644 --- a/src/Image.php +++ b/src/Image.php @@ -715,9 +715,10 @@ final class Image implements ImageInterface int $height, int $offset_x = 0, int $offset_y = 0, + mixed $background = 'ffffff', string $position = 'top-left' ): ImageInterface { - return $this->modify(new CropModifier($width, $height, $offset_x, $offset_y, $position)); + return $this->modify(new CropModifier($width, $height, $offset_x, $offset_y, $background, $position)); } /** diff --git a/src/Interfaces/ImageInterface.php b/src/Interfaces/ImageInterface.php index 95c9e3ef..b77b6ea8 100644 --- a/src/Interfaces/ImageInterface.php +++ b/src/Interfaces/ImageInterface.php @@ -491,6 +491,7 @@ interface ImageInterface extends IteratorAggregate, Countable * @param int $height * @param int $offset_x * @param int $offset_y + * @param mixed $background * @param string $position * @return ImageInterface */ @@ -499,6 +500,7 @@ interface ImageInterface extends IteratorAggregate, Countable int $height, int $offset_x = 0, int $offset_y = 0, + mixed $background = 'ffffff', string $position = 'top-left' ): ImageInterface; diff --git a/src/Modifiers/CropModifier.php b/src/Modifiers/CropModifier.php index d733513f..17f5e4df 100644 --- a/src/Modifiers/CropModifier.php +++ b/src/Modifiers/CropModifier.php @@ -13,6 +13,7 @@ class CropModifier extends AbstractModifier public int $height, public int $offset_x = 0, public int $offset_y = 0, + public mixed $background = 'f00', public string $position = 'top-left' ) { } diff --git a/tests/Drivers/Gd/Modifiers/CropModifierTest.php b/tests/Drivers/Gd/Modifiers/CropModifierTest.php index a51e7d6f..6fd5d1b9 100644 --- a/tests/Drivers/Gd/Modifiers/CropModifierTest.php +++ b/tests/Drivers/Gd/Modifiers/CropModifierTest.php @@ -17,11 +17,23 @@ class CropModifierTest extends TestCase public function testModify(): void { $image = $this->readTestImage('blocks.png'); - $image = $image->modify(new CropModifier(200, 200, 0, 0, 'bottom-right')); + $image = $image->modify(new CropModifier(200, 200, 0, 0, 'ffffff', 'bottom-right')); $this->assertEquals(200, $image->width()); $this->assertEquals(200, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(5, 5)); $this->assertColor(255, 0, 0, 255, $image->pickColor(100, 100)); $this->assertColor(255, 0, 0, 255, $image->pickColor(190, 190)); } + + public function testModifyExtend(): void + { + $image = $this->readTestImage('blocks.png'); + $image = $image->modify(new CropModifier(800, 100, -10, -10, 'ff0000', 'top-left')); + $this->assertEquals(800, $image->width()); + $this->assertEquals(100, $image->height()); + $this->assertColor(255, 0, 0, 255, $image->pickColor(9, 9)); + $this->assertColor(0, 0, 255, 255, $image->pickColor(16, 16)); + $this->assertColor(0, 0, 255, 255, $image->pickColor(445, 16)); + $this->assertTransparency($image->pickColor(460, 16)); + } } diff --git a/tests/Drivers/Imagick/Modifiers/CropModifierTest.php b/tests/Drivers/Imagick/Modifiers/CropModifierTest.php index 6889c247..57c735d0 100644 --- a/tests/Drivers/Imagick/Modifiers/CropModifierTest.php +++ b/tests/Drivers/Imagick/Modifiers/CropModifierTest.php @@ -17,11 +17,23 @@ class CropModifierTest extends TestCase public function testModify(): void { $image = $this->readTestImage('blocks.png'); - $image = $image->modify(new CropModifier(200, 200, 0, 0, 'bottom-right')); + $image = $image->modify(new CropModifier(200, 200, 0, 0, 'ffffff', 'bottom-right')); $this->assertEquals(200, $image->width()); $this->assertEquals(200, $image->height()); $this->assertColor(255, 0, 0, 255, $image->pickColor(5, 5)); $this->assertColor(255, 0, 0, 255, $image->pickColor(100, 100)); $this->assertColor(255, 0, 0, 255, $image->pickColor(190, 190)); } + + public function testModifyExtend(): void + { + $image = $this->readTestImage('blocks.png'); + $image = $image->modify(new CropModifier(800, 100, -10, -10, 'ff0000', 'top-left')); + $this->assertEquals(800, $image->width()); + $this->assertEquals(100, $image->height()); + $this->assertColor(255, 0, 0, 255, $image->pickColor(9, 9)); + $this->assertColor(0, 0, 255, 255, $image->pickColor(16, 16)); + $this->assertColor(0, 0, 255, 255, $image->pickColor(445, 16)); + $this->assertTransparency($image->pickColor(460, 16)); + } }