1
0
mirror of https://github.com/Intervention/image.git synced 2025-01-16 19:58:14 +01:00

Optimize & Refactor Crop- and ResizeCanvasModifiers (#1418)

* Refactor and simplify CropModifier::class
* Refactor and simplify ResizeCanvasModifier::class
This commit is contained in:
Oliver Vogel 2025-01-12 08:24:23 +01:00 committed by GitHub
parent e07119bc97
commit 58f0afd3c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 220 additions and 274 deletions

View File

@ -185,7 +185,7 @@ class Collection implements CollectionInterface, IteratorAggregate, Countable
return new self( return new self(
array_map( array_map(
fn($item) => $callback($item), fn(mixed $item) => $callback($item),
$this->items, $this->items,
) )
); );
@ -202,7 +202,7 @@ class Collection implements CollectionInterface, IteratorAggregate, Countable
return new self( return new self(
array_filter( array_filter(
$this->items, $this->items,
fn($item) => $callback($item), fn(mixed $item) => $callback($item),
) )
); );
} }

View File

@ -35,7 +35,7 @@ class Colorspace implements ColorspaceInterface
public function colorFromNormalized(array $normalized): ColorInterface public function colorFromNormalized(array $normalized): ColorInterface
{ {
return new Color(...array_map( return new Color(...array_map(
fn($classname, $value_normalized) => (new $classname(normalized: $value_normalized))->value(), fn(string $classname, float $value_normalized) => (new $classname(normalized: $value_normalized))->value(),
self::$channels, self::$channels,
$normalized, $normalized,
)); ));

View File

@ -34,7 +34,7 @@ class Colorspace implements ColorspaceInterface
public function colorFromNormalized(array $normalized): ColorInterface public function colorFromNormalized(array $normalized): ColorInterface
{ {
return new Color(...array_map( return new Color(...array_map(
fn($classname, $value_normalized) => (new $classname(normalized: $value_normalized))->value(), fn(string $classname, float $value_normalized) => (new $classname(normalized: $value_normalized))->value(),
self::$channels, self::$channels,
$normalized $normalized
)); ));

View File

@ -34,7 +34,7 @@ class Colorspace implements ColorspaceInterface
public function colorFromNormalized(array $normalized): ColorInterface public function colorFromNormalized(array $normalized): ColorInterface
{ {
return new Color(...array_map( return new Color(...array_map(
fn($classname, $value_normalized) => (new $classname(normalized: $value_normalized))->value(), fn(string $classname, float $value_normalized) => (new $classname(normalized: $value_normalized))->value(),
self::$channels, self::$channels,
$normalized $normalized
)); ));

View File

@ -34,7 +34,7 @@ class Colorspace implements ColorspaceInterface
public function colorFromNormalized(array $normalized): ColorInterface public function colorFromNormalized(array $normalized): ColorInterface
{ {
return new Color(...array_map( return new Color(...array_map(
fn($classname, $value_normalized) => (new $classname(normalized: $value_normalized))->value(), fn($classname, float $value_normalized) => (new $classname(normalized: $value_normalized))->value(),
self::$channels, self::$channels,
$normalized, $normalized,
)); ));

View File

@ -6,6 +6,7 @@ namespace Intervention\Image\Drivers\Gd\Modifiers;
use Intervention\Image\Drivers\Gd\Cloner; use Intervention\Image\Drivers\Gd\Cloner;
use Intervention\Image\Exceptions\ColorException; use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\FrameInterface; use Intervention\Image\Interfaces\FrameInterface;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SizeInterface; use Intervention\Image\Interfaces\SizeInterface;
@ -23,9 +24,7 @@ class CropModifier extends GenericCropModifier implements SpecializedInterface
{ {
$originalSize = $image->size(); $originalSize = $image->size();
$crop = $this->crop($image); $crop = $this->crop($image);
$background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $background = $this->driver()->handleInput($this->background);
$this->driver()->handleInput($this->background)
);
foreach ($image as $frame) { foreach ($image as $frame) {
$this->cropFrame($frame, $originalSize, $crop, $background); $this->cropFrame($frame, $originalSize, $crop, $background);
@ -41,10 +40,10 @@ class CropModifier extends GenericCropModifier implements SpecializedInterface
FrameInterface $frame, FrameInterface $frame,
SizeInterface $originalSize, SizeInterface $originalSize,
SizeInterface $resizeTo, SizeInterface $resizeTo,
int $background ColorInterface $background
): void { ): void {
// create new image with transparent background // create new image with transparent background
$modified = Cloner::cloneEmpty($frame->native(), $resizeTo); $modified = Cloner::cloneEmpty($frame->native(), $resizeTo, $background);
// define offset // define offset
$offset_x = $resizeTo->pivot()->x() + $this->offset_x; $offset_x = $resizeTo->pivot()->x() + $this->offset_x;
@ -56,6 +55,9 @@ class CropModifier extends GenericCropModifier implements SpecializedInterface
$targetWidth = $targetWidth < $originalSize->width() ? $targetWidth + $offset_x : $targetWidth; $targetWidth = $targetWidth < $originalSize->width() ? $targetWidth + $offset_x : $targetWidth;
$targetHeight = $targetHeight < $originalSize->height() ? $targetHeight + $offset_y : $targetHeight; $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 // copy content from resource
imagecopyresampled( imagecopyresampled(
$modified, $modified,
@ -70,57 +72,6 @@ class CropModifier extends GenericCropModifier implements SpecializedInterface
$targetHeight $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 // set new content as resource
$frame->setNative($modified); $frame->setNative($modified);
} }

View File

@ -4,15 +4,7 @@ declare(strict_types=1);
namespace Intervention\Image\Drivers\Gd\Modifiers; namespace Intervention\Image\Drivers\Gd\Modifiers;
use Intervention\Image\Colors\Rgb\Channels\Blue;
use Intervention\Image\Colors\Rgb\Channels\Green;
use Intervention\Image\Colors\Rgb\Channels\Red;
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\ImageInterface;
use Intervention\Image\Interfaces\SizeInterface;
use Intervention\Image\Interfaces\SpecializedInterface; use Intervention\Image\Interfaces\SpecializedInterface;
use Intervention\Image\Modifiers\ResizeCanvasModifier as GenericResizeCanvasModifier; use Intervention\Image\Modifiers\ResizeCanvasModifier as GenericResizeCanvasModifier;
@ -25,62 +17,16 @@ class ResizeCanvasModifier extends GenericResizeCanvasModifier implements Specia
*/ */
public function apply(ImageInterface $image): ImageInterface public function apply(ImageInterface $image): ImageInterface
{ {
$resize = $this->cropSize($image); $cropSize = $this->cropSize($image);
$background = $this->driver()->handleInput($this->background);
foreach ($image as $frame) { $image->modify(new CropModifier(
$this->modify($frame, $resize, $background); $cropSize->width(),
} $cropSize->height(),
$cropSize->pivot()->x(),
$cropSize->pivot()->y(),
$this->background,
));
return $image; return $image;
} }
/**
* @throws ColorException
*/
protected function modify(
FrameInterface $frame,
SizeInterface $resize,
ColorInterface $background,
): void {
// create new canvas with target size & transparent background color
$modified = Cloner::cloneEmpty($frame->native(), $resize, $background);
// make image area transparent to keep transparency
// even if background-color is set
$transparent = imagecolorallocatealpha(
$modified,
$background->channel(Red::class)->value(),
$background->channel(Green::class)->value(),
$background->channel(Blue::class)->value(),
127,
);
// create transparent area to place the original on top
imagealphablending($modified, false); // do not blend / just overwrite
imagecolortransparent($modified, $transparent);
imagefilledrectangle(
$modified,
$resize->pivot()->x() * -1,
$resize->pivot()->y() * -1,
abs($resize->pivot()->x()) + $frame->size()->width() - 1,
abs($resize->pivot()->y()) + $frame->size()->height() - 1,
$transparent,
);
// place original
imagecopy(
$modified,
$frame->native(),
$resize->pivot()->x() * -1,
$resize->pivot()->y() * -1,
0,
0,
$frame->size()->width(),
$frame->size()->height(),
);
// set new content as resource
$frame->setNative($modified);
}
} }

View File

@ -4,8 +4,8 @@ declare(strict_types=1);
namespace Intervention\Image\Drivers\Imagick\Modifiers; namespace Intervention\Image\Drivers\Imagick\Modifiers;
use ImagickDraw; use Imagick;
use ImagickPixel; use Intervention\Image\Drivers\Imagick\Driver;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SpecializedInterface; use Intervention\Image\Interfaces\SpecializedInterface;
use Intervention\Image\Modifiers\CropModifier as GenericCropModifier; use Intervention\Image\Modifiers\CropModifier as GenericCropModifier;
@ -14,79 +14,54 @@ class CropModifier extends GenericCropModifier implements SpecializedInterface
{ {
public function apply(ImageInterface $image): ImageInterface public function apply(ImageInterface $image): ImageInterface
{ {
$originalSize = $image->size();
$crop = $this->crop($image); $crop = $this->crop($image);
$background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->driver()->handleInput($this->background) $this->driver()->handleInput($this->background)
); );
$transparent = new ImagickPixel('transparent'); // create empty container imagick to rebuild core
$imagick = new Imagick();
$draw = new ImagickDraw(); $resolution = $image->resolution()->perInch();
$draw->setFillColor($background);
foreach ($image as $frame) { foreach ($image as $frame) {
$frame->native()->setBackgroundColor($transparent); // create new frame canvas with modifiers background
$frame->native()->setImageBackgroundColor($transparent); $canvas = new Imagick();
$canvas->newImage($crop->width(), $crop->height(), $background, 'png');
$canvas->setImageResolution($resolution->x(), $resolution->y());
// crop image // set animation details
$frame->native()->extentImage( if ($image->isAnimated()) {
$crop->width(), $canvas->setImageDelay($frame->native()->getImageDelay());
$crop->height(), $canvas->setImageIterations($frame->native()->getImageIterations());
$crop->pivot()->x() + $this->offset_x, $canvas->setImageDispose($frame->native()->getImageDispose());
$crop->pivot()->y() + $this->offset_y }
// 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,
); );
// repage // copy alpha channel if available
$frame->native()->setImagePage( if ($frame->native()->getImageAlphaChannel()) {
$crop->width(), $canvas->compositeImage(
$crop->height(), $frame->native(),
0, version_compare(Driver::version(), '7.0.0', '>=') ?
0, Imagick::COMPOSITE_COPYOPACITY :
); Imagick::COMPOSITE_DSTIN,
($crop->pivot()->x() + $this->offset_x) * -1,
// cover the possible newly created areas with background color ($crop->pivot()->y() + $this->offset_y) * -1,
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()
); );
} }
// cover the possible newly created areas with background color // add newly built frame to container imagick
if ($crop->height() > $originalSize->height() || $this->offset_y > 0) { $imagick->addImage($canvas);
$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 // replace imagick
if ((($this->offset_x * -1) - $crop->pivot()->x() - 1) > 0) { $image->core()->setNative($imagick);
$draw->rectangle(
0,
0,
($this->offset_x * -1) - $crop->pivot()->x() - 1,
$crop->height()
);
}
// 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,
);
}
$frame->native()->drawImage($draw);
}
return $image; return $image;
} }

View File

@ -7,7 +7,6 @@ namespace Intervention\Image\Drivers\Imagick\Modifiers;
use Imagick; use Imagick;
use ImagickDraw; use ImagickDraw;
use ImagickPixel; use ImagickPixel;
use Intervention\Image\Interfaces\FrameInterface;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SpecializedInterface; use Intervention\Image\Interfaces\SpecializedInterface;
use Intervention\Image\Modifiers\FillModifier as ModifiersFillModifier; use Intervention\Image\Modifiers\FillModifier as ModifiersFillModifier;
@ -16,12 +15,11 @@ class FillModifier extends ModifiersFillModifier implements SpecializedInterface
{ {
public function apply(ImageInterface $image): ImageInterface public function apply(ImageInterface $image): ImageInterface
{ {
$color = $this->driver()->handleInput($this->color); $pixel = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$pixel = $this->driver() $this->driver()->handleInput($this->color)
->colorProcessor($image->colorspace()) );
->colorToNative($color);
foreach ($image as $frame) { foreach ($image->core()->native() as $frame) {
if ($this->hasPosition()) { if ($this->hasPosition()) {
$this->floodFillWithColor($frame, $pixel); $this->floodFillWithColor($frame, $pixel);
} else { } else {
@ -32,14 +30,14 @@ class FillModifier extends ModifiersFillModifier implements SpecializedInterface
return $image; return $image;
} }
private function floodFillWithColor(FrameInterface $frame, ImagickPixel $pixel): void private function floodFillWithColor(Imagick $frame, ImagickPixel $pixel): void
{ {
$target = $frame->native()->getImagePixelColor( $target = $frame->getImagePixelColor(
$this->position->x(), $this->position->x(),
$this->position->y() $this->position->y()
); );
$frame->native()->floodfillPaintImage( $frame->floodfillPaintImage(
$pixel, $pixel,
100, 100,
$target, $target,
@ -50,16 +48,16 @@ class FillModifier extends ModifiersFillModifier implements SpecializedInterface
); );
} }
private function fillAllWithColor(FrameInterface $frame, ImagickPixel $pixel): void private function fillAllWithColor(Imagick $frame, ImagickPixel $pixel): void
{ {
$draw = new ImagickDraw(); $draw = new ImagickDraw();
$draw->setFillColor($pixel); $draw->setFillColor($pixel);
$draw->rectangle( $draw->rectangle(0, 0, $frame->getImageWidth(), $frame->getImageHeight());
0, $frame->drawImage($draw);
0,
$frame->native()->getImageWidth(), // deactive alpha channel when image was filled with opaque color
$frame->native()->getImageHeight() if ($pixel->getColorValue(Imagick::COLOR_ALPHA) == 1) {
); $frame->setImageAlphaChannel(Imagick::ALPHACHANNEL_DEACTIVATE);
$frame->native()->drawImage($draw); }
} }
} }

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Intervention\Image\Drivers\Imagick\Modifiers; namespace Intervention\Image\Drivers\Imagick\Modifiers;
use ImagickDraw;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SpecializedInterface; use Intervention\Image\Interfaces\SpecializedInterface;
use Intervention\Image\Modifiers\ResizeCanvasModifier as GenericResizeCanvasModifier; use Intervention\Image\Modifiers\ResizeCanvasModifier as GenericResizeCanvasModifier;
@ -13,74 +12,15 @@ class ResizeCanvasModifier extends GenericResizeCanvasModifier implements Specia
{ {
public function apply(ImageInterface $image): ImageInterface public function apply(ImageInterface $image): ImageInterface
{ {
$size = $image->size(); $cropSize = $this->cropSize($image);
$resize = $this->cropSize($image);
$background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $image->modify(new CropModifier(
$this->driver()->handleInput($this->background) $cropSize->width(),
); $cropSize->height(),
$cropSize->pivot()->x(),
foreach ($image as $frame) { $cropSize->pivot()->y(),
$frame->native()->extentImage( $this->background,
$resize->width(), ));
$resize->height(),
$resize->pivot()->x(),
$resize->pivot()->y()
);
if ($resize->width() > $size->width()) {
// fill new emerged background
$draw = new ImagickDraw();
$draw->setFillColor($background);
$delta_width = abs($resize->pivot()->x());
$delta_height = $resize->pivot()->y() * -1;
if ($delta_width > 0) {
$draw->rectangle(
0,
$delta_height,
$delta_width - 1,
$delta_height + $size->height() - 1
);
}
$draw->rectangle(
$size->width() + $delta_width,
$delta_height,
$resize->width(),
$delta_height + $size->height() - 1
);
$frame->native()->drawImage($draw);
}
if ($resize->height() > $size->height()) {
// fill new emerged background
$draw = new ImagickDraw();
$draw->setFillColor($background);
$delta = abs($resize->pivot()->y());
if ($delta > 0) {
$draw->rectangle(
0,
0,
$resize->width(),
$delta - 1
);
}
$draw->rectangle(
0,
$size->height() + $delta,
$resize->width(),
$resize->height()
);
$frame->native()->drawImage($draw);
}
}
return $image; return $image;
} }

View File

@ -17,6 +17,7 @@ use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Exceptions\NotSupportedException; use Intervention\Image\Exceptions\NotSupportedException;
use Intervention\Image\Interfaces\EncoderInterface; use Intervention\Image\Interfaces\EncoderInterface;
use ReflectionClass; use ReflectionClass;
use ReflectionParameter;
enum Format enum Format
{ {
@ -155,7 +156,7 @@ enum Format
$reflectionClass = new ReflectionClass($classname); $reflectionClass = new ReflectionClass($classname);
if ($constructor = $reflectionClass->getConstructor()) { if ($constructor = $reflectionClass->getConstructor()) {
$parameters = array_map( $parameters = array_map(
fn($parameter): string => $parameter->getName(), fn(ReflectionParameter $parameter): string => $parameter->getName(),
$constructor->getParameters(), $constructor->getParameters(),
); );
} }
@ -163,7 +164,7 @@ enum Format
// filter out unavailable options of target encoder // filter out unavailable options of target encoder
$options = array_filter( $options = array_filter(
$options, $options,
fn($key): bool => in_array($key, $parameters), fn(mixed $key): bool => in_array($key, $parameters),
ARRAY_FILTER_USE_KEY, ARRAY_FILTER_USE_KEY,
); );

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Intervention\Image\Tests; namespace Intervention\Image\Tests;
use Imagick; use Imagick;
use ImagickException;
use ImagickPixel; use ImagickPixel;
use Intervention\Image\Decoders\FilePathImageDecoder; use Intervention\Image\Decoders\FilePathImageDecoder;
use Intervention\Image\Drivers\Imagick\Core; use Intervention\Image\Drivers\Imagick\Core;
@ -20,6 +21,14 @@ abstract class ImagickTestCase extends BaseTestCase
); );
} }
/**
* 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 public static function createTestImage(int $width, int $height): Image
{ {
$background = new ImagickPixel('rgb(255, 0, 0)'); $background = new ImagickPixel('rgb(255, 0, 0)');
@ -29,6 +38,7 @@ abstract class ImagickTestCase extends BaseTestCase
$imagick->setImageType(Imagick::IMGTYPE_UNDEFINED); $imagick->setImageType(Imagick::IMGTYPE_UNDEFINED);
$imagick->setColorspace(Imagick::COLORSPACE_SRGB); $imagick->setColorspace(Imagick::COLORSPACE_SRGB);
$imagick->setImageResolution(96, 96); $imagick->setImageResolution(96, 96);
$imagick->setImageBackgroundColor($background);
return new Image( return new Image(
new Driver(), new Driver(),

View File

@ -36,4 +36,55 @@ final class CropModifierTest extends GdTestCase
$this->assertColor(0, 0, 255, 255, $image->pickColor(445, 16)); $this->assertColor(0, 0, 255, 255, $image->pickColor(445, 16));
$this->assertTransparency($image->pickColor(460, 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()));
}
public function testHalfTransparent(): void
{
$image = $this->createTestImage(16, 16);
$image->modify(new CropModifier(32, 32, 0, 0, '00f5', 'center'));
$this->assertEquals(32, $image->width());
$this->assertEquals(32, $image->height());
$this->assertColor(0, 0, 255, 85, $image->pickColor(5, 5));
$this->assertColor(0, 0, 255, 85, $image->pickColor(16, 5));
$this->assertColor(0, 0, 255, 85, $image->pickColor(30, 5));
$this->assertColor(0, 0, 255, 85, $image->pickColor(5, 16));
$this->assertColor(255, 0, 0, 255, $image->pickColor(16, 16));
$this->assertColor(0, 0, 255, 85, $image->pickColor(30, 16));
$this->assertColor(0, 0, 255, 85, $image->pickColor(5, 30));
$this->assertColor(0, 0, 255, 85, $image->pickColor(16, 30));
$this->assertColor(0, 0, 255, 85, $image->pickColor(30, 30));
}
public function testMergeTransparentBackgrounds(): void
{
$image = $this->createTestImage(1, 1)->fill('f00');
$this->assertEquals(1, $image->width());
$this->assertEquals(1, $image->height());
$image->modify(new CropModifier(3, 3, 0, 0, '00f7', 'center'));
$this->assertEquals(3, $image->width());
$this->assertEquals(3, $image->height());
$this->assertColor(0, 0, 255, 119, $image->pickColor(0, 0));
$this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1));
$this->assertColor(0, 0, 255, 119, $image->pickColor(2, 2));
}
} }

View File

@ -40,6 +40,20 @@ final class ResizeCanvasModifierTest extends GdTestCase
$this->assertColor(180, 224, 0, 255, $image->pickColor(2, 2)); $this->assertColor(180, 224, 0, 255, $image->pickColor(2, 2));
$this->assertColor(255, 255, 0, 255, $image->pickColor(17, 17)); $this->assertColor(255, 255, 0, 255, $image->pickColor(17, 17));
$this->assertTransparency($image->pickColor(12, 1)); $this->assertTransparency($image->pickColor(12, 1));
$image = $this->createTestImage(16, 16);
$image->modify(new ResizeCanvasModifier(32, 32, '00f5', 'center'));
$this->assertEquals(32, $image->width());
$this->assertEquals(32, $image->height());
$this->assertColor(0, 0, 255, 85, $image->pickColor(5, 5));
$this->assertColor(0, 0, 255, 85, $image->pickColor(16, 5));
$this->assertColor(0, 0, 255, 85, $image->pickColor(30, 5));
$this->assertColor(0, 0, 255, 85, $image->pickColor(5, 16));
$this->assertColor(255, 0, 0, 255, $image->pickColor(16, 16));
$this->assertColor(0, 0, 255, 85, $image->pickColor(30, 16));
$this->assertColor(0, 0, 255, 85, $image->pickColor(5, 30));
$this->assertColor(0, 0, 255, 85, $image->pickColor(16, 30));
$this->assertColor(0, 0, 255, 85, $image->pickColor(30, 30));
} }
public function testModifyEdge(): void public function testModifyEdge(): void

View File

@ -64,7 +64,7 @@ final class PngEncoderTest extends ImagickTestCase
yield [ yield [
static::createTestImage(3, 2)->fill('ccc'), // new grayscale static::createTestImage(3, 2)->fill('ccc'), // new grayscale
new PngEncoder(indexed: true), new PngEncoder(indexed: true),
'grayscale', // result should be 'indexed' but there seems to be no way to force this with imagick 'indexed',
]; ];
yield [ yield [
static::readTestImage('circle.png'), // truecolor-alpha static::readTestImage('circle.png'), // truecolor-alpha

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Intervention\Image\Tests\Unit\Drivers\Imagick\Modifiers; namespace Intervention\Image\Tests\Unit\Drivers\Imagick\Modifiers;
use Intervention\Image\Colors\Cmyk\Colorspace;
use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\Attributes\RequiresPhpExtension;
use Intervention\Image\Modifiers\CropModifier; use Intervention\Image\Modifiers\CropModifier;
@ -36,4 +37,63 @@ final class CropModifierTest extends ImagickTestCase
$this->assertColor(0, 0, 255, 255, $image->pickColor(445, 16)); $this->assertColor(0, 0, 255, 255, $image->pickColor(445, 16));
$this->assertTransparency($image->pickColor(460, 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()));
}
public function testHalfTransparent(): void
{
$image = $this->createTestImage(16, 16);
$image->modify(new CropModifier(32, 32, 0, 0, '00f5', 'center'));
$this->assertEquals(32, $image->width());
$this->assertEquals(32, $image->height());
$this->assertColor(0, 0, 255, 77, $image->pickColor(5, 5));
$this->assertColor(0, 0, 255, 77, $image->pickColor(16, 5));
$this->assertColor(0, 0, 255, 77, $image->pickColor(30, 5));
$this->assertColor(0, 0, 255, 77, $image->pickColor(5, 16));
$this->assertColor(255, 0, 0, 255, $image->pickColor(16, 16));
$this->assertColor(0, 0, 255, 77, $image->pickColor(30, 16));
$this->assertColor(0, 0, 255, 77, $image->pickColor(5, 30));
$this->assertColor(0, 0, 255, 77, $image->pickColor(16, 30));
$this->assertColor(0, 0, 255, 77, $image->pickColor(30, 30));
}
public function testMergeTransparentBackgrounds(): void
{
$image = $this->createTestImage(1, 1)->fill('f00');
$this->assertEquals(1, $image->width());
$this->assertEquals(1, $image->height());
$image->modify(new CropModifier(3, 3, 0, 0, '00f7', 'center'));
$this->assertEquals(3, $image->width());
$this->assertEquals(3, $image->height());
$this->assertColor(0, 0, 255, 127, $image->pickColor(0, 0), 1);
$this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1));
$this->assertColor(0, 0, 255, 127, $image->pickColor(2, 2), 1);
}
} }

View File

@ -41,7 +41,7 @@ final class ResizeCanvasModifierTest extends ImagickTestCase
$this->assertColor(255, 255, 0, 255, $image->pickColor(17, 17)); $this->assertColor(255, 255, 0, 255, $image->pickColor(17, 17));
$this->assertTransparency($image->pickColor(12, 1)); $this->assertTransparency($image->pickColor(12, 1));
$image = $this->createTestImage(16, 16)->fill('f00'); $image = $this->createTestImage(16, 16);
$image->modify(new ResizeCanvasModifier(32, 32, '00f5', 'center')); $image->modify(new ResizeCanvasModifier(32, 32, '00f5', 'center'));
$this->assertEquals(32, $image->width()); $this->assertEquals(32, $image->width());
$this->assertEquals(32, $image->height()); $this->assertEquals(32, $image->height());