Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor imagick driver CropModifier::class #1415

Closed
wants to merge 11 commits into from
63 changes: 7 additions & 56 deletions src/Drivers/Gd/Modifiers/CropModifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Intervention\Image\Drivers\Gd\Cloner;
use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\FrameInterface;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SizeInterface;
Expand All @@ -23,9 +24,7 @@ public function apply(ImageInterface $image): ImageInterface
{
$originalSize = $image->size();
$crop = $this->crop($image);
$background = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->driver()->handleInput($this->background)
);
$background = $this->driver()->handleInput($this->background);

foreach ($image as $frame) {
$this->cropFrame($frame, $originalSize, $crop, $background);
Expand All @@ -41,10 +40,10 @@ protected function cropFrame(
FrameInterface $frame,
SizeInterface $originalSize,
SizeInterface $resizeTo,
int $background
ColorInterface $background
): void {
// create new image with transparent background
$modified = Cloner::cloneEmpty($frame->native(), $resizeTo);
$modified = Cloner::cloneEmpty($frame->native(), $resizeTo, $background);

// define offset
$offset_x = $resizeTo->pivot()->x() + $this->offset_x;
Expand All @@ -56,6 +55,9 @@ protected function cropFrame(
$targetWidth = $targetWidth < $originalSize->width() ? $targetWidth + $offset_x : $targetWidth;
$targetHeight = $targetHeight < $originalSize->height() ? $targetHeight + $offset_y : $targetHeight;

// don't alpha blend for copy operation to keep transparent areas of original image
imagealphablending($modified, false);

// copy content from resource
imagecopyresampled(
$modified,
Expand All @@ -70,57 +72,6 @@ protected function cropFrame(
$targetHeight
);

// don't alpha blend for covering areas
imagealphablending($modified, false);

// cover the possible newly created areas with background color
if ($resizeTo->width() > $originalSize->width() || $this->offset_x > 0) {
imagefilledrectangle(
$modified,
$originalSize->width() + ($this->offset_x * -1) - $resizeTo->pivot()->x(),
0,
$resizeTo->width(),
$resizeTo->height(),
$background
);
}

// cover the possible newly created areas with background color
if ($resizeTo->height() > $originalSize->height() || $this->offset_y > 0) {
imagefilledrectangle(
$modified,
($this->offset_x * -1) - $resizeTo->pivot()->x(),
$originalSize->height() + ($this->offset_y * -1) - $resizeTo->pivot()->y(),
($this->offset_x * -1) + $originalSize->width() - 1 - $resizeTo->pivot()->x(),
$resizeTo->height(),
$background
);
}

// cover the possible newly created areas with background color
if ((($this->offset_x * -1) - $resizeTo->pivot()->x() - 1) > 0) {
imagefilledrectangle(
$modified,
0,
0,
($this->offset_x * -1) - $resizeTo->pivot()->x() - 1,
$resizeTo->height(),
$background
);
}

// cover the possible newly created areas with background color
if ((($this->offset_y * -1) - $resizeTo->pivot()->y() - 1) > 0) {
imagefilledrectangle(
$modified,
($this->offset_x * -1) - $resizeTo->pivot()->x(),
0,
($this->offset_x * -1) + $originalSize->width() - $resizeTo->pivot()->x() - 1,
($this->offset_y * -1) - $resizeTo->pivot()->y() - 1,
$background
);
}

// set new content as resource
$frame->setNative($modified);
}
Expand Down
95 changes: 35 additions & 60 deletions src/Drivers/Imagick/Modifiers/CropModifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace Intervention\Image\Drivers\Imagick\Modifiers;

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

$transparent = new ImagickPixel('transparent');

$draw = new ImagickDraw();
$draw->setFillColor($background);
// create empty container imagick to rebuild core
$imagick = new Imagick();
$resolution = $image->resolution()->perInch();

foreach ($image as $frame) {
$frame->native()->setBackgroundColor($transparent);
$frame->native()->setImageBackgroundColor($transparent);

// crop image
$frame->native()->extentImage(
$crop->width(),
$crop->height(),
$crop->pivot()->x() + $this->offset_x,
$crop->pivot()->y() + $this->offset_y
);
// create new frame canvas with modifiers background
$canvas = new Imagick();
$canvas->newImage($crop->width(), $crop->height(), $background, 'png');
$canvas->setImageResolution($resolution->x(), $resolution->y());

// repage
$frame->native()->setImagePage(
$crop->width(),
$crop->height(),
0,
0,
);

// cover the possible newly created areas with background color
if ($crop->width() > $originalSize->width() || $this->offset_x > 0) {
$draw->rectangle(
$originalSize->width() + ($this->offset_x * -1) - $crop->pivot()->x(),
0,
$crop->width(),
$crop->height()
);
// set animation details
if ($image->isAnimated()) {
$canvas->setImageDelay($frame->native()->getImageDelay());
$canvas->setImageIterations($frame->native()->getImageIterations());
$canvas->setImageDispose($frame->native()->getImageDispose());
}

// cover the possible newly created areas with background color
if ($crop->height() > $originalSize->height() || $this->offset_y > 0) {
$draw->rectangle(
($this->offset_x * -1) - $crop->pivot()->x(),
$originalSize->height() + ($this->offset_y * -1) - $crop->pivot()->y(),
($this->offset_x * -1) + $originalSize->width() - 1 - $crop->pivot()->x(),
$crop->height()
);
}

// cover the possible newly created areas with background color
if ((($this->offset_x * -1) - $crop->pivot()->x() - 1) > 0) {
$draw->rectangle(
0,
0,
($this->offset_x * -1) - $crop->pivot()->x() - 1,
$crop->height()
);
}
// place original frame content onto the empty colored frame canvas
$canvas->compositeImage(
$frame->native(),
Imagick::COMPOSITE_OVER,
($crop->pivot()->x() + $this->offset_x) * -1,
($crop->pivot()->y() + $this->offset_y) * -1,
);

// cover the possible newly created areas with background color
if ((($this->offset_y * -1) - $crop->pivot()->y() - 1) > 0) {
$draw->rectangle(
($this->offset_x * -1) - $crop->pivot()->x(),
0,
($this->offset_x * -1) + $originalSize->width() - $crop->pivot()->x() - 1,
($this->offset_y * -1) - $crop->pivot()->y() - 1,
// 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,
);
}

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

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

return $image;
}
}
9 changes: 9 additions & 0 deletions tests/ImagickTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Intervention\Image\Tests;

use Imagick;
use ImagickException;
use ImagickPixel;
use Intervention\Image\Decoders\FilePathImageDecoder;
use Intervention\Image\Drivers\Imagick\Core;
Expand All @@ -20,6 +21,14 @@ public static function readTestImage($filename = 'test.jpg'): Image
);
}

/**
* Create test image with red (#ff0000) background
*
* @param int $width
* @param int $height
* @return Image
* @throws ImagickException
*/
public static function createTestImage(int $width, int $height): Image
{
$background = new ImagickPixel('rgb(255, 0, 0)');
Expand Down
21 changes: 21 additions & 0 deletions tests/Unit/Drivers/Gd/Modifiers/CropModifierTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,25 @@ public function testModifyExtend(): void
$this->assertColor(0, 0, 255, 255, $image->pickColor(445, 16));
$this->assertTransparency($image->pickColor(460, 16));
}

public function testModifySinglePixel(): void
{
$image = $this->createTestImage(1, 1);
$this->assertEquals(1, $image->width());
$this->assertEquals(1, $image->height());
$image->modify(new CropModifier(3, 3, 0, 0, 'ff0', 'center'));
$this->assertEquals(3, $image->width());
$this->assertEquals(3, $image->height());
$this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0));
$this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1));
$this->assertColor(255, 255, 0, 255, $image->pickColor(2, 2));
}

