diff --git a/dev/tests/integration/testsuite/Magento/Framework/Image/Adapter/InterfaceTest.php b/dev/tests/integration/testsuite/Magento/Framework/Image/Adapter/InterfaceTest.php index 420803d9530bd..bc8281d55ac4f 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Image/Adapter/InterfaceTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Image/Adapter/InterfaceTest.php @@ -337,6 +337,97 @@ public function rotateDataProvider() ); } + /** + * Test if alpha transparency is correctly handled + * + * @param string $image + * @param string $watermark + * @param int $alphaPercentage + * @param array $comparePoint1 + * @param array $comparePoint2 + * @param string $adapterType + * + * @dataProvider imageWatermarkWithAlphaTransparencyDataProvider + * @depends testOpen + * @depends testImageSize + */ + public function testWatermarkWithAlphaTransparency( + $image, + $watermark, + $alphaPercentage, + $comparePoint1, + $comparePoint2, + $adapterType + ) { + $imageAdapter = $this->_getAdapter($adapterType); + $imageAdapter->open($image); + + $watermarkAdapter = $this->_getAdapter($adapterType); + $watermarkAdapter->open($watermark); + + list($comparePoint1X, $comparePoint1Y) = $comparePoint1; + list($comparePoint2X, $comparePoint2Y) = $comparePoint2; + + $imageAdapter + ->setWatermarkImageOpacity($alphaPercentage) + ->setWatermarkPosition(\Magento\Framework\Image\Adapter\AbstractAdapter::POSITION_TOP_LEFT) + ->watermark($watermark); + + $comparePoint1Color = $imageAdapter->getColorAt($comparePoint1X, $comparePoint1Y); + unset($comparePoint1Color['alpha']); + + $comparePoint2Color = $imageAdapter->getColorAt($comparePoint2X, $comparePoint2Y); + unset($comparePoint2Color['alpha']); + + $result = $this->_compareColors($comparePoint1Color, $comparePoint2Color); + $message = sprintf( + '%s should be different to %s due to alpha transparency', + join(',', $comparePoint1Color), + join(',', $comparePoint2Color) + ); + $this->assertFalse($result, $message); + } + + public function imageWatermarkWithAlphaTransparencyDataProvider() + { + return $this->_prepareData( + [ + // Watermark with alpha channel, 25% + [ + $this->_getFixture('watermark_alpha_base_image.jpg'), + $this->_getFixture('watermark_alpha.png'), + 25, + [ 23, 3 ], + [ 23, 30 ] + ], + // Watermark with alpha channel, 50% + [ + $this->_getFixture('watermark_alpha_base_image.jpg'), + $this->_getFixture('watermark_alpha.png'), + 50, + [ 23, 3 ], + [ 23, 30 ] + ], + // Watermark with no alpha channel, 50% + [ + $this->_getFixture('watermark_alpha_base_image.jpg'), + $this->_getFixture('watermark.png'), + 50, + [ 3, 3 ], + [ 23,3 ] + ], + // Watermark with no alpha channel, 100% + [ + $this->_getFixture('watermark_alpha_base_image.jpg'), + $this->_getFixture('watermark.png'), + 100, + [ 3, 3 ], + [ 3, 60 ] + ], + ] + ); + } + /** * Checks if watermark exists on the right position * @@ -350,10 +441,10 @@ public function rotateDataProvider() * @param int $colorY * @param string $adapterType * - * @dataProvider imageWatermarkDataProvider + * @dataProvider imageWatermarkPositionDataProvider * @depends testOpen */ - public function testWatermark( + public function testWatermarkPosition( $image, $watermark, $width, @@ -387,7 +478,7 @@ public function testWatermark( $this->assertFalse($result, $message); } - public function imageWatermarkDataProvider() + public function imageWatermarkPositionDataProvider() { return $this->_prepareData( [ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Image/_files/watermark_alpha.png b/dev/tests/integration/testsuite/Magento/Framework/Image/_files/watermark_alpha.png new file mode 100644 index 0000000000000..af05a14a99fdd Binary files /dev/null and b/dev/tests/integration/testsuite/Magento/Framework/Image/_files/watermark_alpha.png differ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Image/_files/watermark_alpha_base_image.jpg b/dev/tests/integration/testsuite/Magento/Framework/Image/_files/watermark_alpha_base_image.jpg new file mode 100644 index 0000000000000..361ff523d2130 Binary files /dev/null and b/dev/tests/integration/testsuite/Magento/Framework/Image/_files/watermark_alpha_base_image.jpg differ diff --git a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php index 444ab7113d429..a36e41a526466 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php +++ b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Image\Adapter; +/** + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + */ class Gd2 extends \Magento\Framework\Image\Adapter\AbstractAdapter { /** @@ -404,7 +407,7 @@ public function rotate($angle) */ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = 30, $tile = false) { - list($watermarkSrcWidth, $watermarkSrcHeight, $watermarkFileType, ) = $this->_getImageOptions($imagePath); + list($watermarkSrcWidth, $watermarkSrcHeight, $watermarkFileType,) = $this->_getImageOptions($imagePath); $this->_getFileAttributes(); $watermark = call_user_func( $this->_getCallback('create', $watermarkFileType, 'Unsupported watermark image format.'), @@ -465,7 +468,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = } elseif ($this->getWatermarkPosition() == self::POSITION_CENTER) { $positionX = $this->_imageSrcWidth / 2 - imagesx($watermark) / 2; $positionY = $this->_imageSrcHeight / 2 - imagesy($watermark) / 2; - imagecopymerge( + $this->copyImageWithAlphaPercentage( $this->_imageHandler, $watermark, $positionX, @@ -478,7 +481,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = ); } elseif ($this->getWatermarkPosition() == self::POSITION_TOP_RIGHT) { $positionX = $this->_imageSrcWidth - imagesx($watermark); - imagecopymerge( + $this->copyImageWithAlphaPercentage( $this->_imageHandler, $watermark, $positionX, @@ -490,7 +493,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = $this->getWatermarkImageOpacity() ); } elseif ($this->getWatermarkPosition() == self::POSITION_TOP_LEFT) { - imagecopymerge( + $this->copyImageWithAlphaPercentage( $this->_imageHandler, $watermark, $positionX, @@ -504,7 +507,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = } elseif ($this->getWatermarkPosition() == self::POSITION_BOTTOM_RIGHT) { $positionX = $this->_imageSrcWidth - imagesx($watermark); $positionY = $this->_imageSrcHeight - imagesy($watermark); - imagecopymerge( + $this->copyImageWithAlphaPercentage( $this->_imageHandler, $watermark, $positionX, @@ -517,7 +520,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = ); } elseif ($this->getWatermarkPosition() == self::POSITION_BOTTOM_LEFT) { $positionY = $this->_imageSrcHeight - imagesy($watermark); - imagecopymerge( + $this->copyImageWithAlphaPercentage( $this->_imageHandler, $watermark, $positionX, @@ -531,7 +534,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = } if ($tile === false && $merged === false) { - imagecopymerge( + $this->copyImageWithAlphaPercentage( $this->_imageHandler, $watermark, $positionX, @@ -547,7 +550,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = $offsetY = $positionY; while ($offsetY <= $this->_imageSrcHeight + imagesy($watermark)) { while ($offsetX <= $this->_imageSrcWidth + imagesx($watermark)) { - imagecopymerge( + $this->copyImageWithAlphaPercentage( $this->_imageHandler, $watermark, $offsetX, @@ -778,4 +781,106 @@ protected function _createEmptyImage($width, $height) $this->imageDestroy(); $this->_imageHandler = $image; } + + /** + * Copy source image onto destination image with given alpha percentage + * + * @internal The arguments and functionality is the same as imagecopymerge + * but with proper handling of alpha transparency + * + * @param resource $destinationImage + * @param resource $sourceImage + * @param int $destinationX + * @param int $destinationY + * @param int $sourceX + * @param int $sourceY + * @param int $sourceWidth + * @param int $sourceHeight + * @param int $alphaPercentage + * + * @return bool + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + private function copyImageWithAlphaPercentage( + $destinationImage, + $sourceImage, + $destinationX, + $destinationY, + $sourceX, + $sourceY, + $sourceWidth, + $sourceHeight, + $alphaPercentage + ) { + if (imageistruecolor($destinationImage) === false || imageistruecolor($sourceImage) === false) { + return imagecopymerge( + $destinationImage, + $sourceImage, + $destinationX, + $destinationY, + $sourceX, + $sourceY, + $sourceWidth, + $sourceHeight, + $alphaPercentage + ); + } + + if ($alphaPercentage >= 100) { + return imagecopy( + $destinationImage, + $sourceImage, + $destinationX, + $destinationY, + $sourceX, + $sourceY, + $sourceWidth, + $sourceHeight + ); + } + + if ($alphaPercentage < 0) { + return false; + } + + $sizeX = imagesx($sourceImage); + $sizeY = imagesy($sourceImage); + if ($sizeX === false || $sizeY === false || $sizeX === 0 || $sizeY === 0) { + return false; + } + + $tmpImg = imagecreatetruecolor($sourceWidth, $sourceHeight); + if ($tmpImg === false) { + return false; + } + + if (imagealphablending($tmpImg, false) === false) { + return false; + } + + if (imagecopy($tmpImg, $sourceImage, 0, 0, 0, 0, $sizeX, $sizeY) === false) { + return false; + } + + $transparency = 127 - (($alphaPercentage*127)/100); + if (imagefilter($tmpImg, IMG_FILTER_COLORIZE, 0, 0, 0, $transparency) === false) { + return false; + } + + $result = imagecopy( + $destinationImage, + $tmpImg, + $destinationX, + $destinationY, + $sourceX, + $sourceY, + $sourceWidth, + $sourceHeight + ); + imagedestroy($tmpImg); + + return $result; + } } diff --git a/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php b/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php index 50b9a5a013273..5d9ef49c7d285 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php +++ b/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php @@ -269,15 +269,17 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = ); } - if (method_exists($watermark, 'setImageOpacity')) { - // available from imagick 6.3.1 - $watermark->setImageOpacity($opacity); - } else { - // go to each pixel and make it transparent - $watermark->paintTransparentImage($watermark->getImagePixelColor(0, 0), 1, 65530); - $watermark->evaluateImage(\Imagick::EVALUATE_SUBTRACT, 1 - $opacity, \Imagick::CHANNEL_ALPHA); + if (method_exists($watermark, 'getImageAlphaChannel')) { + // available from imagick 6.4.0 + if ($watermark->getImageAlphaChannel() == 0) { + $watermark->setImageAlphaChannel(\Imagick::ALPHACHANNEL_OPAQUE); + } } + $compositeChannels = \Imagick::CHANNEL_ALL; + $watermark->evaluateImage(\Imagick::EVALUATE_MULTIPLY, $opacity, \Imagick::CHANNEL_OPACITY); + $compositeChannels &= ~(\Imagick::CHANNEL_OPACITY); + switch ($this->getWatermarkPosition()) { case self::POSITION_STRETCH: $watermark->sampleImage($this->_imageSrcWidth, $this->_imageSrcHeight); @@ -309,14 +311,26 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = $offsetY = $positionY; while ($offsetY <= $this->_imageSrcHeight + $watermark->getImageHeight()) { while ($offsetX <= $this->_imageSrcWidth + $watermark->getImageWidth()) { - $this->_imageHandler->compositeImage($watermark, \Imagick::COMPOSITE_OVER, $offsetX, $offsetY); + $this->_imageHandler->compositeImage( + $watermark, + \Imagick::COMPOSITE_OVER, + $offsetX, + $offsetY, + $compositeChannels + ); $offsetX += $watermark->getImageWidth(); } $offsetX = $positionX; $offsetY += $watermark->getImageHeight(); } } else { - $this->_imageHandler->compositeImage($watermark, \Imagick::COMPOSITE_OVER, $positionX, $positionY); + $this->_imageHandler->compositeImage( + $watermark, + \Imagick::COMPOSITE_OVER, + $positionX, + $positionY, + $compositeChannels + ); } } catch (\ImagickException $e) { throw new \Exception('Unable to create watermark.', $e->getCode(), $e);