diff --git a/composer.json b/composer.json index 8cc3f35..b853ba2 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,8 @@ ], "require": { "php": ">=8.1", - "intervention/image": "^2.5", - "spatie/image-optimizer": "^1.2", + "intervention/image": "^3.2.0", + "spatie/image-optimizer": "^1.2.0", "ext-json": "*" }, "require-dev": { @@ -43,7 +43,8 @@ }, "autoload-dev": { "psr-4": { - "PhpCollective\\Test\\": "tests/" + "PhpCollective\\Test\\": "tests/", + "TestApp\\": "tests/test_app/src/" } }, "scripts": { diff --git a/phpstan.neon b/phpstan.neon index 26d8b11..af3d968 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,3 +3,4 @@ parameters: paths: - src/ checkGenericClassInNonGenericObjectType: false + checkMissingIterableValueType: false diff --git a/src/ImageProcessor.php b/src/ImageProcessor.php index c14220c..3dea9a4 100644 --- a/src/ImageProcessor.php +++ b/src/ImageProcessor.php @@ -17,6 +17,7 @@ namespace PhpCollective\Infrastructure\Storage\Processor\Image; use GuzzleHttp\Psr7\StreamWrapper; +use Intervention\Image\Interfaces\ImageInterface; use InvalidArgumentException; use Intervention\Image\Image; use Intervention\Image\ImageManager; @@ -74,9 +75,9 @@ class ImageProcessor implements ProcessorInterface protected ImageManager $imageManager; /** - * @var \Intervention\Image\Image + * @var \Intervention\Image\Interfaces\ImageInterface */ - protected Image $image; + protected ImageInterface $image; /** * Quality setting for writing images @@ -239,7 +240,7 @@ public function process(FileInterface $file): FileInterface continue; } - $this->image = $this->imageManager->make($tempFile); + $this->image = $this->imageManager->read($tempFile); $operations = new Operations($this->image); // Apply the operations @@ -285,11 +286,12 @@ protected function optimizeAndStore(FileInterface $file, string $path): void // We need more tmp files because the optimizer likes to write // and read the files from disk, not from a stream. :( + //FIXME Use memory/stream instead? $optimizerTempFile = TemporaryFile::create(); $optimizerOutput = TemporaryFile::create(); // Save the image to the tmp file - $this->image->save($optimizerTempFile, 90, $file->extension()); + $this->image->save($optimizerTempFile, 90); // Optimize it and write it to another file $this->optimizer()->optimize($optimizerTempFile, $optimizerOutput); // Open a new stream for the storage system diff --git a/src/ImageVariant.php b/src/ImageVariant.php index d261670..e475114 100644 --- a/src/ImageVariant.php +++ b/src/ImageVariant.php @@ -223,17 +223,16 @@ public function callback(callable $callback): self } /** - * @link http://image.intervention.io/api/fit * @param int $width Width - * @param int|null $height Height + * @param null $height Height * @param callable|null $callback Callback * @param bool $preventUpscale Prevent Upscaling * @param string $position Position * @return $this */ - public function fit( + public function cover( int $width, - ?int $height = null, + int $height, ?callable $callback = null, bool $preventUpscale = false, string $position = 'center' diff --git a/src/Operations.php b/src/Operations.php index e983b27..21e03f2 100644 --- a/src/Operations.php +++ b/src/Operations.php @@ -16,24 +16,36 @@ namespace PhpCollective\Infrastructure\Storage\Processor\Image; -use Intervention\Image\Image; +use Intervention\Image\Interfaces\ImageInterface; use InvalidArgumentException; use PhpCollective\Infrastructure\Storage\Processor\Image\Exception\UnsupportedOperationException; /** * Operations + * + * @link https://image.intervention.io/v3 */ class Operations { + public const POSITION_CENTER = 'center'; + public const POSITION_TOP_CENTER = 'top-center'; + public const POSITION_BOTTOM_CENTER = 'bottom-center'; + public const POSITION_LEFT_TOP = 'left-top'; + public const POSITION_RIGHT_TOP = 'right-top'; + public const POSITION_LEFT_CENTER = 'left-center'; + public const POSITION_RIGHT_CENTER = 'right-center'; + public const POSITION_LEFT_BOTTOM = 'left-bottom'; + public const POSITION_RIGHT_BOTTOM = 'right-bottom'; + /** - * @var \Intervention\Image\Image + * @var \Intervention\Image\Interfaces\ImageInterface */ - protected Image $image; + protected ImageInterface $image; /** - * @param \Intervention\Image\Image $image Image + * @param \Intervention\Image\Interfaces\ImageInterface $image Image */ - public function __construct(Image $image) + public function __construct(ImageInterface $image) { $this->image = $image; } @@ -51,34 +63,37 @@ public function __call(string $name, array $arguments): mixed /** * Crops the image * - * @link http://image.intervention.io/api/fit * @param array $arguments Arguments * @return void */ - public function fit(array $arguments): void + public function cover(array $arguments): void { - if (!isset($arguments['width'])) { - throw new InvalidArgumentException('Missing width'); + if (!isset($arguments['height'], $arguments['width'])) { + throw new InvalidArgumentException('Missing width or height'); } + $arguments += ['position' => static::POSITION_CENTER]; $preventUpscale = $arguments['preventUpscale'] ?? false; - $height = $arguments['height'] ?? null; + if ($preventUpscale) { + $this->image->coverDown( + (int)$arguments['width'], + (int)$arguments['height'], + $arguments['position'] + ); - $this->image->fit( + return; + } + + $this->image->cover( (int)$arguments['width'], - (int)$height, - static function ($constraint) use ($preventUpscale) { - if ($preventUpscale) { - $constraint->upsize(); - } - } + (int)$arguments['height'], + $arguments['position'] ); } /** * Crops the image * - * @link http://image.intervention.io/api/crop * @param array $arguments Arguments * @return void */ @@ -88,19 +103,18 @@ public function crop(array $arguments): void throw new InvalidArgumentException('Missing width or height'); } - $arguments = array_merge(['x' => null, 'y' => null], $arguments); + $arguments += ['x' => null, 'y' => null, 'position' => static::POSITION_CENTER]; $height = $arguments['height'] ? (int)$arguments['height'] : null; $width = $arguments['width'] ? (int)$arguments['width'] : null; - $x = $arguments['x'] ? (int)$arguments['x'] : null; - $y = $arguments['y'] ? (int)$arguments['y'] : null; + $x = $arguments['x'] ? (int)$arguments['x'] : 0; + $y = $arguments['y'] ? (int)$arguments['y'] : 0; - $this->image->crop($width, $height, $x, $y); + $this->image->crop($width, $height, $x, $y, $arguments['position']); } /** * Flips the image horizontal * - * @link http://image.intervention.io/api/flip * @return void */ public function flipHorizontal(): void @@ -111,7 +125,6 @@ public function flipHorizontal(): void /** * Flips the image vertical * - * @link http://image.intervention.io/api/flip * @return void */ public function flipVertical(): void @@ -122,7 +135,6 @@ public function flipVertical(): void /** * Flips the image * - * @link http://image.intervention.io/api/flip * @param array $arguments Arguments * @return void */ @@ -138,17 +150,21 @@ public function flip(array $arguments): void ); } - $this->image->flip($arguments['direction']); + if ($arguments['direction'] === 'h') { + $this->image->flip(); + + return; + } + + $this->image->flop(); } /** - * Resizes the image + * @param array $arguments * - * @link http://image.intervention.io/api/resize - * @param array $arguments Arguments * @return void */ - public function resize(array $arguments): void + public function scale(array $arguments): void { if (!isset($arguments['height'], $arguments['width'])) { throw new InvalidArgumentException( @@ -156,69 +172,63 @@ public function resize(array $arguments): void ); } - $aspectRatio = $arguments['aspectRatio'] ?? true; $preventUpscale = $arguments['preventUpscale'] ?? false; - $this->image->resize( + if ($preventUpscale) { + $this->image->scaleDown( + $arguments['width'], + $arguments['height'] + ); + + return; + } + + $this->image->scale( $arguments['width'], $arguments['height'], - static function ($constraint) use ($aspectRatio, $preventUpscale) { - if ($aspectRatio) { - $constraint->aspectRatio(); - } - if ($preventUpscale) { - $constraint->upsize(); - } - } ); } /** - * @link http://image.intervention.io/api/widen + * Resizes the image + * * @param array $arguments Arguments * @return void */ - public function widen(array $arguments): void + public function resize(array $arguments): void { - if (!isset($arguments['width'])) { + if (!isset($arguments['height'], $arguments['width'])) { throw new InvalidArgumentException( - 'Missing width' + 'Missing height or width' ); } - $preventUpscale = $arguments['preventUpscale'] ?? false; - - $this->image->widen((int)$arguments['width'], function ($constraint) use ($preventUpscale) { - if ($preventUpscale) { - $constraint->upsize(); - } - }); - } + // Deprecated: Coming from old API + $aspectRatio = $arguments['aspectRatio'] ?? null; + if ($aspectRatio !== null) { + $this->scale($arguments); - /** - * @link http://image.intervention.io/api/heighten - * @param array $arguments Arguments - * @return void - */ - public function heighten(array $arguments): void - { - if (!isset($arguments['height'])) { - throw new InvalidArgumentException( - 'Missing height' - ); + return; } $preventUpscale = $arguments['preventUpscale'] ?? false; - $this->image->heighten((int)$arguments['height'], function ($constraint) use ($preventUpscale) { - if ($preventUpscale) { - $constraint->upsize(); - } - }); + if ($preventUpscale) { + $this->image->resizeDown( + $arguments['width'], + $arguments['height'] + ); + + return; + } + + $this->image->resize( + $arguments['width'], + $arguments['height'], + ); } /** - * @link http://image.intervention.io/api/rotate * @param array $arguments Arguments * @return void */ @@ -234,8 +244,6 @@ public function rotate(array $arguments): void } /** - * @link http://image.intervention.io/api/rotate - * @param array $arguments Arguments * @return void */ public function sharpen(array $arguments): void diff --git a/tests/TestCase/Processor/Image/ImageProcessorTest.php b/tests/TestCase/Processor/Image/ImageProcessorTest.php index d461ce0..cb4d34b 100644 --- a/tests/TestCase/Processor/Image/ImageProcessorTest.php +++ b/tests/TestCase/Processor/Image/ImageProcessorTest.php @@ -16,7 +16,7 @@ namespace PhpCollective\Test\TestCase\Processor\Image; -use Intervention\Image\Image; +use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\ImageManager; use PhpCollective\Infrastructure\Storage\File; use PhpCollective\Infrastructure\Storage\FileFactory; @@ -25,6 +25,7 @@ use PhpCollective\Infrastructure\Storage\Processor\Image\ImageVariantCollection; use PhpCollective\Infrastructure\Storage\Processor\Image\ImageProcessor; use PhpCollective\Test\TestCase\TestCase; +use TestApp\Image; /** * ImageProcessorTest @@ -41,15 +42,7 @@ public function testProcessor(): void $pathBuilder = new PathBuilder(); - $imageManager = $this->getMockBuilder(ImageManager::class) - ->getMock(); - - $image = $this->getMockBuilder(Image::class) - ->getMock(); - - $imageManager->expects($this->any()) - ->method('make') - ->willReturn($image); + $imageManager = new ImageManager(new Driver()); $processor = new ImageProcessor( $fileStorage, diff --git a/tests/TestCase/Processor/Image/ImageVariantTest.php b/tests/TestCase/Processor/Image/ImageVariantTest.php index c573f14..99cd0b6 100644 --- a/tests/TestCase/Processor/Image/ImageVariantTest.php +++ b/tests/TestCase/Processor/Image/ImageVariantTest.php @@ -37,7 +37,7 @@ public function testVariant(): void ->heighten(200) ->widen(200) ->crop(200, 200) - ->fit(200, 200) + ->cover(200, 200) ->rotate(90) ->sharpen(90) ->optimize(); diff --git a/tests/TestCase/Processor/Image/OperationsTest.php b/tests/TestCase/Processor/Image/OperationsTest.php index 88b0f7d..1abf866 100644 --- a/tests/TestCase/Processor/Image/OperationsTest.php +++ b/tests/TestCase/Processor/Image/OperationsTest.php @@ -16,9 +16,9 @@ namespace PhpCollective\Test\TestCase\Processor\Image; -use Intervention\Image\Image; use PhpCollective\Infrastructure\Storage\Processor\Image\Operations; use PhpCollective\Test\TestCase\TestCase; +use TestApp\Image; /** * OperationsTest @@ -31,7 +31,8 @@ class OperationsTest extends TestCase public function testOperations(): void { $imageMock = $this->getMockBuilder(Image::class) - ->addMethods([ + ->disableOriginalConstructor() + ->onlyMethods([ 'resize' ]) ->getMock(); @@ -48,45 +49,22 @@ public function testOperations(): void /** * @return void */ - public function testHeighten(): void + public function testScale(): void { $imageMock = $this->getMockBuilder(Image::class) - ->addMethods([ - 'heighten' + ->disableOriginalConstructor() + ->onlyMethods([ + 'scale' ]) ->getMock(); $operations = new Operations($imageMock); $imageMock->expects($this->once()) - ->method('heighten') - ->with(100); + ->method('scale') + ->with(100, 200); - $operations->heighten(['height' => 100]); - } - - /** - * @return void - */ - public function testWiden(): void - { - $imageMock = $this->getMockBuilder(Image::class) - ->addMethods([ - 'widen' - ]) - ->getMock(); - - $operations = new Operations($imageMock); - - $imageMock->expects($this->once()) - ->method('widen') - ->with(100); - - $operations->widen(['width' => 100]); - - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Missing width'); - $operations->widen([]); + $operations->scale(['height' => 200, 'width' => 100]); } /** @@ -95,7 +73,8 @@ public function testWiden(): void public function testRotate(): void { $imageMock = $this->getMockBuilder(Image::class) - ->addMethods([ + ->disableOriginalConstructor() + ->onlyMethods([ 'rotate' ]) ->getMock(); @@ -119,7 +98,8 @@ public function testRotate(): void public function testSharpen(): void { $imageMock = $this->getMockBuilder(Image::class) - ->addMethods([ + ->disableOriginalConstructor() + ->onlyMethods([ 'sharpen' ]) ->getMock(); @@ -140,25 +120,26 @@ public function testSharpen(): void /** * @return void */ - public function testFit(): void + public function testCover(): void { $imageMock = $this->getMockBuilder(Image::class) - ->addMethods([ - 'fit' + ->disableOriginalConstructor() + ->onlyMethods([ + 'cover' ]) ->getMock(); $operations = new Operations($imageMock); $imageMock->expects($this->once()) - ->method('fit') - ->with(100); + ->method('cover') + ->with(100, 100); - $operations->fit(['width' => 100]); + $operations->cover(['width' => 100, 'height' => 100]); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Missing width'); - $operations->fit([]); + $operations->cover([]); } /** @@ -167,7 +148,8 @@ public function testFit(): void public function testCrop(): void { $imageMock = $this->getMockBuilder(Image::class) - ->addMethods([ + ->disableOriginalConstructor() + ->onlyMethods([ 'crop' ]) ->getMock(); @@ -176,7 +158,7 @@ public function testCrop(): void $imageMock->expects($this->once()) ->method('crop') - ->with(100); + ->with(100, 200); $operations->crop(['width' => 100, 'height' => 200]); diff --git a/tests/test_app/src/Image.php b/tests/test_app/src/Image.php new file mode 100644 index 0000000..e4f2da1 --- /dev/null +++ b/tests/test_app/src/Image.php @@ -0,0 +1,932 @@ +origin = new Origin(); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::driver() + */ + public function driver(): DriverInterface + { + return $this->driver; + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::core() + */ + public function core(): CoreInterface + { + return $this->core; + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::origin() + */ + public function origin(): Origin + { + return $this->origin; + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::setOrigin() + */ + public function setOrigin(Origin $origin): ImageInterface + { + $this->origin = $origin; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::count() + */ + public function count(): int + { + return $this->core->count(); + } + + /** + * Implementation of IteratorAggregate + * + * @return Traversable + */ + public function getIterator(): Traversable + { + return $this->core; + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::isAnimated() + */ + public function isAnimated(): bool + { + return $this->count() > 1; + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::removeAnimation( + */ + public function removeAnimation(int|string $position = 0): ImageInterface + { + return $this->modify(new RemoveAnimationModifier($position)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::loops() + */ + public function loops(): int + { + return $this->core->loops(); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::setLoops() + */ + public function setLoops(int $loops): ImageInterface + { + $this->core->setLoops($loops); + + return $this; + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::exif() + */ + public function exif(?string $query = null): mixed + { + return is_null($query) ? $this->exif : $this->exif->get($query); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::modify() + */ + public function modify(ModifierInterface $modifier): ImageInterface + { + return $this->driver->resolve($modifier)->apply($this); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::analyze() + */ + public function analyze(AnalyzerInterface $analyzer): mixed + { + return $this->driver->resolve($analyzer)->analyze($this); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::encode() + */ + public function encode(EncoderInterface $encoder = new AutoEncoder()): EncodedImage + { + return $this->driver->resolve($encoder)->encode($this); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::save() + */ + public function save(?string $path = null, int $quality = 75): ImageInterface + { + $path = is_null($path) ? $this->origin()->filePath() : $path; + + $this->encodeByPath($path, $quality)->save($path); + + return $this; + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::width() + */ + public function width(): int + { + return $this->analyze(new WidthAnalyzer()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::height() + */ + public function height(): int + { + return $this->analyze(new HeightAnalyzer()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::size() + */ + public function size(): SizeInterface + { + return new Rectangle($this->width(), $this->height()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::colorspace() + */ + public function colorspace(): ColorspaceInterface + { + return $this->analyze(new ColorspaceAnalyzer()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::setColorspace() + */ + public function setColorspace(string|ColorspaceInterface $colorspace): ImageInterface + { + return $this->modify(new ColorspaceModifier($colorspace)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::resolution() + */ + public function resolution(): ResolutionInterface + { + return $this->analyze(new ResolutionAnalyzer()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::setResolution() + */ + public function setResolution(float $x, float $y): ImageInterface + { + return $this->modify(new ResolutionModifier($x, $y)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::pickColor() + */ + public function pickColor(int $x, int $y, int $frame_key = 0): ColorInterface + { + return $this->analyze(new PixelColorAnalyzer($x, $y, $frame_key)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::pickColors() + */ + public function pickColors(int $x, int $y): CollectionInterface + { + return $this->analyze(new PixelColorsAnalyzer($x, $y)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::profile() + */ + public function profile(): ProfileInterface + { + return $this->analyze(new ProfileAnalyzer()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::setProfile() + */ + public function setProfile(ProfileInterface $profile): ImageInterface + { + return $this->modify(new ProfileModifier($profile)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::removeProfile() + */ + public function removeProfile(): ImageInterface + { + return $this->modify(new ProfileRemovalModifier()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::reduceColors() + */ + public function reduceColors(int $limit, mixed $background = 'transparent'): ImageInterface + { + return $this->modify(new QuantizeColorsModifier($limit, $background)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::sharpen() + */ + public function sharpen(int $amount = 10): ImageInterface + { + return $this->modify(new SharpenModifier($amount)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::invert() + */ + public function invert(): ImageInterface + { + return $this->modify(new InvertModifier()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::pixelate() + */ + public function pixelate(int $size): ImageInterface + { + return $this->modify(new PixelateModifier($size)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::greyscale() + */ + public function greyscale(): ImageInterface + { + return $this->modify(new GreyscaleModifier()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::brightness() + */ + public function brightness(int $level): ImageInterface + { + return $this->modify(new BrightnessModifier($level)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::contrast() + */ + public function contrast(int $level): ImageInterface + { + return $this->modify(new ContrastModifier($level)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::gamma() + */ + public function gamma(float $gamma): ImageInterface + { + return $this->modify(new GammaModifier($gamma)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::colorize() + */ + public function colorize(int $red = 0, int $green = 0, int $blue = 0): ImageInterface + { + return $this->modify(new ColorizeModifier($red, $green, $blue)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::flip() + */ + public function flip(): ImageInterface + { + return $this->modify(new FlipModifier()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::flop() + */ + public function flop(): ImageInterface + { + return $this->modify(new FlopModifier()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::blur() + */ + public function blur(int $amount = 5): ImageInterface + { + return $this->modify(new BlurModifier($amount)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::rotate() + */ + public function rotate(float $angle, mixed $background = 'ffffff'): ImageInterface + { + return $this->modify(new RotateModifier($angle, $background)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::text() + */ + public function text(string $text, int $x, int $y, callable|FontInterface $font): ImageInterface + { + return $this->modify( + new TextModifier( + $text, + new Point($x, $y), + call_user_func(new FontFactory($font)), + ), + ); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::resize() + */ + public function resize(?int $width = null, ?int $height = null): ImageInterface + { + return $this->modify(new ResizeModifier($width, $height)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::resizeDown() + */ + public function resizeDown(?int $width = null, ?int $height = null): ImageInterface + { + return $this->modify(new ResizeDownModifier($width, $height)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::scale() + */ + public function scale(?int $width = null, ?int $height = null): ImageInterface + { + return $this->modify(new ScaleModifier($width, $height)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::scaleDown() + */ + public function scaleDown(?int $width = null, ?int $height = null): ImageInterface + { + return $this->modify(new ScaleDownModifier($width, $height)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::cover() + */ + public function cover(int $width, int $height, string $position = 'center'): ImageInterface + { + return $this->modify(new CoverModifier($width, $height, $position)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::coverDown() + */ + public function coverDown(int $width, int $height, string $position = 'center'): ImageInterface + { + return $this->modify(new CoverDownModifier($width, $height, $position)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::resizeCanvas() + */ + public function resizeCanvas( + ?int $width = null, + ?int $height = null, + mixed $background = 'ffffff', + string $position = 'center' + ): ImageInterface { + return $this->modify(new ResizeCanvasModifier($width, $height, $background, $position)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::resizeCanvasRelative() + */ + public function resizeCanvasRelative( + ?int $width = null, + ?int $height = null, + mixed $background = 'ffffff', + string $position = 'center' + ): ImageInterface { + return $this->modify(new ResizeCanvasRelativeModifier($width, $height, $background, $position)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::padDown() + */ + public function pad( + int $width, + int $height, + mixed $background = 'ffffff', + string $position = 'center' + ): ImageInterface { + return $this->modify(new PadModifier($width, $height, $background, $position)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::pad() + */ + public function contain( + int $width, + int $height, + mixed $background = 'ffffff', + string $position = 'center' + ): ImageInterface { + return $this->modify(new ContainModifier($width, $height, $background, $position)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::crop() + */ + public function crop( + int $width, + int $height, + int $offset_x = 0, + int $offset_y = 0, + string $position = 'top-left' + ): ImageInterface { + return $this->modify(new CropModifier($width, $height, $offset_x, $offset_y, $position)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::place() + */ + public function place( + mixed $element, + string $position = 'top-left', + int $offset_x = 0, + int $offset_y = 0 + ): ImageInterface { + return $this->modify(new PlaceModifier($element, $position, $offset_x, $offset_y)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::fill() + */ + public function fill(mixed $color, ?int $x = null, ?int $y = null): ImageInterface + { + return $this->modify( + new FillModifier( + $color, + (is_null($x) || is_null($y)) ? null : new Point($x, $y), + ), + ); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::drawPixel() + */ + public function drawPixel(int $x, int $y, mixed $color): ImageInterface + { + return $this->modify(new DrawPixelModifier(new Point($x, $y), $color)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::drawRectangle() + */ + public function drawRectangle(int $x, int $y, callable|Rectangle $init): ImageInterface + { + return $this->modify( + new DrawRectangleModifier( + call_user_func(new RectangleFactory(new Point($x, $y), $init)), + ), + ); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::drawEllipse() + */ + public function drawEllipse(int $x, int $y, callable $init): ImageInterface + { + return $this->modify( + new DrawEllipseModifier( + call_user_func(new EllipseFactory(new Point($x, $y), $init)), + ), + ); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::drawCircle() + */ + public function drawCircle(int $x, int $y, callable $init): ImageInterface + { + return $this->modify( + new DrawEllipseModifier( + call_user_func(new CircleFactory(new Point($x, $y), $init)), + ), + ); + } + + public function drawPolygon(callable $init): ImageInterface + { + return $this->modify( + new DrawPolygonModifier( + call_user_func(new PolygonFactory($init)), + ), + ); + } + + public function drawLine(callable $init): ImageInterface + { + return $this->modify( + new DrawLineModifier( + call_user_func(new LineFactory($init)), + ), + ); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::encodeByMediaType() + */ + public function encodeByMediaType(?string $type = null, int $quality = 75): EncodedImageInterface + { + return $this->encode(new MediaTypeEncoder($type, $quality)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::encodeByExtension() + */ + public function encodeByExtension(?string $extension = null, int $quality = 75): EncodedImageInterface + { + return $this->encode(new FileExtensionEncoder($extension, $quality)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::encodeByPath() + */ + public function encodeByPath(?string $path = null, int $quality = 75): EncodedImageInterface + { + return $this->encode(new FilePathEncoder($path, $quality)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::toJpeg() + */ + public function toJpeg(int $quality = 75): EncodedImageInterface + { + return $this->encode(new JpegEncoder($quality)); + } + + /** + * Alias of self::toJpeg() + * + * @param int $quality + * @return EncodedImageInterface + */ + public function toJpg(int $quality = 75): EncodedImageInterface + { + return $this->toJpeg($quality); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::toJpeg() + */ + public function toJpeg2000(int $quality = 75): EncodedImageInterface + { + return $this->encode(new Jpeg2000Encoder($quality)); + } + + /** + * ALias of self::toJpeg2000() + * + * @param int $quality + * @return EncodedImageInterface + */ + public function toJp2(int $quality = 75): EncodedImageInterface + { + return $this->toJpeg2000($quality); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::toPng() + */ + public function toPng(): EncodedImageInterface + { + return $this->encode(new PngEncoder()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::toGif() + */ + public function toGif(): EncodedImageInterface + { + return $this->encode(new GifEncoder()); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::toWebp() + */ + public function toWebp(int $quality = 75): EncodedImageInterface + { + return $this->encode(new WebpEncoder($quality)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::toBitmap() + */ + public function toBitmap(): EncodedImageInterface + { + return $this->encode(new BmpEncoder()); + } + + /** + * Alias if self::toBitmap() + * + * @return EncodedImageInterface + */ + public function toBmp(): EncodedImageInterface + { + return $this->toBitmap(); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::toAvif() + */ + public function toAvif(int $quality = 75): EncodedImageInterface + { + return $this->encode(new AvifEncoder($quality)); + } + + /** + * {@inheritdoc} + * + * @see ImageInterface::toTiff() + */ + public function toTiff(int $quality = 75): EncodedImageInterface + { + return $this->encode(new TiffEncoder($quality)); + } + + /** + * Alias of self::toTiff() + * + * @param int $quality + * @return EncodedImageInterface + */ + public function toTif(int $quality = 75): EncodedImageInterface + { + return $this->toTiff($quality); + } + + /** + * Clone image + * + * @return void + */ + public function __clone(): void + { + $this->driver = clone $this->driver; + $this->core = clone $this->core; + $this->exif = clone $this->exif; + } +}