public function testModifyKeepsResolution(): void
{
$image = $this->readTestImage('300dpi.png');
$this->assertEquals(300, round($image->resolution()->perInch()->x()));
$image = $image->modify(new CropModifier(800, 100, -10, -10, 'ff0000'));
$this->assertEquals(300, round($image->resolution()->perInch()->x()));
}
}
30 changes: 30 additions & 0 deletions tests/Unit/Drivers/Imagick/Modifiers/CropModifierTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Intervention\Image\Tests\Unit\Drivers\Imagick\Modifiers;

use Intervention\Image\Colors\Cmyk\Colorspace;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
use Intervention\Image\Modifiers\CropModifier;
Expand Down Expand Up @@ -36,4 +37,33 @@ public function testModifyExtend(): void
$this->assertColor(0, 0, 255, 255, $image->pickColor(445, 16));
$this->assertTransparency($image->pickColor(460, 16));
}

public function testModifySinglePixel(): void
{
$image = $this->createTestImage(1, 1);
$this->assertEquals(1, $image->width());
$this->assertEquals(1, $image->height());
$image->modify(new CropModifier(3, 3, 0, 0, 'ff0', 'center'));
$this->assertEquals(3, $image->width());
$this->assertEquals(3, $image->height());
$this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0));
$this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1));
$this->assertColor(255, 255, 0, 255, $image->pickColor(2, 2));
}

public function testModifyKeepsColorspace(): void
{
$image = $this->readTestImage('cmyk.jpg');
$this->assertInstanceOf(Colorspace::class, $image->colorspace());
$image = $image->modify(new CropModifier(800, 100, -10, -10, 'ff0000'));
$this->assertInstanceOf(Colorspace::class, $image->colorspace());
}

public function testModifyKeepsResolution(): void
{
$image = $this->readTestImage('300dpi.png');
$this->assertEquals(300, round($image->resolution()->perInch()->x()));
$image = $image->modify(new CropModifier(800, 100, -10, -10, 'ff0000'));
$this->assertEquals(300, round($image->resolution()->perInch()->x()));
}
}
Loading