Skip to content

Commit a623e1d

Browse files
author
Oleksii Korshenko
committed
MAGETWO-84647: Handle transparncy correctly for watermark #11060
- Merge Pull Request #11060 from elzekool/magento2:bugfix/handle-alpha-transparency-correctly-for-watermark - Merged commits: 1. 92f24ec 2. 6581e80 3. f2446b6 4. 519f3e1 5. 00a5c3a 6. 503204c
2 parents 5956610 + 503204c commit a623e1d

File tree

5 files changed

+230
-20
lines changed

5 files changed

+230
-20
lines changed

dev/tests/integration/testsuite/Magento/Framework/Image/Adapter/InterfaceTest.php

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,97 @@ public function rotateDataProvider()
337337
);
338338
}
339339

340+
/**
341+
* Test if alpha transparency is correctly handled
342+
*
343+
* @param string $image
344+
* @param string $watermark
345+
* @param int $alphaPercentage
346+
* @param array $comparePoint1
347+
* @param array $comparePoint2
348+
* @param string $adapterType
349+
*
350+
* @dataProvider imageWatermarkWithAlphaTransparencyDataProvider
351+
* @depends testOpen
352+
* @depends testImageSize
353+
*/
354+
public function testWatermarkWithAlphaTransparency(
355+
$image,
356+
$watermark,
357+
$alphaPercentage,
358+
$comparePoint1,
359+
$comparePoint2,
360+
$adapterType
361+
) {
362+
$imageAdapter = $this->_getAdapter($adapterType);
363+
$imageAdapter->open($image);
364+
365+
$watermarkAdapter = $this->_getAdapter($adapterType);
366+
$watermarkAdapter->open($watermark);
367+
368+
list($comparePoint1X, $comparePoint1Y) = $comparePoint1;
369+
list($comparePoint2X, $comparePoint2Y) = $comparePoint2;
370+
371+
$imageAdapter
372+
->setWatermarkImageOpacity($alphaPercentage)
373+
->setWatermarkPosition(\Magento\Framework\Image\Adapter\AbstractAdapter::POSITION_TOP_LEFT)
374+
->watermark($watermark);
375+
376+
$comparePoint1Color = $imageAdapter->getColorAt($comparePoint1X, $comparePoint1Y);
377+
unset($comparePoint1Color['alpha']);
378+
379+
$comparePoint2Color = $imageAdapter->getColorAt($comparePoint2X, $comparePoint2Y);
380+
unset($comparePoint2Color['alpha']);
381+
382+
$result = $this->_compareColors($comparePoint1Color, $comparePoint2Color);
383+
$message = sprintf(
384+
'%s should be different to %s due to alpha transparency',
385+
join(',', $comparePoint1Color),
386+
join(',', $comparePoint2Color)
387+
);
388+
$this->assertFalse($result, $message);
389+
}
390+
391+
public function imageWatermarkWithAlphaTransparencyDataProvider()
392+
{
393+
return $this->_prepareData(
394+
[
395+
// Watermark with alpha channel, 25%
396+
[
397+
$this->_getFixture('watermark_alpha_base_image.jpg'),
398+
$this->_getFixture('watermark_alpha.png'),
399+
25,
400+
[ 23, 3 ],
401+
[ 23, 30 ]
402+
],
403+
// Watermark with alpha channel, 50%
404+
[
405+
$this->_getFixture('watermark_alpha_base_image.jpg'),
406+
$this->_getFixture('watermark_alpha.png'),
407+
50,
408+
[ 23, 3 ],
409+
[ 23, 30 ]
410+
],
411+
// Watermark with no alpha channel, 50%
412+
[
413+
$this->_getFixture('watermark_alpha_base_image.jpg'),
414+
$this->_getFixture('watermark.png'),
415+
50,
416+
[ 3, 3 ],
417+
[ 23,3 ]
418+
],
419+
// Watermark with no alpha channel, 100%
420+
[
421+
$this->_getFixture('watermark_alpha_base_image.jpg'),
422+
$this->_getFixture('watermark.png'),
423+
100,
424+
[ 3, 3 ],
425+
[ 3, 60 ]
426+
],
427+
]
428+
);
429+
}
430+
340431
/**
341432
* Checks if watermark exists on the right position
342433
*
@@ -350,10 +441,10 @@ public function rotateDataProvider()
350441
* @param int $colorY
351442
* @param string $adapterType
352443
*
353-
* @dataProvider imageWatermarkDataProvider
444+
* @dataProvider imageWatermarkPositionDataProvider
354445
* @depends testOpen
355446
*/
356-
public function testWatermark(
447+
public function testWatermarkPosition(
357448
$image,
358449
$watermark,
359450
$width,
@@ -387,7 +478,7 @@ public function testWatermark(
387478
$this->assertFalse($result, $message);
388479
}
389480

390-
public function imageWatermarkDataProvider()
481+
public function imageWatermarkPositionDataProvider()
391482
{
392483
return $this->_prepareData(
393484
[
Loading
Loading

lib/internal/Magento/Framework/Image/Adapter/Gd2.php

Lines changed: 113 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
*/
66
namespace Magento\Framework\Image\Adapter;
77

8+
/**
9+
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
10+
*/
811
class Gd2 extends \Magento\Framework\Image\Adapter\AbstractAdapter
912
{
1013
/**
@@ -404,7 +407,7 @@ public function rotate($angle)
404407
*/
405408
public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = 30, $tile = false)
406409
{
407-
list($watermarkSrcWidth, $watermarkSrcHeight, $watermarkFileType, ) = $this->_getImageOptions($imagePath);
410+
list($watermarkSrcWidth, $watermarkSrcHeight, $watermarkFileType,) = $this->_getImageOptions($imagePath);
408411
$this->_getFileAttributes();
409412
$watermark = call_user_func(
410413
$this->_getCallback('create', $watermarkFileType, 'Unsupported watermark image format.'),
@@ -465,7 +468,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity =
465468
} elseif ($this->getWatermarkPosition() == self::POSITION_CENTER) {
466469
$positionX = $this->_imageSrcWidth / 2 - imagesx($watermark) / 2;
467470
$positionY = $this->_imageSrcHeight / 2 - imagesy($watermark) / 2;
468-
imagecopymerge(
471+
$this->copyImageWithAlphaPercentage(
469472
$this->_imageHandler,
470473
$watermark,
471474
$positionX,
@@ -478,7 +481,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity =
478481
);
479482
} elseif ($this->getWatermarkPosition() == self::POSITION_TOP_RIGHT) {
480483
$positionX = $this->_imageSrcWidth - imagesx($watermark);
481-
imagecopymerge(
484+
$this->copyImageWithAlphaPercentage(
482485
$this->_imageHandler,
483486
$watermark,
484487
$positionX,
@@ -490,7 +493,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity =
490493
$this->getWatermarkImageOpacity()
491494
);
492495
} elseif ($this->getWatermarkPosition() == self::POSITION_TOP_LEFT) {
493-
imagecopymerge(
496+
$this->copyImageWithAlphaPercentage(
494497
$this->_imageHandler,
495498
$watermark,
496499
$positionX,
@@ -504,7 +507,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity =
504507
} elseif ($this->getWatermarkPosition() == self::POSITION_BOTTOM_RIGHT) {
505508
$positionX = $this->_imageSrcWidth - imagesx($watermark);
506509
$positionY = $this->_imageSrcHeight - imagesy($watermark);
507-
imagecopymerge(
510+
$this->copyImageWithAlphaPercentage(
508511
$this->_imageHandler,
509512
$watermark,
510513
$positionX,
@@ -517,7 +520,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity =
517520
);
518521
} elseif ($this->getWatermarkPosition() == self::POSITION_BOTTOM_LEFT) {
519522
$positionY = $this->_imageSrcHeight - imagesy($watermark);
520-
imagecopymerge(
523+
$this->copyImageWithAlphaPercentage(
521524
$this->_imageHandler,
522525
$watermark,
523526
$positionX,
@@ -531,7 +534,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity =
531534
}
532535

533536
if ($tile === false && $merged === false) {
534-
imagecopymerge(
537+
$this->copyImageWithAlphaPercentage(
535538
$this->_imageHandler,
536539
$watermark,
537540
$positionX,
@@ -547,7 +550,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity =
547550
$offsetY = $positionY;
548551
while ($offsetY <= $this->_imageSrcHeight + imagesy($watermark)) {
549552
while ($offsetX <= $this->_imageSrcWidth + imagesx($watermark)) {
550-
imagecopymerge(
553+
$this->copyImageWithAlphaPercentage(
551554
$this->_imageHandler,
552555
$watermark,
553556
$offsetX,
@@ -778,4 +781,106 @@ protected function _createEmptyImage($width, $height)
778781
$this->imageDestroy();
779782
$this->_imageHandler = $image;
780783
}
784+
785+
/**
786+
* Copy source image onto destination image with given alpha percentage
787+
*
788+
* @internal The arguments and functionality is the same as imagecopymerge
789+
* but with proper handling of alpha transparency
790+
*
791+
* @param resource $destinationImage
792+
* @param resource $sourceImage
793+
* @param int $destinationX
794+
* @param int $destinationY
795+
* @param int $sourceX
796+
* @param int $sourceY
797+
* @param int $sourceWidth
798+
* @param int $sourceHeight
799+
* @param int $alphaPercentage
800+
*
801+
* @return bool
802+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
803+
* @SuppressWarnings(PHPMD.NPathComplexity)
804+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
805+
*/
806+
private function copyImageWithAlphaPercentage(
807+
$destinationImage,
808+
$sourceImage,
809+
$destinationX,
810+
$destinationY,
811+
$sourceX,
812+
$sourceY,
813+
$sourceWidth,
814+
$sourceHeight,
815+
$alphaPercentage
816+
) {
817+
if (imageistruecolor($destinationImage) === false || imageistruecolor($sourceImage) === false) {
818+
return imagecopymerge(
819+
$destinationImage,
820+
$sourceImage,
821+
$destinationX,
822+
$destinationY,
823+
$sourceX,
824+
$sourceY,
825+
$sourceWidth,
826+
$sourceHeight,
827+
$alphaPercentage
828+
);
829+
}
830+
831+
if ($alphaPercentage >= 100) {
832+
return imagecopy(
833+
$destinationImage,
834+
$sourceImage,
835+
$destinationX,
836+
$destinationY,
837+
$sourceX,
838+
$sourceY,
839+
$sourceWidth,
840+
$sourceHeight
841+
);
842+
}
843+
844+
if ($alphaPercentage < 0) {
845+
return false;
846+
}
847+
848+
$sizeX = imagesx($sourceImage);
849+
$sizeY = imagesy($sourceImage);
850+
if ($sizeX === false || $sizeY === false || $sizeX === 0 || $sizeY === 0) {
851+
return false;
852+
}
853+
854+
$tmpImg = imagecreatetruecolor($sourceWidth, $sourceHeight);
855+
if ($tmpImg === false) {
856+
return false;
857+
}
858+
859+
if (imagealphablending($tmpImg, false) === false) {
860+
return false;
861+
}
862+
863+
if (imagecopy($tmpImg, $sourceImage, 0, 0, 0, 0, $sizeX, $sizeY) === false) {
864+
return false;
865+
}
866+
867+
$transparency = 127 - (($alphaPercentage*127)/100);
868+
if (imagefilter($tmpImg, IMG_FILTER_COLORIZE, 0, 0, 0, $transparency) === false) {
869+
return false;
870+
}
871+
872+
$result = imagecopy(
873+
$destinationImage,
874+
$tmpImg,
875+
$destinationX,
876+
$destinationY,
877+
$sourceX,
878+
$sourceY,
879+
$sourceWidth,
880+
$sourceHeight
881+
);
882+
imagedestroy($tmpImg);
883+
884+
return $result;
885+
}
781886
}

lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -269,15 +269,17 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity =
269269
);
270270
}
271271

272-
if (method_exists($watermark, 'setImageOpacity')) {
273-
// available from imagick 6.3.1
274-
$watermark->setImageOpacity($opacity);
275-
} else {
276-
// go to each pixel and make it transparent
277-
$watermark->paintTransparentImage($watermark->getImagePixelColor(0, 0), 1, 65530);
278-
$watermark->evaluateImage(\Imagick::EVALUATE_SUBTRACT, 1 - $opacity, \Imagick::CHANNEL_ALPHA);
272+
if (method_exists($watermark, 'getImageAlphaChannel')) {
273+
// available from imagick 6.4.0
274+
if ($watermark->getImageAlphaChannel() == 0) {
275+
$watermark->setImageAlphaChannel(\Imagick::ALPHACHANNEL_OPAQUE);
276+
}
279277
}
280278

279+
$compositeChannels = \Imagick::CHANNEL_ALL;
280+
$watermark->evaluateImage(\Imagick::EVALUATE_MULTIPLY, $opacity, \Imagick::CHANNEL_OPACITY);
281+
$compositeChannels &= ~(\Imagick::CHANNEL_OPACITY);
282+
281283
switch ($this->getWatermarkPosition()) {
282284
case self::POSITION_STRETCH:
283285
$watermark->sampleImage($this->_imageSrcWidth, $this->_imageSrcHeight);
@@ -309,14 +311,26 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity =
309311
$offsetY = $positionY;
310312
while ($offsetY <= $this->_imageSrcHeight + $watermark->getImageHeight()) {
311313
while ($offsetX <= $this->_imageSrcWidth + $watermark->getImageWidth()) {
312-
$this->_imageHandler->compositeImage($watermark, \Imagick::COMPOSITE_OVER, $offsetX, $offsetY);
314+
$this->_imageHandler->compositeImage(
315+
$watermark,
316+
\Imagick::COMPOSITE_OVER,
317+
$offsetX,
318+
$offsetY,
319+
$compositeChannels
320+
);
313321
$offsetX += $watermark->getImageWidth();
314322
}
315323
$offsetX = $positionX;
316324
$offsetY += $watermark->getImageHeight();
317325
}
318326
} else {
319-
$this->_imageHandler->compositeImage($watermark, \Imagick::COMPOSITE_OVER, $positionX, $positionY);
327+
$this->_imageHandler->compositeImage(
328+
$watermark,
329+
\Imagick::COMPOSITE_OVER,
330+
$positionX,
331+
$positionY,
332+
$compositeChannels
333+
);
320334
}
321335
} catch (\ImagickException $e) {
322336
throw new \Exception('Unable to create watermark.', $e->getCode(), $e);

0 commit comments

Comments
 (0)