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(
array_map(
fn($item) => $callback($item),
fn(mixed $item) => $callback($item),
$this->items,
)
);
@ -202,7 +202,7 @@ class Collection implements CollectionInterface, IteratorAggregate, Countable
return new self(
array_filter(
$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
{
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,
$normalized,
));

View File

@ -34,7 +34,7 @@ class Colorspace implements ColorspaceInterface
public function colorFromNormalized(array $normalized): ColorInterface
{
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,
$normalized
));

View File

@ -34,7 +34,7 @@ class Colorspace implements ColorspaceInterface
public function colorFromNormalized(array $normalized): ColorInterface
{
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,
$normalized
));

View File

@ -34,7 +34,7 @@ class Colorspace implements ColorspaceInterface
public function colorFromNormalized(array $normalized): ColorInterface
{
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,
$normalized,
));

View File

@ -6,6 +6,7 @@ namespace Intervention\Image\Drivers\Gd\Modifiers;
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;
@ -23,9 +24,7 @@ class CropModifier extends GenericCropModifier implements SpecializedInterface
{
$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);
@ -41,10 +40,10 @@ class CropModifier extends GenericCropModifier implements SpecializedInterface
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;
@ -56,6 +55,9 @@ class CropModifier extends GenericCropModifier implements SpecializedInterface
$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,
@ -70,57 +72,6 @@ class CropModifier extends GenericCropModifier implements SpecializedInterface
$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);
}

View File

@ -4,15 +4,7 @@ declare(strict_types=1);
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\SizeInterface;
use Intervention\Image\Interfaces\SpecializedInterface;
use Intervention\Image\Modifiers\ResizeCanvasModifier as GenericResizeCanvasModifier;
@ -25,62 +17,16 @@ class ResizeCanvasModifier extends GenericResizeCanvasModifier implements Specia
*/
public function apply(ImageInterface $image): ImageInterface
{
$resize = $this->cropSize($image);
$background = $this->driver()->handleInput($this->background);
$cropSize = $this->cropSize($image);
foreach ($image as $frame) {
$this->modify($frame, $resize, $background);
}
$image->modify(new CropModifier(
$cropSize->width(),
$cropSize->height(),
$cropSize->pivot()->x(),
$cropSize->pivot()->y(),
$this->background,
));
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;
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;
@ -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);
// create new frame canvas with modifiers background
$canvas = new Imagick();
$canvas->newImage($crop->width(), $crop->height(), $background, 'png');
$canvas->setImageResolution($resolution->x(), $resolution->y());
// crop image
$frame->native()->extentImage(
$crop->width(),
$crop->height(),
$crop->pivot()->x() + $this->offset_x,
$crop->pivot()->y() + $this->offset_y
// set animation details
if ($image->isAnimated()) {
$canvas->setImageDelay($frame->native()->getImageDelay());
$canvas->setImageIterations($frame->native()->getImageIterations());
$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,
);
// 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()
// 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,
);
}
// 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()
);
}
// 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);
// add newly built frame to container imagick
$imagick->addImage($canvas);
}
// replace imagick
$image->core()->setNative($imagick);
return $image;
}
}

View File

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

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Intervention\Image\Drivers\Imagick\Modifiers;
use ImagickDraw;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SpecializedInterface;
use Intervention\Image\Modifiers\ResizeCanvasModifier as GenericResizeCanvasModifier;
@ -13,74 +12,15 @@ class ResizeCanvasModifier extends GenericResizeCanvasModifier implements Specia
{
public function apply(ImageInterface $image): ImageInterface
{
$size = $image->size();
$resize = $this->cropSize($image);
$cropSize = $this->cropSize($image);
$background = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->driver()->handleInput($this->background)
);
foreach ($image as $frame) {
$frame->native()->extentImage(
$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);
}
}
$image->modify(new CropModifier(
$cropSize->width(),
$cropSize->height(),
$cropSize->pivot()->x(),
$cropSize->pivot()->y(),
$this->background,
));
return $image;
}

View File

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

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Intervention\Image\Tests;
use Imagick;
use ImagickException;
use ImagickPixel;
use Intervention\Image\Decoders\FilePathImageDecoder;
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
{
$background = new ImagickPixel('rgb(255, 0, 0)');
@ -29,6 +38,7 @@ abstract class ImagickTestCase extends BaseTestCase
$imagick->setImageType(Imagick::IMGTYPE_UNDEFINED);
$imagick->setColorspace(Imagick::COLORSPACE_SRGB);
$imagick->setImageResolution(96, 96);
$imagick->setImageBackgroundColor($background);
return new Image(
new Driver(),

View File

@ -36,4 +36,55 @@ final class CropModifierTest extends GdTestCase
$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()));
}
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(255, 255, 0, 255, $image->pickColor(17, 17));
$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

View File

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

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
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;
@ -36,4 +37,63 @@ final class CropModifierTest extends ImagickTestCase
$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()));
}
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->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'));
$this->assertEquals(32, $image->width());
$this->assertEquals(32, $image->height());