Skip to content

Commit 0f87254

Browse files
authored
Fix bug in CropModifier of Imagick driver (#1428)
The CropModifier produced strange artifacts if another resize step was performed after the modification. This patch removes the copying of the alpha channel in the CropModifier and implements a different method. See: #1426
1 parent 629142e commit 0f87254

File tree

2 files changed

+46
-20
lines changed

2 files changed

+46
-20
lines changed

src/Drivers/Imagick/Modifiers/CropModifier.php

+27-20
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Intervention\Image\Drivers\Imagick\Modifiers;
66

77
use Imagick;
8-
use Intervention\Image\Drivers\Imagick\Driver;
8+
use ImagickPixel;
99
use Intervention\Image\Interfaces\ImageInterface;
1010
use Intervention\Image\Interfaces\SpecializedInterface;
1111
use Intervention\Image\Modifiers\CropModifier as GenericCropModifier;
@@ -14,20 +14,30 @@ class CropModifier extends GenericCropModifier implements SpecializedInterface
1414
{
1515
public function apply(ImageInterface $image): ImageInterface
1616
{
17-
$crop = $this->crop($image);
17+
// decode background color
1818
$background = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
1919
$this->driver()->handleInput($this->background)
2020
);
2121

2222
// create empty container imagick to rebuild core
2323
$imagick = new Imagick();
24+
25+
// save resolution to add it later
2426
$resolution = $image->resolution()->perInch();
2527

28+
// define position of the image on the new canvas
29+
$crop = $this->crop($image);
30+
$position = [
31+
($crop->pivot()->x() + $this->offset_x) * -1,
32+
($crop->pivot()->y() + $this->offset_y) * -1,
33+
];
34+
2635
foreach ($image as $frame) {
2736
// create new frame canvas with modifiers background
2837
$canvas = new Imagick();
2938
$canvas->newImage($crop->width(), $crop->height(), $background, 'png');
3039
$canvas->setImageResolution($resolution->x(), $resolution->y());
40+
$canvas->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); // or ALPHACHANNEL_ACTIVATE?
3141
3242
// set animation details
3343
if ($image->isAnimated()) {
@@ -36,31 +46,28 @@ public function apply(ImageInterface $image): ImageInterface
3646
$canvas->setImageDispose($frame->native()->getImageDispose());
3747
}
3848

39-
// place original frame content onto the empty colored frame canvas
40-
$canvas->compositeImage(
41-
$frame->native(),
42-
Imagick::COMPOSITE_DEFAULT,
43-
($crop->pivot()->x() + $this->offset_x) * -1,
44-
($crop->pivot()->y() + $this->offset_y) * -1,
49+
// make the rectangular position of the original image transparent
50+
// so that we can later place the original on top. this preserves
51+
// the transparency of the original and shows the background color
52+
// of the modifier in the other areas. if the original image has no
53+
// transparent area the rectangular transparency will be covered by
54+
// the original.
55+
$clearer = new Imagick();
56+
$clearer->newImage(
57+
$frame->native()->getImageWidth(),
58+
$frame->native()->getImageHeight(),
59+
new ImagickPixel('black'),
4560
);
61+
$canvas->compositeImage($clearer, Imagick::COMPOSITE_DSTOUT, ...$position);
4662

47-
// copy alpha channel if available
48-
if ($frame->native()->getImageAlphaChannel()) {
49-
$canvas->compositeImage(
50-
$frame->native(),
51-
version_compare(Driver::version(), '7.0.0', '>=') ?
52-
Imagick::COMPOSITE_COPYOPACITY :
53-
Imagick::COMPOSITE_DSTIN,
54-
($crop->pivot()->x() + $this->offset_x) * -1,
55-
($crop->pivot()->y() + $this->offset_y) * -1,
56-
);
57-
}
63+
// place original frame content onto prepared frame canvas
64+
$canvas->compositeImage($frame->native(), Imagick::COMPOSITE_DEFAULT, ...$position);
5865

5966
// add newly built frame to container imagick
6067
$imagick->addImage($canvas);
6168
}
6269

63-
// replace imagick
70+
// replace imagick in the original image
6471
$image->core()->setNative($imagick);
6572

6673
return $image;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Intervention\Image\Tests\Feature\Imagick;
6+
7+
use Intervention\Image\Tests\ImagickTestCase;
8+
9+
class CropResizePngTest extends ImagickTestCase
10+
{
11+
public function testCropResizePng(): void
12+
{
13+
$image = $this->readTestImage('tile.png');
14+
$image->crop(100, 100);
15+
$image->resize(200, 200);
16+
$this->assertTransparency($image->pickColor(7, 22));
17+
$this->assertTransparency($image->pickColor(22, 7));
18+
}
19+
}

0 commit comments

Comments
 (0)