Skip to content

Commit

Permalink
Fix bug in CropModifier of Imagick driver (#1428)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
olivervogel authored Feb 1, 2025
1 parent 629142e commit 0f87254
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 20 deletions.
47 changes: 27 additions & 20 deletions src/Drivers/Imagick/Modifiers/CropModifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Intervention\Image\Drivers\Imagick\Modifiers;

use Imagick;
use Intervention\Image\Drivers\Imagick\Driver;
use ImagickPixel;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SpecializedInterface;
use Intervention\Image\Modifiers\CropModifier as GenericCropModifier;
Expand All @@ -14,20 +14,30 @@ class CropModifier extends GenericCropModifier implements SpecializedInterface
{
public function apply(ImageInterface $image): ImageInterface
{
$crop = $this->crop($image);
// decode background color
$background = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->driver()->handleInput($this->background)
);

// create empty container imagick to rebuild core
$imagick = new Imagick();

// save resolution to add it later
$resolution = $image->resolution()->perInch();

// define position of the image on the new canvas
$crop = $this->crop($image);
$position = [
($crop->pivot()->x() + $this->offset_x) * -1,
($crop->pivot()->y() + $this->offset_y) * -1,
];

foreach ($image as $frame) {
// create new frame canvas with modifiers background
$canvas = new Imagick();
$canvas->newImage($crop->width(), $crop->height(), $background, 'png');
$canvas->setImageResolution($resolution->x(), $resolution->y());
$canvas->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET); // or ALPHACHANNEL_ACTIVATE?
// set animation details
if ($image->isAnimated()) {
Expand All @@ -36,31 +46,28 @@ public function apply(ImageInterface $image): ImageInterface
$canvas->setImageDispose($frame->native()->getImageDispose());
}

// place original frame content onto the empty colored frame canvas
$canvas->compositeImage(
$frame->native(),
Imagick::COMPOSITE_DEFAULT,
($crop->pivot()->x() + $this->offset_x) * -1,
($crop->pivot()->y() + $this->offset_y) * -1,
// make the rectangular position of the original image transparent
// so that we can later place the original on top. this preserves
// the transparency of the original and shows the background color
// of the modifier in the other areas. if the original image has no
// transparent area the rectangular transparency will be covered by
// the original.
$clearer = new Imagick();
$clearer->newImage(
$frame->native()->getImageWidth(),
$frame->native()->getImageHeight(),
new ImagickPixel('black'),
);
$canvas->compositeImage($clearer, Imagick::COMPOSITE_DSTOUT, ...$position);

// copy alpha channel if available
if ($frame->native()->getImageAlphaChannel()) {
$canvas->compositeImage(
$frame->native(),
version_compare(Driver::version(), '7.0.0', '>=') ?
Imagick::COMPOSITE_COPYOPACITY :
Imagick::COMPOSITE_DSTIN,
($crop->pivot()->x() + $this->offset_x) * -1,
($crop->pivot()->y() + $this->offset_y) * -1,
);
}
// place original frame content onto prepared frame canvas
$canvas->compositeImage($frame->native(), Imagick::COMPOSITE_DEFAULT, ...$position);

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

// replace imagick
// replace imagick in the original image
$image->core()->setNative($imagick);

return $image;
Expand Down
19 changes: 19 additions & 0 deletions tests/Feature/Imagick/CropResizePngTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Intervention\Image\Tests\Feature\Imagick;

use Intervention\Image\Tests\ImagickTestCase;

class CropResizePngTest extends ImagickTestCase
{
public function testCropResizePng(): void
{
$image = $this->readTestImage('tile.png');
$image->crop(100, 100);
$image->resize(200, 200);
$this->assertTransparency($image->pickColor(7, 22));
$this->assertTransparency($image->pickColor(22, 7));
}
}

0 comments on commit 0f87254

Please sign in to comment.