From 884d4a476a3424bea126898b2ab1b6a98cd742eb Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Thu, 18 Jan 2024 20:04:52 +0100 Subject: [PATCH 1/2] Add opacity parameter to PlaceModifier --- src/Drivers/Gd/Modifiers/PlaceModifier.php | 98 +++++++++++++++++-- .../Imagick/Modifiers/PlaceModifier.php | 12 ++- src/Image.php | 5 +- src/Interfaces/ImageInterface.php | 5 +- src/Modifiers/PlaceModifier.php | 3 +- .../Gd/Modifiers/PlaceModifierTest.php | 8 ++ .../Imagick/Modifiers/PlaceModifierTest.php | 8 ++ 7 files changed, 123 insertions(+), 16 deletions(-) diff --git a/src/Drivers/Gd/Modifiers/PlaceModifier.php b/src/Drivers/Gd/Modifiers/PlaceModifier.php index 2b809fea..7d01d532 100644 --- a/src/Drivers/Gd/Modifiers/PlaceModifier.php +++ b/src/Drivers/Gd/Modifiers/PlaceModifier.php @@ -5,8 +5,10 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Gd\Modifiers; use Intervention\Image\Drivers\DriverSpecialized; +use Intervention\Image\Interfaces\FrameInterface; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ModifierInterface; +use Intervention\Image\Interfaces\PointInterface; /** * @method mixed getPosition(ImageInterface $image, ImageInterface $watermark) @@ -14,6 +16,7 @@ use Intervention\Image\Interfaces\ModifierInterface; * @property string $position * @property int $offset_x * @property int $offset_y + * @property int $opacity */ class PlaceModifier extends DriverSpecialized implements ModifierInterface { @@ -24,18 +27,93 @@ class PlaceModifier extends DriverSpecialized implements ModifierInterface foreach ($image as $frame) { imagealphablending($frame->native(), true); - imagecopy( - $frame->native(), - $watermark->core()->native(), - $position->x(), - $position->y(), - 0, - 0, - $watermark->width(), - $watermark->height() - ); + + if ($this->opacity === 100) { + $this->placeOpaque($frame, $watermark, $position); + } else { + $this->placeTransparent($frame, $watermark, $position); + } } return $image; } + + /** + * Insert watermark with 100% opacity + * + * @param FrameInterface $frame + * @param ImageInterface $watermark + * @param PointInterface $position + * @return void + */ + private function placeOpaque(FrameInterface $frame, ImageInterface $watermark, PointInterface $position): void + { + imagecopy( + $frame->native(), + $watermark->core()->native(), + $position->x(), + $position->y(), + 0, + 0, + $watermark->width(), + $watermark->height() + ); + } + + /** + * Insert watermark transparent with current opacity + * + * Unfortunately, the original PHP function imagecopymerge does not work reliably. + * For example, any transparency of the image to be inserted is not applied correctly. + * For this reason, a new GDImage is created into which the original image is inserted + * in the first step and the watermark is inserted with 100% opacity in the second + * step. This combination is then transferred to the original image again with the + * respective opacity. + * + * Please note: Unfortunately, there is still an edge case, when a transparent image + * is placed on a transparent background, the "double" transparent areas appear opaque! + * + * @param FrameInterface $frame + * @param ImageInterface $watermark + * @param PointInterface $position + * @return void + */ + private function placeTransparent(FrameInterface $frame, ImageInterface $watermark, PointInterface $position): void + { + $cut = imagecreatetruecolor($watermark->width(), $watermark->height()); + + imagecopy( + $cut, + $frame->native(), + 0, + 0, + $position->x(), + $position->y(), + imagesx($cut), + imagesy($cut) + ); + + imagecopy( + $cut, + $watermark->core()->native(), + 0, + 0, + 0, + 0, + imagesx($cut), + imagesy($cut) + ); + + imagecopymerge( + $frame->native(), + $cut, + $position->x(), + $position->y(), + 0, + 0, + $watermark->width(), + $watermark->height(), + $this->opacity + ); + } } diff --git a/src/Drivers/Imagick/Modifiers/PlaceModifier.php b/src/Drivers/Imagick/Modifiers/PlaceModifier.php index 6851ef6e..a188aaa6 100644 --- a/src/Drivers/Imagick/Modifiers/PlaceModifier.php +++ b/src/Drivers/Imagick/Modifiers/PlaceModifier.php @@ -15,6 +15,7 @@ use Intervention\Image\Interfaces\ModifierInterface; * @property string $position * @property int $offset_x * @property int $offset_y + * @property int $opacity */ class PlaceModifier extends DriverSpecialized implements ModifierInterface { @@ -23,10 +24,19 @@ class PlaceModifier extends DriverSpecialized implements ModifierInterface $watermark = $this->driver()->handleInput($this->element); $position = $this->getPosition($image, $watermark); + // set opacity of watermark + if ($this->opacity < 100) { + $watermark->core()->native()->evaluateImage( + Imagick::EVALUATE_DIVIDE, + $this->opacity > 0 ? (100 / $this->opacity) : 1000, + Imagick::CHANNEL_ALPHA, + ); + } + foreach ($image as $frame) { $frame->native()->compositeImage( $watermark->core()->native(), - Imagick::COMPOSITE_DEFAULT, + Imagick::COMPOSITE_OVER, $position->x(), $position->y() ); diff --git a/src/Image.php b/src/Image.php index 4de96baa..75103904 100644 --- a/src/Image.php +++ b/src/Image.php @@ -744,9 +744,10 @@ final class Image implements ImageInterface mixed $element, string $position = 'top-left', int $offset_x = 0, - int $offset_y = 0 + int $offset_y = 0, + int $opacity = 100 ): ImageInterface { - return $this->modify(new PlaceModifier($element, $position, $offset_x, $offset_y)); + return $this->modify(new PlaceModifier($element, $position, $offset_x, $offset_y, $opacity)); } /** diff --git a/src/Interfaces/ImageInterface.php b/src/Interfaces/ImageInterface.php index d4b90d1d..4694e524 100644 --- a/src/Interfaces/ImageInterface.php +++ b/src/Interfaces/ImageInterface.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Intervention\Image\Interfaces; use Countable; -use Intervention\Image\EncodedImage; use Intervention\Image\Origin; use IteratorAggregate; @@ -522,13 +521,15 @@ interface ImageInterface extends IteratorAggregate, Countable * @param string $position * @param int $offset_x * @param int $offset_y + * @param int $opacity * @return ImageInterface */ public function place( mixed $element, string $position = 'top-left', int $offset_x = 0, - int $offset_y = 0 + int $offset_y = 0, + int $opacity = 100 ): ImageInterface; /** diff --git a/src/Modifiers/PlaceModifier.php b/src/Modifiers/PlaceModifier.php index 50f32694..3d75a9a0 100644 --- a/src/Modifiers/PlaceModifier.php +++ b/src/Modifiers/PlaceModifier.php @@ -13,7 +13,8 @@ class PlaceModifier extends SpecializableModifier public mixed $element, public string $position, public int $offset_x, - public int $offset_y + public int $offset_y, + public int $opacity = 100 ) { } diff --git a/tests/Drivers/Gd/Modifiers/PlaceModifierTest.php b/tests/Drivers/Gd/Modifiers/PlaceModifierTest.php index 8267c2ba..cfe35d55 100644 --- a/tests/Drivers/Gd/Modifiers/PlaceModifierTest.php +++ b/tests/Drivers/Gd/Modifiers/PlaceModifierTest.php @@ -24,4 +24,12 @@ class PlaceModifierTest extends TestCase $image->modify(new PlaceModifier(__DIR__ . '/../../../images/circle.png', 'top-right', 0, 0)); $this->assertEquals('32250d', $image->pickColor(300, 25)->toHex()); } + + public function testColorChangeOpacity(): void + { + $image = $this->readTestImage('test.jpg'); + $this->assertEquals('febc44', $image->pickColor(300, 25)->toHex()); + $image->modify(new PlaceModifier(__DIR__ . '/../../../images/circle.png', 'top-right', 0, 0, 50)); + $this->assertEquals('987028', $image->pickColor(300, 25)->toHex()); + } } diff --git a/tests/Drivers/Imagick/Modifiers/PlaceModifierTest.php b/tests/Drivers/Imagick/Modifiers/PlaceModifierTest.php index 005698f2..0bd415dd 100644 --- a/tests/Drivers/Imagick/Modifiers/PlaceModifierTest.php +++ b/tests/Drivers/Imagick/Modifiers/PlaceModifierTest.php @@ -24,4 +24,12 @@ class PlaceModifierTest extends TestCase $image->modify(new PlaceModifier(__DIR__ . '/../../../images/circle.png', 'top-right', 0, 0)); $this->assertEquals('33260e', $image->pickColor(300, 25)->toHex()); } + + public function testColorChangeOpacity(): void + { + $image = $this->readTestImage('test.jpg'); + $this->assertEquals('febc44', $image->pickColor(300, 25)->toHex()); + $image->modify(new PlaceModifier(__DIR__ . '/../../../images/circle.png', 'top-right', 0, 0, 50)); + $this->assertEquals('987129', $image->pickColor(300, 25)->toHex()); + } } From 9d1263b31dbb0ecac2e8d391dad15c99569f463d Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Fri, 19 Jan 2024 09:38:07 +0100 Subject: [PATCH 2/2] Change composite mode for Imagick PlaceModifier --- src/Drivers/Imagick/Modifiers/PlaceModifier.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Drivers/Imagick/Modifiers/PlaceModifier.php b/src/Drivers/Imagick/Modifiers/PlaceModifier.php index a188aaa6..de7357b4 100644 --- a/src/Drivers/Imagick/Modifiers/PlaceModifier.php +++ b/src/Drivers/Imagick/Modifiers/PlaceModifier.php @@ -36,7 +36,7 @@ class PlaceModifier extends DriverSpecialized implements ModifierInterface foreach ($image as $frame) { $frame->native()->compositeImage( $watermark->core()->native(), - Imagick::COMPOSITE_OVER, + Imagick::COMPOSITE_DEFAULT, $position->x(), $position->y() );