From cf0291f9c1174b112199c5ac8cbc4a4a6faef3f7 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Mon, 6 May 2024 16:38:11 +0200 Subject: [PATCH 01/25] Refactor InputHandler logic --- src/Colors/Cmyk/Color.php | 8 +- src/Colors/Hsl/Color.php | 8 +- src/Colors/Hsv/Color.php | 8 +- src/Colors/Rgb/Color.php | 8 +- src/Decoders/ColorObjectDecoder.php | 11 ++ src/Drivers/AbstractDecoder.php | 40 ----- src/Drivers/AbstractDriver.php | 13 ++ src/Drivers/AbstractInputHandler.php | 71 -------- .../Gd/Decoders/ColorObjectDecoder.php | 1 - .../Gd/Decoders/ImageObjectDecoder.php | 1 - src/Drivers/Gd/Driver.php | 11 -- src/Drivers/Gd/InputHandler.php | 50 ------ .../Imagick/Decoders/ColorObjectDecoder.php | 4 +- src/Drivers/Imagick/Driver.php | 11 -- src/Drivers/Imagick/InputHandler.php | 50 ------ src/InputHandler.php | 112 +++++++++++++ tests/GdTestCase.php | 2 +- tests/ImagickTestCase.php | 2 +- tests/Unit/Drivers/AbstractDecoderTest.php | 30 ---- .../Unit/Drivers/AbstractInputHandlerTest.php | 55 ------ tests/Unit/Drivers/Gd/InputHandlerTest.php | 153 ----------------- .../Unit/Drivers/Imagick/InputHandlerTest.php | 157 ------------------ 22 files changed, 152 insertions(+), 654 deletions(-) create mode 100644 src/Decoders/ColorObjectDecoder.php delete mode 100644 src/Drivers/AbstractInputHandler.php delete mode 100644 src/Drivers/Gd/InputHandler.php delete mode 100644 src/Drivers/Imagick/InputHandler.php create mode 100644 src/InputHandler.php delete mode 100644 tests/Unit/Drivers/AbstractInputHandlerTest.php delete mode 100644 tests/Unit/Drivers/Gd/InputHandlerTest.php delete mode 100644 tests/Unit/Drivers/Imagick/InputHandlerTest.php diff --git a/src/Colors/Cmyk/Color.php b/src/Colors/Cmyk/Color.php index 68c6e0db..dabacbbf 100644 --- a/src/Colors/Cmyk/Color.php +++ b/src/Colors/Cmyk/Color.php @@ -10,7 +10,7 @@ use Intervention\Image\Colors\Cmyk\Channels\Magenta; use Intervention\Image\Colors\Cmyk\Channels\Yellow; use Intervention\Image\Colors\Cmyk\Channels\Key; use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace; -use Intervention\Image\Drivers\AbstractInputHandler; +use Intervention\Image\InputHandler; use Intervention\Image\Interfaces\ColorChannelInterface; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; @@ -44,11 +44,9 @@ class Color extends AbstractColor */ public static function create(mixed $input): ColorInterface { - return (new class ([ + return (new InputHandler([ Decoders\StringColorDecoder::class, - ]) extends AbstractInputHandler - { - })->handle($input); + ]))->handle($input); } /** diff --git a/src/Colors/Hsl/Color.php b/src/Colors/Hsl/Color.php index 3cc1d433..2871963f 100644 --- a/src/Colors/Hsl/Color.php +++ b/src/Colors/Hsl/Color.php @@ -9,7 +9,7 @@ use Intervention\Image\Colors\Hsl\Channels\Hue; use Intervention\Image\Colors\Hsl\Channels\Luminance; use Intervention\Image\Colors\Hsl\Channels\Saturation; use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace; -use Intervention\Image\Drivers\AbstractInputHandler; +use Intervention\Image\InputHandler; use Intervention\Image\Interfaces\ColorChannelInterface; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; @@ -43,11 +43,9 @@ class Color extends AbstractColor */ public static function create(mixed $input): ColorInterface { - return (new class ([ + return (new InputHandler([ Decoders\StringColorDecoder::class, - ]) extends AbstractInputHandler - { - })->handle($input); + ]))->handle($input); } /** diff --git a/src/Colors/Hsv/Color.php b/src/Colors/Hsv/Color.php index e09a49b8..d4580149 100644 --- a/src/Colors/Hsv/Color.php +++ b/src/Colors/Hsv/Color.php @@ -9,7 +9,7 @@ use Intervention\Image\Colors\Hsv\Channels\Hue; use Intervention\Image\Colors\Hsv\Channels\Saturation; use Intervention\Image\Colors\Hsv\Channels\Value; use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace; -use Intervention\Image\Drivers\AbstractInputHandler; +use Intervention\Image\InputHandler; use Intervention\Image\Interfaces\ColorChannelInterface; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; @@ -43,11 +43,9 @@ class Color extends AbstractColor */ public static function create(mixed $input): ColorInterface { - return (new class ([ + return (new InputHandler([ Decoders\StringColorDecoder::class, - ]) extends AbstractInputHandler - { - })->handle($input); + ]))->handle($input); } /** diff --git a/src/Colors/Rgb/Color.php b/src/Colors/Rgb/Color.php index 4473987f..40d831ac 100644 --- a/src/Colors/Rgb/Color.php +++ b/src/Colors/Rgb/Color.php @@ -9,7 +9,7 @@ use Intervention\Image\Colors\Rgb\Channels\Blue; use Intervention\Image\Colors\Rgb\Channels\Green; use Intervention\Image\Colors\Rgb\Channels\Red; use Intervention\Image\Colors\Rgb\Channels\Alpha; -use Intervention\Image\Drivers\AbstractInputHandler; +use Intervention\Image\InputHandler; use Intervention\Image\Interfaces\ColorChannelInterface; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; @@ -53,14 +53,12 @@ class Color extends AbstractColor */ public static function create(mixed $input): ColorInterface { - return (new class ([ + return (new InputHandler([ Decoders\HexColorDecoder::class, Decoders\StringColorDecoder::class, Decoders\TransparentColorDecoder::class, Decoders\HtmlColornameDecoder::class, - ]) extends AbstractInputHandler - { - })->handle($input); + ]))->handle($input); } /** diff --git a/src/Decoders/ColorObjectDecoder.php b/src/Decoders/ColorObjectDecoder.php new file mode 100644 index 00000000..f81f7170 --- /dev/null +++ b/src/Decoders/ColorObjectDecoder.php @@ -0,0 +1,11 @@ +decode($input); - } catch (DecoderException $e) { - if (!$this->hasSuccessor()) { - throw new DecoderException($e->getMessage()); - } - - return $this->successor->handle($input); - } - - return $decoded; - } - - /** - * Determine if current decoder has a successor - * - * @return bool - */ - protected function hasSuccessor(): bool - { - return $this->successor !== null; - } - /** * Determine if the given input is GIF data format * diff --git a/src/Drivers/AbstractDriver.php b/src/Drivers/AbstractDriver.php index bbef4608..62c76817 100644 --- a/src/Drivers/AbstractDriver.php +++ b/src/Drivers/AbstractDriver.php @@ -6,10 +6,13 @@ namespace Intervention\Image\Drivers; use Intervention\Image\Exceptions\DriverException; use Intervention\Image\Exceptions\NotSupportedException; +use Intervention\Image\InputHandler; use Intervention\Image\Interfaces\AnalyzerInterface; +use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\DecoderInterface; use Intervention\Image\Interfaces\DriverInterface; use Intervention\Image\Interfaces\EncoderInterface; +use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ModifierInterface; use Intervention\Image\Interfaces\SpecializableInterface; use Intervention\Image\Interfaces\SpecializedInterface; @@ -25,6 +28,16 @@ abstract class AbstractDriver implements DriverInterface $this->checkHealth(); } + /** + * {@inheritdoc} + * + * @see DriverInterface::handleInput() + */ + public function handleInput(mixed $input, array $decoders = []): ImageInterface|ColorInterface + { + return (new InputHandler($decoders, $this))->handle($input); + } + /** * {@inheritdoc} * diff --git a/src/Drivers/AbstractInputHandler.php b/src/Drivers/AbstractInputHandler.php deleted file mode 100644 index bb079546..00000000 --- a/src/Drivers/AbstractInputHandler.php +++ /dev/null @@ -1,71 +0,0 @@ - - */ - protected array $decoders = []; - - /** - * Create new input handler instance with given decoder classnames - * - * @param array $decoders - * @return void - */ - public function __construct(array $decoders = []) - { - $this->decoders = count($decoders) ? $decoders : $this->decoders; - } - - /** - * {@inheritdoc} - * - * @see InputHandlerInterface::handle() - */ - public function handle($input): ImageInterface|ColorInterface - { - return $this->chain()->handle($input); - } - - /** - * Stack the decoder array into a nested decoder object - * - * @throws DecoderException - * @return AbstractDecoder - */ - protected function chain(): AbstractDecoder - { - if (count($this->decoders) == 0) { - throw new DecoderException('No decoders found in ' . $this::class); - } - - // get last decoder in stack - list($decoder) = array_slice(array_reverse($this->decoders), 0, 1); - $chain = ($decoder instanceof DecoderInterface) ? $decoder : new $decoder(); - - // only accept DecoderInterface - if (!($chain instanceof DecoderInterface)) { - throw new DecoderException('Decoder must implement in ' . DecoderInterface::class); - } - - // build decoder chain - foreach (array_slice(array_reverse($this->decoders), 1) as $decoder) { - $chain = ($decoder instanceof DecoderInterface) ? new ($decoder::class)($chain) : new $decoder($chain); - } - - return $chain; - } -} diff --git a/src/Drivers/Gd/Decoders/ColorObjectDecoder.php b/src/Drivers/Gd/Decoders/ColorObjectDecoder.php index b1096aa6..51abb3c0 100644 --- a/src/Drivers/Gd/Decoders/ColorObjectDecoder.php +++ b/src/Drivers/Gd/Decoders/ColorObjectDecoder.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Gd\Decoders; -use Intervention\Image\Drivers\AbstractDecoder; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\DecoderInterface; diff --git a/src/Drivers/Gd/Decoders/ImageObjectDecoder.php b/src/Drivers/Gd/Decoders/ImageObjectDecoder.php index aa5e9eb1..61b92674 100644 --- a/src/Drivers/Gd/Decoders/ImageObjectDecoder.php +++ b/src/Drivers/Gd/Decoders/ImageObjectDecoder.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Gd\Decoders; -use Intervention\Image\Drivers\AbstractDecoder; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\DecoderInterface; diff --git a/src/Drivers/Gd/Driver.php b/src/Drivers/Gd/Driver.php index 090faebd..0542d220 100644 --- a/src/Drivers/Gd/Driver.php +++ b/src/Drivers/Gd/Driver.php @@ -11,7 +11,6 @@ use Intervention\Image\Exceptions\RuntimeException; use Intervention\Image\Format; use Intervention\Image\FileExtension; use Intervention\Image\Image; -use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorProcessorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; use Intervention\Image\Interfaces\DriverInterface; @@ -113,16 +112,6 @@ class Driver extends AbstractDriver return call_user_func($animation); } - /** - * {@inheritdoc} - * - * @see DriverInterface::handleInput() - */ - public function handleInput(mixed $input, array $decoders = []): ImageInterface|ColorInterface - { - return (new InputHandler($this->specializeMultiple($decoders)))->handle($input); - } - /** * {@inheritdoc} * diff --git a/src/Drivers/Gd/InputHandler.php b/src/Drivers/Gd/InputHandler.php deleted file mode 100644 index d1628935..00000000 --- a/src/Drivers/Gd/InputHandler.php +++ /dev/null @@ -1,50 +0,0 @@ - - */ - protected array $decoders = [ - NativeObjectDecoder::class, - ImageObjectDecoder::class, - ColorObjectDecoder::class, - RgbHexColorDecoder::class, - RgbStringColorDecoder::class, - CmykStringColorDecoder::class, - HsvStringColorDecoder::class, - HslStringColorDecoder::class, - TransparentColorDecoder::class, - HtmlColornameDecoder::class, - FilePointerImageDecoder::class, - FilePathImageDecoder::class, - SplFileInfoImageDecoder::class, - BinaryImageDecoder::class, - DataUriImageDecoder::class, - Base64ImageDecoder::class, - ]; -} diff --git a/src/Drivers/Imagick/Decoders/ColorObjectDecoder.php b/src/Drivers/Imagick/Decoders/ColorObjectDecoder.php index 138046b5..7938c823 100644 --- a/src/Drivers/Imagick/Decoders/ColorObjectDecoder.php +++ b/src/Drivers/Imagick/Decoders/ColorObjectDecoder.php @@ -4,12 +4,12 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Imagick\Decoders; -use Intervention\Image\Drivers\AbstractDecoder; +use Intervention\Image\Drivers\SpecializableDecoder; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ImageInterface; -class ColorObjectDecoder extends AbstractDecoder +class ColorObjectDecoder extends SpecializableDecoder { public function decode(mixed $input): ImageInterface|ColorInterface { diff --git a/src/Drivers/Imagick/Driver.php b/src/Drivers/Imagick/Driver.php index 2c01f83b..fbd2f2d6 100644 --- a/src/Drivers/Imagick/Driver.php +++ b/src/Drivers/Imagick/Driver.php @@ -13,7 +13,6 @@ use Intervention\Image\Exceptions\RuntimeException; use Intervention\Image\Format; use Intervention\Image\FileExtension; use Intervention\Image\Image; -use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorProcessorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; use Intervention\Image\Interfaces\DriverInterface; @@ -116,16 +115,6 @@ class Driver extends AbstractDriver return call_user_func($animation); } - /** - * {@inheritdoc} - * - * @see DriverInterface::handleInput() - */ - public function handleInput(mixed $input, array $decoders = []): ImageInterface|ColorInterface - { - return (new InputHandler($this->specializeMultiple($decoders)))->handle($input); - } - /** * {@inheritdoc} * diff --git a/src/Drivers/Imagick/InputHandler.php b/src/Drivers/Imagick/InputHandler.php deleted file mode 100644 index 5c7b748b..00000000 --- a/src/Drivers/Imagick/InputHandler.php +++ /dev/null @@ -1,50 +0,0 @@ - - */ - protected array $decoders = [ - NativeObjectDecoder::class, - ImageObjectDecoder::class, - ColorObjectDecoder::class, - RgbHexColorDecoder::class, - RgbStringColorDecoder::class, - CmykStringColorDecoder::class, - HsvStringColorDecoder::class, - HslStringColorDecoder::class, - TransparentColorDecoder::class, - HtmlColornameDecoder::class, - FilePointerImageDecoder::class, - FilePathImageDecoder::class, - SplFileInfoImageDecoder::class, - BinaryImageDecoder::class, - DataUriImageDecoder::class, - Base64ImageDecoder::class, - ]; -} diff --git a/src/InputHandler.php b/src/InputHandler.php new file mode 100644 index 00000000..9e19bc26 --- /dev/null +++ b/src/InputHandler.php @@ -0,0 +1,112 @@ + + */ + protected array $decoders = [ + // NativeObjectDecoder::class, + ImageObjectDecoder::class, + ColorObjectDecoder::class, + RgbHexColorDecoder::class, + RgbStringColorDecoder::class, + CmykStringColorDecoder::class, + HsvStringColorDecoder::class, + HslStringColorDecoder::class, + TransparentColorDecoder::class, + HtmlColornameDecoder::class, + FilePointerImageDecoder::class, + FilePathImageDecoder::class, + SplFileInfoImageDecoder::class, + BinaryImageDecoder::class, + DataUriImageDecoder::class, + Base64ImageDecoder::class, + ]; + + /** + * Driver with which the decoder classes are specialized + * + * @var null|DriverInterface + */ + protected ?DriverInterface $driver = null; + + /** + * Create new input handler instance with given decoder classnames + * + * @param DriverInterface $driver + * @param array $decoders + * @return void + */ + public function __construct(array $decoders = [], ?DriverInterface $driver = null) + { + $this->decoders = count($decoders) ? $decoders : $this->decoders; + $this->driver = $driver; + } + + /** + * {@inheritdoc} + * + * @see InputHandlerInterface::handle() + */ + public function handle($input): ImageInterface|ColorInterface + { + foreach ($this->decoders as $decoderClassname) { + // resolve river specialized decoder + $decoder = $this->resolve($decoderClassname); + + try { + return $decoder->decode($input); + } catch (DecoderException $e) { + // let next decoder try + } + } + + throw new DecoderException(isset($e) ? $e->getMessage() : ''); + } + + /** + * Resolve the given classname to an decoder object + * + * @param string|DecoderInterface $decoder + * @return DecoderInterface + * @throws NotSupportedException + */ + private function resolve(string|DecoderInterface $decoder): DecoderInterface + { + if (empty($this->driver)) { + return new $decoder(); + } + + return $this->driver->specialize(new $decoder()); + } +} diff --git a/tests/GdTestCase.php b/tests/GdTestCase.php index b4ea3f49..6afd44f9 100644 --- a/tests/GdTestCase.php +++ b/tests/GdTestCase.php @@ -14,7 +14,7 @@ abstract class GdTestCase extends BaseTestCase { public function readTestImage($filename = 'test.jpg'): Image { - return (new FilePathImageDecoder())->handle( + return (new FilePathImageDecoder())->decode( $this->getTestResourcePath($filename) ); } diff --git a/tests/ImagickTestCase.php b/tests/ImagickTestCase.php index c312b5a4..ef23695c 100644 --- a/tests/ImagickTestCase.php +++ b/tests/ImagickTestCase.php @@ -15,7 +15,7 @@ abstract class ImagickTestCase extends BaseTestCase { public function readTestImage($filename = 'test.jpg'): Image { - return (new FilePathImageDecoder())->handle( + return (new FilePathImageDecoder())->decode( $this->getTestResourcePath($filename) ); } diff --git a/tests/Unit/Drivers/AbstractDecoderTest.php b/tests/Unit/Drivers/AbstractDecoderTest.php index e156db8f..2a9ff77f 100644 --- a/tests/Unit/Drivers/AbstractDecoderTest.php +++ b/tests/Unit/Drivers/AbstractDecoderTest.php @@ -7,7 +7,6 @@ namespace Intervention\Image\Tests\Unit\Drivers; use PHPUnit\Framework\Attributes\CoversClass; use Exception; use Intervention\Image\Drivers\AbstractDecoder; -use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Interfaces\CollectionInterface; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ImageInterface; @@ -18,35 +17,6 @@ use stdClass; #[CoversClass(\Intervention\Image\Drivers\AbstractDecoder::class)] final class AbstractDecoderTest extends BaseTestCase { - public function testHandle(): void - { - $result = Mockery::mock(ColorInterface::class); - $decoder = Mockery::mock(AbstractDecoder::class); - $decoder->shouldReceive('decode')->with('test input')->andReturn($result); - $decoder->handle('test input'); - } - - public function testHandleFail(): void - { - $decoder = Mockery::mock(AbstractDecoder::class, []); - $decoder->shouldReceive('decode')->with('test input')->andThrow(DecoderException::class); - $this->expectException(DecoderException::class); - $decoder->handle('test input'); - } - - public function testHandleFailWithSuccessor(): void - { - $result = Mockery::mock(ColorInterface::class); - $successor = Mockery::mock(AbstractDecoder::class); - $successor->shouldReceive('decode')->with('test input')->andReturn($result); - $decoder = Mockery::mock( - AbstractDecoder::class, - [$successor] - ); - $decoder->shouldReceive('decode')->with('test input')->andThrow(DecoderException::class); - $decoder->handle('test input'); - } - public function testIsGifFormat(): void { $decoder = Mockery::mock(AbstractDecoder::class); diff --git a/tests/Unit/Drivers/AbstractInputHandlerTest.php b/tests/Unit/Drivers/AbstractInputHandlerTest.php deleted file mode 100644 index 0e0e1929..00000000 --- a/tests/Unit/Drivers/AbstractInputHandlerTest.php +++ /dev/null @@ -1,55 +0,0 @@ -shouldReceive('handle')->with('test image')->andReturn($image); - $chain->shouldReceive('decode')->with('test image')->andReturn(Mockery::mock(ImageInterface::class)); - - $modifier = $this->getModifier($chain); - $modifier->handle('test image'); - } - - public function testChainNoItems(): void - { - $handler = new class () extends AbstractInputHandler - { - }; - - $this->expectException(DecoderException::class); - $handler->handle('test'); - } - - private function getModifier(AbstractDecoder $chain): AbstractInputHandler - { - return new class ([$chain]) extends AbstractInputHandler - { - public function __construct(protected array $decoders = []) - { - // - } - - protected function chain(): AbstractDecoder - { - return $this->decoders[0]; - } - }; - } -} diff --git a/tests/Unit/Drivers/Gd/InputHandlerTest.php b/tests/Unit/Drivers/Gd/InputHandlerTest.php deleted file mode 100644 index f2e2e475..00000000 --- a/tests/Unit/Drivers/Gd/InputHandlerTest.php +++ /dev/null @@ -1,153 +0,0 @@ -expectException(DecoderException::class); - $handler->handle(''); - } - - public function testHandleBinaryImage(): void - { - $handler = new InputHandler(); - $input = file_get_contents($this->getTestResourcePath('test.jpg')); - $result = $handler->handle($input); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleGdImage(): void - { - $handler = new InputHandler(); - $result = $handler->handle(imagecreatetruecolor(3, 2)); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleSplFileInfo(): void - { - $handler = new InputHandler(); - $input = new SplFileInfo($this->getTestResourcePath('test.jpg')); - $result = $handler->handle($input); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleFilePathImage(): void - { - $handler = new InputHandler(); - $input = $this->getTestResourcePath('animation.gif'); - $result = $handler->handle($input); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleBase64Image(): void - { - $handler = new InputHandler(); - $input = base64_encode(file_get_contents($this->getTestResourcePath('animation.gif'))); - $result = $handler->handle($input); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleDataUriImage(): void - { - $handler = new InputHandler(); - $input = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNb' . - 'yblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='; - $result = $handler->handle($input); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleHexColor(): void - { - $handler = new InputHandler(); - $input = 'ccff33'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([204, 255, 51, 255], $result->toArray()); - - $handler = new InputHandler(); - $input = 'cf3'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([204, 255, 51, 255], $result->toArray()); - - $handler = new InputHandler(); - $input = '#123456'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([18, 52, 86, 255], $result->toArray()); - - $handler = new InputHandler(); - $input = '#333'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([51, 51, 51, 255], $result->toArray()); - - $handler = new InputHandler(); - $input = '#3333'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([51, 51, 51, 51], $result->toArray()); - - $handler = new InputHandler(); - $input = '#33333333'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([51, 51, 51, 51], $result->toArray()); - } - - public function testHandleRgbString(): void - { - $handler = new InputHandler(); - $result = $handler->handle('rgb(10, 20, 30)'); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([10, 20, 30, 255], $result->toArray()); - - $handler = new InputHandler(); - $result = $handler->handle('rgba(10, 20, 30, 1.0)'); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([10, 20, 30, 255], $result->toArray()); - } - - public function testHandleHsvString(): void - { - $handler = new InputHandler(); - $result = $handler->handle('hsv(10, 20, 30)'); - $this->assertInstanceOf(HsvColor::class, $result); - $this->assertEquals([10, 20, 30], $result->toArray()); - } - - public function testHandleCmykString(): void - { - $handler = new InputHandler(); - $result = $handler->handle('cmyk(10, 20, 30, 40)'); - $this->assertInstanceOf(CmykColor::class, $result); - $this->assertEquals([10, 20, 30, 40], $result->toArray()); - } - - public function testHandleTransparent(): void - { - $handler = new InputHandler(); - $input = 'transparent'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([255, 255, 255, 0], $result->toArray()); - } -} diff --git a/tests/Unit/Drivers/Imagick/InputHandlerTest.php b/tests/Unit/Drivers/Imagick/InputHandlerTest.php deleted file mode 100644 index 1b4fbf39..00000000 --- a/tests/Unit/Drivers/Imagick/InputHandlerTest.php +++ /dev/null @@ -1,157 +0,0 @@ -expectException(DecoderException::class); - $handler->handle(''); - } - - public function testHandleBinaryImage(): void - { - $handler = new InputHandler(); - $input = file_get_contents($this->getTestResourcePath('animation.gif')); - $result = $handler->handle($input); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleImagick(): void - { - $imagick = new Imagick(); - $imagick->newImage(3, 2, new ImagickPixel('rgba(255, 255, 255, 255)'), 'png'); - $handler = new InputHandler(); - $result = $handler->handle($imagick); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleSplFileInfo(): void - { - $handler = new InputHandler(); - $input = new SplFileInfo($this->getTestResourcePath('test.jpg')); - $result = $handler->handle($input); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleFilePathImage(): void - { - $handler = new InputHandler(); - $input = $this->getTestResourcePath('animation.gif'); - $result = $handler->handle($input); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleBase64Image(): void - { - $handler = new InputHandler(); - $input = base64_encode(file_get_contents($this->getTestResourcePath('animation.gif'))); - $result = $handler->handle($input); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleDataUriImage(): void - { - $handler = new InputHandler(); - $input = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACN' . - 'byblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='; - $result = $handler->handle($input); - $this->assertInstanceOf(Image::class, $result); - } - - public function testHandleHexColor(): void - { - $handler = new InputHandler(); - $input = 'ccff33'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([204, 255, 51, 255], $result->toArray()); - - $handler = new InputHandler(); - $input = 'cf3'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([204, 255, 51, 255], $result->toArray()); - - $handler = new InputHandler(); - $input = '#123456'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([18, 52, 86, 255], $result->toArray()); - - $handler = new InputHandler(); - $input = '#333'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([51, 51, 51, 255], $result->toArray()); - - $handler = new InputHandler(); - $input = '#3333'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([51, 51, 51, 51], $result->toArray()); - - $handler = new InputHandler(); - $input = '#33333333'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([51, 51, 51, 51], $result->toArray()); - } - - public function testHandleRgbString(): void - { - $handler = new InputHandler(); - $result = $handler->handle('rgb(10, 20, 30)'); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([10, 20, 30, 255], $result->toArray()); - - $handler = new InputHandler(); - $result = $handler->handle('rgba(10, 20, 30, 1.0)'); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([10, 20, 30, 255], $result->toArray()); - } - - public function testHandleCmykString(): void - { - $handler = new InputHandler(); - $result = $handler->handle('cmyk(10, 20, 30, 40)'); - $this->assertInstanceOf(CmykColor::class, $result); - $this->assertEquals([10, 20, 30, 40], $result->toArray()); - } - - public function testHandleHsvString(): void - { - $handler = new InputHandler(); - $result = $handler->handle('hsv(10, 20, 30)'); - $this->assertInstanceOf(HsvColor::class, $result); - $this->assertEquals([10, 20, 30], $result->toArray()); - } - - public function testHandleTransparent(): void - { - $handler = new InputHandler(); - $input = 'transparent'; - $result = $handler->handle($input); - $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals([255, 255, 255, 0], $result->toArray()); - } -} From 93fcd102871e365384d6165f8a07421e99f7c21a Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Mon, 6 May 2024 17:28:12 +0200 Subject: [PATCH 02/25] Add configuration object --- src/Config.php | 32 ++++++++++++++++++++++++++++++ src/Drivers/AbstractDriver.php | 16 ++++++++++++++- src/ImageManager.php | 10 ++++++---- src/Interfaces/ConfigInterface.php | 12 +++++++++++ src/Interfaces/DriverInterface.php | 7 +++++++ 5 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 src/Config.php create mode 100644 src/Interfaces/ConfigInterface.php diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 00000000..aec6d346 --- /dev/null +++ b/src/Config.php @@ -0,0 +1,32 @@ +decodeAnimation; + } + + public function autoOrientate(): bool + { + return $this->autoOrientate; + } + + public function blendingColor(): mixed + { + return $this->blendingColor; + } +} diff --git a/src/Drivers/AbstractDriver.php b/src/Drivers/AbstractDriver.php index 62c76817..abfe9bf0 100644 --- a/src/Drivers/AbstractDriver.php +++ b/src/Drivers/AbstractDriver.php @@ -4,11 +4,13 @@ declare(strict_types=1); namespace Intervention\Image\Drivers; +use Intervention\Image\Config; use Intervention\Image\Exceptions\DriverException; use Intervention\Image\Exceptions\NotSupportedException; use Intervention\Image\InputHandler; use Intervention\Image\Interfaces\AnalyzerInterface; use Intervention\Image\Interfaces\ColorInterface; +use Intervention\Image\Interfaces\ConfigInterface; use Intervention\Image\Interfaces\DecoderInterface; use Intervention\Image\Interfaces\DriverInterface; use Intervention\Image\Interfaces\EncoderInterface; @@ -21,13 +23,25 @@ use ReflectionClass; abstract class AbstractDriver implements DriverInterface { /** + * @param ConfigInterface $config * @throws DriverException + * @return void */ - public function __construct() + public function __construct(protected ConfigInterface $config = new Config()) { $this->checkHealth(); } + /** + * {@inheritdoc} + * + * @see DriverInterface::config() + */ + public function config(): ConfigInterface + { + return $this->config; + } + /** * {@inheritdoc} * diff --git a/src/ImageManager.php b/src/ImageManager.php index 383dea5f..4f8ca7c1 100644 --- a/src/ImageManager.php +++ b/src/ImageManager.php @@ -40,22 +40,24 @@ final class ImageManager implements ImageManagerInterface * Create image manager with GD driver * * @link https://image.intervention.io/v3/basics/image-manager#static-gd-driver-constructor + * @param mixed $options * @return ImageManager */ - public static function gd(): self + public static function gd(mixed ...$options): self { - return self::withDriver(GdDriver::class); + return self::withDriver(new GdDriver(new Config(...$options))); } /** * Create image manager with Imagick driver * * @link https://image.intervention.io/v3/basics/image-manager#static-imagick-driver-constructor + * @param mixed $options * @return ImageManager */ - public static function imagick(): self + public static function imagick(mixed ...$options): self { - return self::withDriver(ImagickDriver::class); + return self::withDriver(new ImagickDriver(new Config(...$options))); } /** diff --git a/src/Interfaces/ConfigInterface.php b/src/Interfaces/ConfigInterface.php new file mode 100644 index 00000000..27ceda9c --- /dev/null +++ b/src/Interfaces/ConfigInterface.php @@ -0,0 +1,12 @@ + Date: Thu, 9 May 2024 09:15:44 +0200 Subject: [PATCH 03/25] Add tests for NativeObjectDecoders --- .../Gd/Decoders/NativeObjectDecoderTest.php | 34 ++++++++++++++++++ .../Decoders/NativeObjectDecoderTest.php | 36 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 tests/Unit/Drivers/Gd/Decoders/NativeObjectDecoderTest.php create mode 100644 tests/Unit/Drivers/Imagick/Decoders/NativeObjectDecoderTest.php diff --git a/tests/Unit/Drivers/Gd/Decoders/NativeObjectDecoderTest.php b/tests/Unit/Drivers/Gd/Decoders/NativeObjectDecoderTest.php new file mode 100644 index 00000000..0b77f263 --- /dev/null +++ b/tests/Unit/Drivers/Gd/Decoders/NativeObjectDecoderTest.php @@ -0,0 +1,34 @@ +decoder = new NativeObjectDecoder(); + $this->decoder->setDriver(new Driver()); + } + + public function testDecode(): void + { + $result = $this->decoder->decode( + imagecreatetruecolor(3, 2) + ); + + $this->assertInstanceOf(Image::class, $result); + } +} diff --git a/tests/Unit/Drivers/Imagick/Decoders/NativeObjectDecoderTest.php b/tests/Unit/Drivers/Imagick/Decoders/NativeObjectDecoderTest.php new file mode 100644 index 00000000..e397c36e --- /dev/null +++ b/tests/Unit/Drivers/Imagick/Decoders/NativeObjectDecoderTest.php @@ -0,0 +1,36 @@ +decoder = new NativeObjectDecoder(); + $this->decoder->setDriver(new Driver()); + } + + public function testDecode(): void + { + $native = new Imagick(); + $native->newImage(3, 2, new ImagickPixel('red'), 'png'); + $result = $this->decoder->decode($native); + + $this->assertInstanceOf(Image::class, $result); + } +} From fa2bf7d79384b753b2c08965e3629570f5029b5e Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Thu, 9 May 2024 09:58:10 +0200 Subject: [PATCH 04/25] Implement config options --- src/Config.php | 29 +++++++-- src/Drivers/AbstractDriver.php | 8 ++- .../Gd/Decoders/BinaryImageDecoder.php | 7 +- .../Gd/Decoders/FilePathImageDecoder.php | 7 +- .../Gd/Decoders/NativeObjectDecoder.php | 65 ++++++++++++++++++- .../Gd/Decoders/Traits/CanDecodeGif.php | 54 --------------- .../Imagick/Decoders/NativeObjectDecoder.php | 14 +++- src/Image.php | 31 +++++---- src/ImageManager.php | 3 + src/InputHandler.php | 19 ++++-- src/Interfaces/ConfigInterface.php | 6 +- src/Interfaces/DriverInterface.php | 1 + src/Interfaces/ImageInterface.php | 9 +++ src/Interfaces/SpecializableInterface.php | 3 + src/Traits/CanBeDriverSpecialized.php | 23 +++++++ tests/BaseTestCase.php | 8 +-- tests/GdTestCase.php | 4 +- tests/ImagickTestCase.php | 4 +- .../Gd/Decoders/Base64ImageDecoderTest.php | 2 + .../Gd/Decoders/BinaryImageDecoderTest.php | 21 +++--- .../Gd/Decoders/DataUriImageDecoderTest.php | 2 + .../Gd/Decoders/FilePathImageDecoderTest.php | 46 +++++++------ .../Decoders/FilePointerImageDecoderTest.php | 3 + .../Decoders/SplFileInfoImageDecoderTest.php | 3 + .../Drivers/Gd/Traits/CanDecodeGifTest.php | 57 ---------------- .../Decoders/Base64ImageDecoderTest.php | 2 + .../Decoders/BinaryImageDecoderTest.php | 27 ++++---- .../Decoders/DataUriImageDecoderTest.php | 2 + .../Decoders/FilePathImageDecoderTest.php | 46 +++++++------ .../Decoders/FilePointerImageDecoderTest.php | 2 + .../Decoders/SplFileInfoImageDecoderTest.php | 2 + 31 files changed, 289 insertions(+), 221 deletions(-) delete mode 100644 src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php delete mode 100644 tests/Unit/Drivers/Gd/Traits/CanDecodeGifTest.php diff --git a/src/Config.php b/src/Config.php index aec6d346..10c1a685 100644 --- a/src/Config.php +++ b/src/Config.php @@ -9,9 +9,9 @@ use Intervention\Image\Interfaces\ConfigInterface; class Config implements ConfigInterface { public function __construct( - protected bool $autoOrientate = true, + protected bool $autoOrientation = true, protected bool $decodeAnimation = true, - protected mixed $blendingColor = 'ffffffff', + protected mixed $blendingColor = 'ffffff00', ) { } @@ -20,13 +20,34 @@ class Config implements ConfigInterface return $this->decodeAnimation; } - public function autoOrientate(): bool + public function setDecodeAnimation(bool $status): self { - return $this->autoOrientate; + $this->decodeAnimation = $status; + + return $this; + } + + public function autoOrientation(): bool + { + return $this->autoOrientation; + } + + public function setAutoOrientation(bool $status): self + { + $this->autoOrientation = $status; + + return $this; } public function blendingColor(): mixed { return $this->blendingColor; } + + public function setBlendingColor(mixed $color): self + { + $this->blendingColor = $color; + + return $this; + } } diff --git a/src/Drivers/AbstractDriver.php b/src/Drivers/AbstractDriver.php index abfe9bf0..b2db442e 100644 --- a/src/Drivers/AbstractDriver.php +++ b/src/Drivers/AbstractDriver.php @@ -65,8 +65,10 @@ abstract class AbstractDriver implements DriverInterface return $object; } - // return directly if object is already specialized - if ($object instanceof SpecializedInterface) { + // return directly and only attach driver if object is already specialized + if (($object instanceof SpecializedInterface)) { + $object->setDriver($this); + return $object; } @@ -81,7 +83,7 @@ abstract class AbstractDriver implements DriverInterface ); } - // create driver specialized object with specializable properties of generic object + // create a driver specialized object with the specializable properties of generic object $specialized = (new $specialized_classname(...$object->specializable())); // attach driver diff --git a/src/Drivers/Gd/Decoders/BinaryImageDecoder.php b/src/Drivers/Gd/Decoders/BinaryImageDecoder.php index c71b3232..c4f43749 100644 --- a/src/Drivers/Gd/Decoders/BinaryImageDecoder.php +++ b/src/Drivers/Gd/Decoders/BinaryImageDecoder.php @@ -8,14 +8,11 @@ use Intervention\Image\Exceptions\RuntimeException; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\DecoderInterface; use Intervention\Image\Interfaces\ImageInterface; -use Intervention\Image\Drivers\Gd\Decoders\Traits\CanDecodeGif; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Modifiers\AlignRotationModifier; class BinaryImageDecoder extends NativeObjectDecoder implements DecoderInterface { - use CanDecodeGif; - /** * {@inheritdoc} * @@ -63,7 +60,9 @@ class BinaryImageDecoder extends NativeObjectDecoder implements DecoderInterface } // adjust image orientation - $image->modify(new AlignRotationModifier()); + if ($this->driver()->config()->autoOrientation()) { + $image->modify(new AlignRotationModifier()); + } return $image; } diff --git a/src/Drivers/Gd/Decoders/FilePathImageDecoder.php b/src/Drivers/Gd/Decoders/FilePathImageDecoder.php index f83252ba..e1606a85 100644 --- a/src/Drivers/Gd/Decoders/FilePathImageDecoder.php +++ b/src/Drivers/Gd/Decoders/FilePathImageDecoder.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Gd\Decoders; -use Intervention\Image\Drivers\Gd\Decoders\Traits\CanDecodeGif; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\DecoderInterface; @@ -13,8 +12,6 @@ use Intervention\Image\Modifiers\AlignRotationModifier; class FilePathImageDecoder extends NativeObjectDecoder implements DecoderInterface { - use CanDecodeGif; - public function decode(mixed $input): ImageInterface|ColorInterface { if (!$this->isFile($input)) { @@ -53,7 +50,9 @@ class FilePathImageDecoder extends NativeObjectDecoder implements DecoderInterfa $image->setExif($this->extractExifData($input)); // adjust image orientation - $image->modify(new AlignRotationModifier()); + if ($this->driver()->config()->autoOrientation()) { + $image->modify(new AlignRotationModifier()); + } return $image; } diff --git a/src/Drivers/Gd/Decoders/NativeObjectDecoder.php b/src/Drivers/Gd/Decoders/NativeObjectDecoder.php index ca1bc38d..cf853598 100644 --- a/src/Drivers/Gd/Decoders/NativeObjectDecoder.php +++ b/src/Drivers/Gd/Decoders/NativeObjectDecoder.php @@ -5,13 +5,15 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Gd\Decoders; use GdImage; +use Intervention\Gif\Decoder as GifDecoder; +use Intervention\Gif\Splitter as GifSplitter; use Intervention\Image\Drivers\Gd\Core; -use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\Drivers\Gd\Frame; use Intervention\Image\Exceptions\DecoderException; +use Intervention\Image\Exceptions\RuntimeException; use Intervention\Image\Image; -use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ColorInterface; +use Intervention\Image\Interfaces\ImageInterface; class NativeObjectDecoder extends AbstractDecoder { @@ -38,10 +40,67 @@ class NativeObjectDecoder extends AbstractDecoder // build image instance return new Image( - new Driver(), + $this->driver(), new Core([ new Frame($input) ]) ); } + + /** + * Decode image from given GIF source which can be either a file path or binary data + * + * Depending on the configuration, this is taken over by the native GD function + * or, if animations are required, by our own extended decoder. + * + * @param mixed $input + * @throws RuntimeException + * @return ImageInterface + */ + protected function decodeGif(mixed $input): ImageInterface + { + // create non-animated image depending on config + if (!$this->driver()->config()->decodeAnimation()) { + $native = match (true) { + $this->isGifFormat($input) => @imagecreatefromstring($input), + default => @imagecreatefromgif($input), + }; + + if ($native === false) { + throw new DecoderException('Unable to decode input.'); + } + + $image = self::decode($native); + $image->origin()->setMediaType('image/gif'); + + return $image; + } + + // create empty core + $core = new Core(); + + $gif = GifDecoder::decode($input); + $splitter = GifSplitter::create($gif)->split(); + $delays = $splitter->getDelays(); + + // set loops on core + if ($loops = $gif->getMainApplicationExtension()?->getLoops()) { + $core->setLoops($loops); + } + + // add GDImage instances to core + foreach ($splitter->coalesceToResources() as $key => $native) { + $core->push( + new Frame($native, $delays[$key] / 100) + ); + } + + // create (possibly) animated image + $image = new Image($this->driver(), $core); + + // set media type + $image->origin()->setMediaType('image/gif'); + + return $image; + } } diff --git a/src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php b/src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php deleted file mode 100644 index 9b1452b9..00000000 --- a/src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php +++ /dev/null @@ -1,54 +0,0 @@ -split(); - $delays = $splitter->getDelays(); - - // build core - $core = new Core(); - - // set loops on core - if ($loops = $gif->getMainApplicationExtension()?->getLoops()) { - $core->setLoops($loops); - } - - // add GDImage instances to core - foreach ($splitter->coalesceToResources() as $key => $native) { - $core->push( - (new Frame($native))->setDelay($delays[$key] / 100) - ); - } - - // create image - $image = new Image(new Driver(), $core); - - // set media type - $image->origin()->setMediaType('image/gif'); - - return $image; - } -} diff --git a/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php b/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php index 15d61b8d..d1ae00f3 100644 --- a/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php +++ b/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php @@ -6,13 +6,13 @@ namespace Intervention\Image\Drivers\Imagick\Decoders; use Imagick; use Intervention\Image\Drivers\Imagick\Core; -use Intervention\Image\Drivers\Imagick\Driver; use Intervention\Image\Drivers\SpecializableDecoder; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Image; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Modifiers\AlignRotationModifier; +use Intervention\Image\Modifiers\RemoveAnimationModifier; class NativeObjectDecoder extends SpecializableDecoder { @@ -33,13 +33,21 @@ class NativeObjectDecoder extends SpecializableDecoder $input = $input->coalesceImages(); } + // create image object $image = new Image( - new Driver(), + $this->driver(), new Core($input) ); + // discard animation depending on config + if (!$this->driver()->config()->decodeAnimation()) { + $image->modify(new RemoveAnimationModifier()); + } + // adjust image rotatation - $image->modify(new AlignRotationModifier()); + if ($this->driver()->config()->autoOrientation()) { + $image->modify(new AlignRotationModifier()); + } // set media type on origin $image->origin()->setMediaType($input->getImageMimeType()); diff --git a/src/Image.php b/src/Image.php index 0c1731c7..124af013 100644 --- a/src/Image.php +++ b/src/Image.php @@ -13,7 +13,6 @@ use Intervention\Image\Analyzers\PixelColorsAnalyzer; use Intervention\Image\Analyzers\ProfileAnalyzer; use Intervention\Image\Analyzers\ResolutionAnalyzer; use Intervention\Image\Analyzers\WidthAnalyzer; -use Intervention\Image\Colors\Rgb\Color; use Intervention\Image\Encoders\AutoEncoder; use Intervention\Image\Encoders\AvifEncoder; use Intervention\Image\Encoders\BmpEncoder; @@ -50,6 +49,7 @@ use Intervention\Image\Interfaces\ModifierInterface; use Intervention\Image\Interfaces\ProfileInterface; use Intervention\Image\Interfaces\ResolutionInterface; use Intervention\Image\Interfaces\SizeInterface; +use Intervention\Image\Modifiers\AlignRotationModifier; use Intervention\Image\Modifiers\BlendTransparencyModifier; use Intervention\Image\Modifiers\BlurModifier; use Intervention\Image\Modifiers\BrightnessModifier; @@ -101,14 +101,6 @@ final class Image implements ImageInterface */ protected Origin $origin; - /** - * Color is mixed with transparent areas when converting to a format which - * does not support transparency. - * - * @var ColorInterface - */ - protected ColorInterface $blendingColor; - /** * Create new instance * @@ -124,9 +116,6 @@ final class Image implements ImageInterface protected CollectionInterface $exif = new Collection() ) { $this->origin = new Origin(); - $this->blendingColor = $this->colorspace()->importColor( - new Color(255, 255, 255, 0) - ); } /** @@ -418,7 +407,9 @@ final class Image implements ImageInterface */ public function blendingColor(): ColorInterface { - return $this->blendingColor; + return $this->driver()->handleInput( + $this->driver()->config()->blendingColor() + ); } /** @@ -428,7 +419,9 @@ final class Image implements ImageInterface */ public function setBlendingColor(mixed $color): ImageInterface { - $this->blendingColor = $this->driver()->handleInput($color); + $this->driver()->config()->setBlendingColor( + $this->driver()->handleInput($color) + ); return $this; } @@ -603,6 +596,16 @@ final class Image implements ImageInterface return $this->modify(new RotateModifier($angle, $background)); } + /** + * {@inheritdoc} + * + * @see ImageInterface::orient() + */ + public function orient(): ImageInterface + { + return $this->modify(new AlignRotationModifier()); + } + /** * {@inheritdoc} * diff --git a/src/ImageManager.php b/src/ImageManager.php index 4f8ca7c1..10327ca8 100644 --- a/src/ImageManager.php +++ b/src/ImageManager.php @@ -8,6 +8,7 @@ use Intervention\Image\Interfaces\DriverInterface; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Drivers\Gd\Driver as GdDriver; use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver; +use Intervention\Image\Exceptions\DriverException; use Intervention\Image\Interfaces\DecoderInterface; use Intervention\Image\Interfaces\ImageManagerInterface; @@ -41,6 +42,7 @@ final class ImageManager implements ImageManagerInterface * * @link https://image.intervention.io/v3/basics/image-manager#static-gd-driver-constructor * @param mixed $options + * @throws DriverException * @return ImageManager */ public static function gd(mixed ...$options): self @@ -53,6 +55,7 @@ final class ImageManager implements ImageManagerInterface * * @link https://image.intervention.io/v3/basics/image-manager#static-imagick-driver-constructor * @param mixed $options + * @throws DriverException * @return ImageManager */ public static function imagick(mixed ...$options): self diff --git a/src/InputHandler.php b/src/InputHandler.php index 9e19bc26..4e8fc2af 100644 --- a/src/InputHandler.php +++ b/src/InputHandler.php @@ -18,8 +18,10 @@ use Intervention\Image\Decoders\DataUriImageDecoder; use Intervention\Image\Decoders\FilePathImageDecoder; use Intervention\Image\Decoders\FilePointerImageDecoder; use Intervention\Image\Decoders\ImageObjectDecoder; +use Intervention\Image\Decoders\NativeObjectDecoder; use Intervention\Image\Decoders\SplFileInfoImageDecoder; use Intervention\Image\Exceptions\DecoderException; +use Intervention\Image\Exceptions\DriverException; use Intervention\Image\Exceptions\NotSupportedException; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\DecoderInterface; @@ -35,7 +37,7 @@ class InputHandler implements InputHandlerInterface * @var array */ protected array $decoders = [ - // NativeObjectDecoder::class, + NativeObjectDecoder::class, ImageObjectDecoder::class, ColorObjectDecoder::class, RgbHexColorDecoder::class, @@ -80,9 +82,9 @@ class InputHandler implements InputHandlerInterface */ public function handle($input): ImageInterface|ColorInterface { - foreach ($this->decoders as $decoderClassname) { + foreach ($this->decoders as $decoder) { // resolve river specialized decoder - $decoder = $this->resolve($decoderClassname); + $decoder = $this->resolve($decoder); try { return $decoder->decode($input); @@ -98,11 +100,20 @@ class InputHandler implements InputHandlerInterface * Resolve the given classname to an decoder object * * @param string|DecoderInterface $decoder - * @return DecoderInterface + * @throws DriverException * @throws NotSupportedException + * @return DecoderInterface */ private function resolve(string|DecoderInterface $decoder): DecoderInterface { + if (($decoder instanceof DecoderInterface) && empty($this->driver)) { + return $decoder; + } + + if (($decoder instanceof DecoderInterface) && !empty($this->driver)) { + return $this->driver->specialize($decoder); + } + if (empty($this->driver)) { return new $decoder(); } diff --git a/src/Interfaces/ConfigInterface.php b/src/Interfaces/ConfigInterface.php index 27ceda9c..67cedb06 100644 --- a/src/Interfaces/ConfigInterface.php +++ b/src/Interfaces/ConfigInterface.php @@ -7,6 +7,10 @@ namespace Intervention\Image\Interfaces; interface ConfigInterface { public function decodeAnimation(): bool; - public function autoOrientate(): bool; + + public function autoOrientation(): bool; + public function blendingColor(): mixed; + + public function setBlendingColor(mixed $color): mixed; } diff --git a/src/Interfaces/DriverInterface.php b/src/Interfaces/DriverInterface.php index 31e33080..c7e3c314 100644 --- a/src/Interfaces/DriverInterface.php +++ b/src/Interfaces/DriverInterface.php @@ -32,6 +32,7 @@ interface DriverInterface * * @param ModifierInterface|AnalyzerInterface|EncoderInterface|DecoderInterface $object * @throws NotSupportedException + * @throws DriverException * @return ModifierInterface|AnalyzerInterface|EncoderInterface|DecoderInterface */ public function specialize( diff --git a/src/Interfaces/ImageInterface.php b/src/Interfaces/ImageInterface.php index 4315ac8d..49afbb10 100644 --- a/src/Interfaces/ImageInterface.php +++ b/src/Interfaces/ImageInterface.php @@ -248,6 +248,7 @@ interface ImageInterface extends IteratorAggregate, Countable * does not support transparency. * * @link https://image.intervention.io/v3/basics/colors#transparency + * @throws RuntimeException * @return ColorInterface */ public function blendingColor(): ColorInterface; @@ -430,6 +431,14 @@ interface ImageInterface extends IteratorAggregate, Countable */ public function rotate(float $angle, mixed $background = 'ffffff'): self; + /** + * Rotate the image to be upright according to exif information + * + * @throws RuntimeException + * @return ImageInterface + */ + public function orient(): self; + /** * Draw text on image * diff --git a/src/Interfaces/SpecializableInterface.php b/src/Interfaces/SpecializableInterface.php index e04ee6dc..5c188d01 100644 --- a/src/Interfaces/SpecializableInterface.php +++ b/src/Interfaces/SpecializableInterface.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Intervention\Image\Interfaces; +use Intervention\Image\Exceptions\DriverException; + interface SpecializableInterface { /** @@ -18,6 +20,7 @@ interface SpecializableInterface * Set the driver for which the object is specialized * * @param DriverInterface $driver + * @throws DriverException * @return SpecializableInterface */ public function setDriver(DriverInterface $driver): self; diff --git a/src/Traits/CanBeDriverSpecialized.php b/src/Traits/CanBeDriverSpecialized.php index 463050f4..eb597be3 100644 --- a/src/Traits/CanBeDriverSpecialized.php +++ b/src/Traits/CanBeDriverSpecialized.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Intervention\Image\Traits; +use Intervention\Image\Exceptions\DriverException; use Intervention\Image\Interfaces\DriverInterface; use Intervention\Image\Interfaces\SpecializableInterface; use ReflectionClass; @@ -53,8 +54,30 @@ trait CanBeDriverSpecialized */ public function setDriver(DriverInterface $driver): SpecializableInterface { + if (!$this->belongsToDriver($driver)) { + throw new DriverException( + "Class '" . $this::class . "' can not be used with " . $driver->id() . " driver." + ); + } + $this->driver = $driver; return $this; } + + /** + * Determine if the given object belongs to the driver's namespace + * + * @param object $object + * @return bool + */ + protected function belongsToDriver(object $object): bool + { + $driverId = function (object $object): string|bool { + $id = substr($object::class, 27); + return strstr($id, "\\", true); + }; + + return $driverId($this) === $driverId($object); + } } diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index 7416909f..41caa2aa 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -15,20 +15,20 @@ use PHPUnit\Framework\ExpectationFailedException; abstract class BaseTestCase extends MockeryTestCase { - public function getTestResourcePath($filename = 'test.jpg'): string + public static function getTestResourcePath($filename = 'test.jpg'): string { return sprintf('%s/resources/%s', __DIR__, $filename); } - public function getTestResourceData($filename = 'test.jpg'): string + public static function getTestResourceData($filename = 'test.jpg'): string { - return file_get_contents($this->getTestResourcePath($filename)); + return file_get_contents(self::getTestResourcePath($filename)); } public function getTestResourcePointer($filename = 'test.jpg') { $pointer = fopen('php://temp', 'rw'); - fputs($pointer, $this->getTestResourceData($filename)); + fputs($pointer, self::getTestResourceData($filename)); rewind($pointer); return $pointer; diff --git a/tests/GdTestCase.php b/tests/GdTestCase.php index 6afd44f9..2cfa9527 100644 --- a/tests/GdTestCase.php +++ b/tests/GdTestCase.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace Intervention\Image\Tests; +use Intervention\Image\Decoders\FilePathImageDecoder; use Intervention\Image\Drivers\Gd\Core; -use Intervention\Image\Drivers\Gd\Decoders\FilePathImageDecoder; use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\Drivers\Gd\Frame; use Intervention\Image\Image; @@ -14,7 +14,7 @@ abstract class GdTestCase extends BaseTestCase { public function readTestImage($filename = 'test.jpg'): Image { - return (new FilePathImageDecoder())->decode( + return (new Driver())->specialize(new FilePathImageDecoder())->decode( $this->getTestResourcePath($filename) ); } diff --git a/tests/ImagickTestCase.php b/tests/ImagickTestCase.php index ef23695c..cbd24dcc 100644 --- a/tests/ImagickTestCase.php +++ b/tests/ImagickTestCase.php @@ -6,8 +6,8 @@ namespace Intervention\Image\Tests; use Imagick; use ImagickPixel; +use Intervention\Image\Decoders\FilePathImageDecoder; use Intervention\Image\Drivers\Imagick\Core; -use Intervention\Image\Drivers\Imagick\Decoders\FilePathImageDecoder; use Intervention\Image\Drivers\Imagick\Driver; use Intervention\Image\Image; @@ -15,7 +15,7 @@ abstract class ImagickTestCase extends BaseTestCase { public function readTestImage($filename = 'test.jpg'): Image { - return (new FilePathImageDecoder())->decode( + return (new Driver())->specialize(new FilePathImageDecoder())->decode( $this->getTestResourcePath($filename) ); } diff --git a/tests/Unit/Drivers/Gd/Decoders/Base64ImageDecoderTest.php b/tests/Unit/Drivers/Gd/Decoders/Base64ImageDecoderTest.php index 51853e44..ff1bcc81 100644 --- a/tests/Unit/Drivers/Gd/Decoders/Base64ImageDecoderTest.php +++ b/tests/Unit/Drivers/Gd/Decoders/Base64ImageDecoderTest.php @@ -7,6 +7,7 @@ namespace Intervention\Image\Tests\Unit\Drivers\Gd\Decoders; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Drivers\Gd\Decoders\Base64ImageDecoder; +use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Image; use Intervention\Image\Tests\BaseTestCase; @@ -20,6 +21,7 @@ final class Base64ImageDecoderTest extends BaseTestCase protected function setUp(): void { $this->decoder = new Base64ImageDecoder(); + $this->decoder->setDriver(new Driver()); } public function testDecode(): void diff --git a/tests/Unit/Drivers/Gd/Decoders/BinaryImageDecoderTest.php b/tests/Unit/Drivers/Gd/Decoders/BinaryImageDecoderTest.php index d09690a3..f7309f40 100644 --- a/tests/Unit/Drivers/Gd/Decoders/BinaryImageDecoderTest.php +++ b/tests/Unit/Drivers/Gd/Decoders/BinaryImageDecoderTest.php @@ -7,6 +7,7 @@ namespace Intervention\Image\Tests\Unit\Drivers\Gd\Decoders; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Drivers\Gd\Decoders\BinaryImageDecoder; +use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\Image; use Intervention\Image\Tests\BaseTestCase; @@ -14,10 +15,17 @@ use Intervention\Image\Tests\BaseTestCase; #[CoversClass(\Intervention\Image\Drivers\Gd\Decoders\BinaryImageDecoder::class)] final class BinaryImageDecoderTest extends BaseTestCase { + protected BinaryImageDecoder $decoder; + + protected function setUp(): void + { + $this->decoder = new BinaryImageDecoder(); + $this->decoder->setDriver(new Driver()); + } + public function testDecodePng(): void { - $decoder = new BinaryImageDecoder(); - $image = $decoder->decode(file_get_contents($this->getTestResourcePath('tile.png'))); + $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('tile.png'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); @@ -26,8 +34,7 @@ final class BinaryImageDecoderTest extends BaseTestCase public function testDecodeGif(): void { - $decoder = new BinaryImageDecoder(); - $image = $decoder->decode(file_get_contents($this->getTestResourcePath('red.gif'))); + $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('red.gif'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); @@ -36,8 +43,7 @@ final class BinaryImageDecoderTest extends BaseTestCase public function testDecodeAnimatedGif(): void { - $decoder = new BinaryImageDecoder(); - $image = $decoder->decode(file_get_contents($this->getTestResourcePath('cats.gif'))); + $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('cats.gif'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(75, $image->width()); $this->assertEquals(50, $image->height()); @@ -46,8 +52,7 @@ final class BinaryImageDecoderTest extends BaseTestCase public function testDecodeJpegWithExif(): void { - $decoder = new BinaryImageDecoder(); - $image = $decoder->decode(file_get_contents($this->getTestResourcePath('exif.jpg'))); + $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('exif.jpg'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); diff --git a/tests/Unit/Drivers/Gd/Decoders/DataUriImageDecoderTest.php b/tests/Unit/Drivers/Gd/Decoders/DataUriImageDecoderTest.php index 100fd982..8c2f604e 100644 --- a/tests/Unit/Drivers/Gd/Decoders/DataUriImageDecoderTest.php +++ b/tests/Unit/Drivers/Gd/Decoders/DataUriImageDecoderTest.php @@ -7,6 +7,7 @@ namespace Intervention\Image\Tests\Unit\Drivers\Gd\Decoders; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Drivers\Gd\Decoders\DataUriImageDecoder; +use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Image; use Intervention\Image\Tests\BaseTestCase; @@ -21,6 +22,7 @@ final class DataUriImageDecoderTest extends BaseTestCase protected function setUp(): void { $this->decoder = new DataUriImageDecoder(); + $this->decoder->setDriver(new Driver()); } public function testDecode(): void diff --git a/tests/Unit/Drivers/Gd/Decoders/FilePathImageDecoderTest.php b/tests/Unit/Drivers/Gd/Decoders/FilePathImageDecoderTest.php index 0a1a3d95..9a76bd2f 100644 --- a/tests/Unit/Drivers/Gd/Decoders/FilePathImageDecoderTest.php +++ b/tests/Unit/Drivers/Gd/Decoders/FilePathImageDecoderTest.php @@ -7,10 +7,11 @@ namespace Intervention\Image\Tests\Unit\Drivers\Gd\Decoders; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Drivers\Gd\Decoders\FilePathImageDecoder; +use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Image; use Intervention\Image\Tests\BaseTestCase; -use stdClass; +use PHPUnit\Framework\Attributes\DataProvider; #[RequiresPhpExtension('gd')] #[CoversClass(\Intervention\Image\Drivers\Gd\Decoders\FilePathImageDecoder::class)] @@ -21,32 +22,35 @@ final class FilePathImageDecoderTest extends BaseTestCase protected function setUp(): void { $this->decoder = new FilePathImageDecoder(); + $this->decoder->setDriver(new Driver()); } - public function testDecode(): void + #[DataProvider('validFormatPathsProvider')] + public function testDecode(string $path, bool $result): void { - $result = $this->decoder->decode( - $this->getTestResourcePath() - ); + if ($result === false) { + $this->expectException(DecoderException::class); + } - $this->assertInstanceOf(Image::class, $result); + $result = $this->decoder->decode($path); + + if ($result === true) { + $this->assertInstanceOf(Image::class, $result); + } } - public function testDecoderNonString(): void + public static function validFormatPathsProvider(): array { - $this->expectException(DecoderException::class); - $this->decoder->decode(new stdClass()); - } - - public function testDecoderNoPath(): void - { - $this->expectException(DecoderException::class); - $this->decoder->decode('no-path'); - } - - public function testDecoderTooLongPath(): void - { - $this->expectException(DecoderException::class); - $this->decoder->decode(str_repeat('x', PHP_MAXPATHLEN + 1)); + return [ + [self::getTestResourcePath('cats.gif'), true], + [self::getTestResourcePath('animation.gif'), true], + [self::getTestResourcePath('red.gif'), true], + [self::getTestResourcePath('green.gif'), true], + [self::getTestResourcePath('blue.gif'), true], + [self::getTestResourcePath('gradient.bmp'), true], + [self::getTestResourcePath('circle.png'), true], + ['no-path', false], + [str_repeat('x', PHP_MAXPATHLEN + 1), false], + ]; } } diff --git a/tests/Unit/Drivers/Gd/Decoders/FilePointerImageDecoderTest.php b/tests/Unit/Drivers/Gd/Decoders/FilePointerImageDecoderTest.php index bf2281bc..e40777ef 100644 --- a/tests/Unit/Drivers/Gd/Decoders/FilePointerImageDecoderTest.php +++ b/tests/Unit/Drivers/Gd/Decoders/FilePointerImageDecoderTest.php @@ -7,6 +7,7 @@ namespace Intervention\Image\Tests\Unit\Drivers\Gd\Decoders; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Drivers\Gd\Decoders\FilePointerImageDecoder; +use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\Image; use Intervention\Image\Tests\GdTestCase; @@ -17,6 +18,8 @@ final class FilePointerImageDecoderTest extends GdTestCase public function testDecode(): void { $decoder = new FilePointerImageDecoder(); + $decoder->setDriver(new Driver()); + $fp = fopen($this->getTestResourcePath('test.jpg'), 'r'); $result = $decoder->decode($fp); $this->assertInstanceOf(Image::class, $result); diff --git a/tests/Unit/Drivers/Gd/Decoders/SplFileInfoImageDecoderTest.php b/tests/Unit/Drivers/Gd/Decoders/SplFileInfoImageDecoderTest.php index e7517235..d0a5617e 100644 --- a/tests/Unit/Drivers/Gd/Decoders/SplFileInfoImageDecoderTest.php +++ b/tests/Unit/Drivers/Gd/Decoders/SplFileInfoImageDecoderTest.php @@ -7,6 +7,7 @@ namespace Intervention\Image\Tests\Unit\Drivers\Gd\Decoders; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Drivers\Gd\Decoders\SplFileInfoImageDecoder; +use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\Image; use Intervention\Image\Tests\BaseTestCase; use SplFileInfo; @@ -18,6 +19,8 @@ final class SplFileInfoImageDecoderTest extends BaseTestCase public function testDecode(): void { $decoder = new SplFileInfoImageDecoder(); + $decoder->setDriver(new Driver()); + $result = $decoder->decode( new SplFileInfo($this->getTestResourcePath('blue.gif')) ); diff --git a/tests/Unit/Drivers/Gd/Traits/CanDecodeGifTest.php b/tests/Unit/Drivers/Gd/Traits/CanDecodeGifTest.php deleted file mode 100644 index f8cdafe8..00000000 --- a/tests/Unit/Drivers/Gd/Traits/CanDecodeGifTest.php +++ /dev/null @@ -1,57 +0,0 @@ -makePartial(); - - $result = $decoder->decodeGif($this->getTestResourceData('animation.gif')); - $this->assertInstanceOf(ImageInterface::class, $result); - $this->assertEquals('image/gif', $result->origin()->mediaType()); - } - - public function testDecodeGifFromBinaryStatic(): void - { - $decoder = Mockery::mock(new class () { - use CanDecodeGif; - })->makePartial(); - - $result = $decoder->decodeGif($this->getTestResourceData('red.gif')); - $this->assertInstanceOf(ImageInterface::class, $result); - $this->assertEquals('image/gif', $result->origin()->mediaType()); - } - - public function testDecodeGifFromPathAnimation(): void - { - $decoder = Mockery::mock(new class () { - use CanDecodeGif; - })->makePartial(); - - $result = $decoder->decodeGif($this->getTestResourcePath('animation.gif')); - $this->assertInstanceOf(ImageInterface::class, $result); - $this->assertEquals('image/gif', $result->origin()->mediaType()); - } - - public function testDecodeGifFromPathStatic(): void - { - $decoder = Mockery::mock(new class () { - use CanDecodeGif; - })->makePartial(); - - $result = $decoder->decodeGif($this->getTestResourcePath('red.gif')); - $this->assertInstanceOf(ImageInterface::class, $result); - $this->assertEquals('image/gif', $result->origin()->mediaType()); - } -} diff --git a/tests/Unit/Drivers/Imagick/Decoders/Base64ImageDecoderTest.php b/tests/Unit/Drivers/Imagick/Decoders/Base64ImageDecoderTest.php index 82910d26..d4f622eb 100644 --- a/tests/Unit/Drivers/Imagick/Decoders/Base64ImageDecoderTest.php +++ b/tests/Unit/Drivers/Imagick/Decoders/Base64ImageDecoderTest.php @@ -7,6 +7,7 @@ namespace Intervention\Image\Tests\Unit\Drivers\Imagick\Decoders; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Drivers\Imagick\Decoders\Base64ImageDecoder; +use Intervention\Image\Drivers\Imagick\Driver; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Image; use Intervention\Image\Tests\BaseTestCase; @@ -20,6 +21,7 @@ final class Base64ImageDecoderTest extends BaseTestCase protected function setUp(): void { $this->decoder = new Base64ImageDecoder(); + $this->decoder->setDriver(new Driver()); } public function testDecode(): void diff --git a/tests/Unit/Drivers/Imagick/Decoders/BinaryImageDecoderTest.php b/tests/Unit/Drivers/Imagick/Decoders/BinaryImageDecoderTest.php index 6cab39a4..2b221166 100644 --- a/tests/Unit/Drivers/Imagick/Decoders/BinaryImageDecoderTest.php +++ b/tests/Unit/Drivers/Imagick/Decoders/BinaryImageDecoderTest.php @@ -7,6 +7,7 @@ namespace Intervention\Image\Tests\Unit\Drivers\Imagick\Decoders; use Intervention\Image\Colors\Cmyk\Colorspace as CmykColorspace; use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace; use Intervention\Image\Drivers\Imagick\Decoders\BinaryImageDecoder; +use Intervention\Image\Drivers\Imagick\Driver; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Image; use Intervention\Image\Tests\BaseTestCase; @@ -14,10 +15,17 @@ use stdClass; final class BinaryImageDecoderTest extends BaseTestCase { + protected BinaryImageDecoder $decoder; + + protected function setUp(): void + { + $this->decoder = new BinaryImageDecoder(); + $this->decoder->setDriver(new Driver()); + } + public function testDecodePng(): void { - $decoder = new BinaryImageDecoder(); - $image = $decoder->decode(file_get_contents($this->getTestResourcePath('tile.png'))); + $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('tile.png'))); $this->assertInstanceOf(Image::class, $image); $this->assertInstanceOf(RgbColorspace::class, $image->colorspace()); $this->assertEquals(16, $image->width()); @@ -27,8 +35,7 @@ final class BinaryImageDecoderTest extends BaseTestCase public function testDecodeGif(): void { - $decoder = new BinaryImageDecoder(); - $image = $decoder->decode(file_get_contents($this->getTestResourcePath('red.gif'))); + $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('red.gif'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); @@ -37,8 +44,7 @@ final class BinaryImageDecoderTest extends BaseTestCase public function testDecodeAnimatedGif(): void { - $decoder = new BinaryImageDecoder(); - $image = $decoder->decode(file_get_contents($this->getTestResourcePath('cats.gif'))); + $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('cats.gif'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(75, $image->width()); $this->assertEquals(50, $image->height()); @@ -47,8 +53,7 @@ final class BinaryImageDecoderTest extends BaseTestCase public function testDecodeJpegWithExif(): void { - $decoder = new BinaryImageDecoder(); - $image = $decoder->decode(file_get_contents($this->getTestResourcePath('exif.jpg'))); + $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('exif.jpg'))); $this->assertInstanceOf(Image::class, $image); $this->assertEquals(16, $image->width()); $this->assertEquals(16, $image->height()); @@ -58,16 +63,14 @@ final class BinaryImageDecoderTest extends BaseTestCase public function testDecodeCmykImage(): void { - $decoder = new BinaryImageDecoder(); - $image = $decoder->decode(file_get_contents($this->getTestResourcePath('cmyk.jpg'))); + $image = $this->decoder->decode(file_get_contents($this->getTestResourcePath('cmyk.jpg'))); $this->assertInstanceOf(Image::class, $image); $this->assertInstanceOf(CmykColorspace::class, $image->colorspace()); } public function testDecodeNonString(): void { - $decoder = new BinaryImageDecoder(); $this->expectException(DecoderException::class); - $decoder->decode(new stdClass()); + $this->decoder->decode(new stdClass()); } } diff --git a/tests/Unit/Drivers/Imagick/Decoders/DataUriImageDecoderTest.php b/tests/Unit/Drivers/Imagick/Decoders/DataUriImageDecoderTest.php index 1a4ca500..db8de4d5 100644 --- a/tests/Unit/Drivers/Imagick/Decoders/DataUriImageDecoderTest.php +++ b/tests/Unit/Drivers/Imagick/Decoders/DataUriImageDecoderTest.php @@ -7,6 +7,7 @@ namespace Intervention\Image\Tests\Unit\Drivers\Imagick\Decoders; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Drivers\Imagick\Decoders\DataUriImageDecoder; +use Intervention\Image\Drivers\Imagick\Driver; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Image; use Intervention\Image\Tests\BaseTestCase; @@ -21,6 +22,7 @@ final class DataUriImageDecoderTest extends BaseTestCase protected function setUp(): void { $this->decoder = new DataUriImageDecoder(); + $this->decoder->setDriver(new Driver()); } public function testDecode(): void diff --git a/tests/Unit/Drivers/Imagick/Decoders/FilePathImageDecoderTest.php b/tests/Unit/Drivers/Imagick/Decoders/FilePathImageDecoderTest.php index 2a8ffcd6..8e5cd768 100644 --- a/tests/Unit/Drivers/Imagick/Decoders/FilePathImageDecoderTest.php +++ b/tests/Unit/Drivers/Imagick/Decoders/FilePathImageDecoderTest.php @@ -7,10 +7,11 @@ namespace Intervention\Image\Tests\Unit\Drivers\Imagick\Decoders; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Drivers\Imagick\Decoders\FilePathImageDecoder; +use Intervention\Image\Drivers\Imagick\Driver; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Image; use Intervention\Image\Tests\BaseTestCase; -use stdClass; +use PHPUnit\Framework\Attributes\DataProvider; #[RequiresPhpExtension('imagick')] #[CoversClass(\Intervention\Image\Drivers\Imagick\Decoders\FilePathImageDecoder::class)] @@ -21,32 +22,35 @@ final class FilePathImageDecoderTest extends BaseTestCase protected function setUp(): void { $this->decoder = new FilePathImageDecoder(); + $this->decoder->setDriver(new Driver()); } - public function testDecode(): void + #[DataProvider('validFormatPathsProvider')] + public function testDecode(string $path, bool $result): void { - $result = $this->decoder->decode( - $this->getTestResourcePath() - ); + if ($result === false) { + $this->expectException(DecoderException::class); + } - $this->assertInstanceOf(Image::class, $result); + $result = $this->decoder->decode($path); + + if ($result === true) { + $this->assertInstanceOf(Image::class, $result); + } } - public function testDecoderNonString(): void + public static function validFormatPathsProvider(): array { - $this->expectException(DecoderException::class); - $this->decoder->decode(new stdClass()); - } - - public function testDecoderNoPath(): void - { - $this->expectException(DecoderException::class); - $this->decoder->decode('no-path'); - } - - public function testDecoderTooLongPath(): void - { - $this->expectException(DecoderException::class); - $this->decoder->decode(str_repeat('x', PHP_MAXPATHLEN + 1)); + return [ + [self::getTestResourcePath('cats.gif'), true], + [self::getTestResourcePath('animation.gif'), true], + [self::getTestResourcePath('red.gif'), true], + [self::getTestResourcePath('green.gif'), true], + [self::getTestResourcePath('blue.gif'), true], + [self::getTestResourcePath('gradient.bmp'), true], + [self::getTestResourcePath('circle.png'), true], + ['no-path', false], + [str_repeat('x', PHP_MAXPATHLEN + 1), false], + ]; } } diff --git a/tests/Unit/Drivers/Imagick/Decoders/FilePointerImageDecoderTest.php b/tests/Unit/Drivers/Imagick/Decoders/FilePointerImageDecoderTest.php index 8165b3c2..15724014 100644 --- a/tests/Unit/Drivers/Imagick/Decoders/FilePointerImageDecoderTest.php +++ b/tests/Unit/Drivers/Imagick/Decoders/FilePointerImageDecoderTest.php @@ -7,6 +7,7 @@ namespace Intervention\Image\Tests\Unit\Drivers\Imagick\Decoders; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Drivers\Imagick\Decoders\FilePointerImageDecoder; +use Intervention\Image\Drivers\Imagick\Driver; use Intervention\Image\Image; use Intervention\Image\Tests\ImagickTestCase; @@ -17,6 +18,7 @@ final class FilePointerImageDecoderTest extends ImagickTestCase public function testDecode(): void { $decoder = new FilePointerImageDecoder(); + $decoder->setDriver(new Driver()); $fp = fopen($this->getTestResourcePath('test.jpg'), 'r'); $result = $decoder->decode($fp); $this->assertInstanceOf(Image::class, $result); diff --git a/tests/Unit/Drivers/Imagick/Decoders/SplFileInfoImageDecoderTest.php b/tests/Unit/Drivers/Imagick/Decoders/SplFileInfoImageDecoderTest.php index 3e549909..c4c26c86 100644 --- a/tests/Unit/Drivers/Imagick/Decoders/SplFileInfoImageDecoderTest.php +++ b/tests/Unit/Drivers/Imagick/Decoders/SplFileInfoImageDecoderTest.php @@ -7,6 +7,7 @@ namespace Intervention\Image\Tests\Unit\Drivers\Imagick\Decoders; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Intervention\Image\Drivers\Imagick\Decoders\SplFileInfoImageDecoder; +use Intervention\Image\Drivers\Imagick\Driver; use Intervention\Image\Image; use Intervention\Image\Tests\BaseTestCase; use SplFileInfo; @@ -18,6 +19,7 @@ final class SplFileInfoImageDecoderTest extends BaseTestCase public function testDecode(): void { $decoder = new SplFileInfoImageDecoder(); + $decoder->setDriver(new Driver()); $result = $decoder->decode( new SplFileInfo($this->getTestResourcePath('blue.gif')) ); From 51a81c845b0e0c11c1fa49025c1ec05c0cf2ba17 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Thu, 9 May 2024 11:49:35 +0200 Subject: [PATCH 05/25] Add config tests --- tests/Unit/ConfigTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/Unit/ConfigTest.php diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php new file mode 100644 index 00000000..45166c7e --- /dev/null +++ b/tests/Unit/ConfigTest.php @@ -0,0 +1,18 @@ +assertInstanceOf(Config::class, new Config()); + } +} From b79650acee726d3aee719de161f45ef67c404408 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Fri, 10 May 2024 10:04:11 +0200 Subject: [PATCH 06/25] Refactor config handling --- src/Config.php | 63 ++++++++++++------- src/Drivers/AbstractDriver.php | 9 ++- .../Gd/Decoders/BinaryImageDecoder.php | 2 +- .../Gd/Decoders/FilePathImageDecoder.php | 2 +- .../Gd/Decoders/NativeObjectDecoder.php | 2 +- .../Imagick/Decoders/NativeObjectDecoder.php | 4 +- src/Image.php | 5 +- src/ImageManager.php | 21 +++---- src/Interfaces/ConfigInterface.php | 31 +++++++-- tests/Unit/ConfigTest.php | 53 +++++++++++++++- 10 files changed, 143 insertions(+), 49 deletions(-) diff --git a/src/Config.php b/src/Config.php index 10c1a685..5b7a4b86 100644 --- a/src/Config.php +++ b/src/Config.php @@ -4,10 +4,19 @@ declare(strict_types=1); namespace Intervention\Image; +use Intervention\Image\Exceptions\InputException; use Intervention\Image\Interfaces\ConfigInterface; class Config implements ConfigInterface { + /** + * Create config object instance + * + * @param bool $autoOrientation + * @param bool $decodeAnimation + * @param mixed $blendingColor + * @return void + */ public function __construct( protected bool $autoOrientation = true, protected bool $decodeAnimation = true, @@ -15,38 +24,46 @@ class Config implements ConfigInterface ) { } - public function decodeAnimation(): bool + /** + * {@inheritdoc} + * + * @see ConfigInterface::option() + */ + public function option(string $name, mixed $default = null): mixed { - return $this->decodeAnimation; + if (!property_exists($this, $name)) { + return $default; + } + + return $this->{$name}; } - public function setDecodeAnimation(bool $status): self + /** + * {@inheritdoc} + * + * @see ConfigInterface::setOption() + */ + public function setOption(string $name, mixed $value): self { - $this->decodeAnimation = $status; + if (!property_exists($this, $name)) { + throw new InputException('Property ' . $name . ' does not exists for ' . $this::class . '.'); + } + + $this->{$name} = $value; return $this; } - public function autoOrientation(): bool + /** + * {@inheritdoc} + * + * @see COnfigInterface::setOptions() + */ + public function setOptions(mixed ...$options): self { - return $this->autoOrientation; - } - - public function setAutoOrientation(bool $status): self - { - $this->autoOrientation = $status; - - return $this; - } - - public function blendingColor(): mixed - { - return $this->blendingColor; - } - - public function setBlendingColor(mixed $color): self - { - $this->blendingColor = $color; + foreach ($options as $name => $value) { + $this->setOption($name, $value); + } return $this; } diff --git a/src/Drivers/AbstractDriver.php b/src/Drivers/AbstractDriver.php index b2db442e..1ccd79fd 100644 --- a/src/Drivers/AbstractDriver.php +++ b/src/Drivers/AbstractDriver.php @@ -23,12 +23,17 @@ use ReflectionClass; abstract class AbstractDriver implements DriverInterface { /** - * @param ConfigInterface $config + * Driver options + */ + protected ConfigInterface $config; + + /** * @throws DriverException * @return void */ - public function __construct(protected ConfigInterface $config = new Config()) + public function __construct() { + $this->config = new Config(); $this->checkHealth(); } diff --git a/src/Drivers/Gd/Decoders/BinaryImageDecoder.php b/src/Drivers/Gd/Decoders/BinaryImageDecoder.php index c4f43749..b775eb09 100644 --- a/src/Drivers/Gd/Decoders/BinaryImageDecoder.php +++ b/src/Drivers/Gd/Decoders/BinaryImageDecoder.php @@ -60,7 +60,7 @@ class BinaryImageDecoder extends NativeObjectDecoder implements DecoderInterface } // adjust image orientation - if ($this->driver()->config()->autoOrientation()) { + if ($this->driver()->config()->option('autoOrientation') === true) { $image->modify(new AlignRotationModifier()); } diff --git a/src/Drivers/Gd/Decoders/FilePathImageDecoder.php b/src/Drivers/Gd/Decoders/FilePathImageDecoder.php index e1606a85..e3f1780e 100644 --- a/src/Drivers/Gd/Decoders/FilePathImageDecoder.php +++ b/src/Drivers/Gd/Decoders/FilePathImageDecoder.php @@ -50,7 +50,7 @@ class FilePathImageDecoder extends NativeObjectDecoder implements DecoderInterfa $image->setExif($this->extractExifData($input)); // adjust image orientation - if ($this->driver()->config()->autoOrientation()) { + if ($this->driver()->config()->option('autoOrientation') === true) { $image->modify(new AlignRotationModifier()); } diff --git a/src/Drivers/Gd/Decoders/NativeObjectDecoder.php b/src/Drivers/Gd/Decoders/NativeObjectDecoder.php index cf853598..d0e48381 100644 --- a/src/Drivers/Gd/Decoders/NativeObjectDecoder.php +++ b/src/Drivers/Gd/Decoders/NativeObjectDecoder.php @@ -60,7 +60,7 @@ class NativeObjectDecoder extends AbstractDecoder protected function decodeGif(mixed $input): ImageInterface { // create non-animated image depending on config - if (!$this->driver()->config()->decodeAnimation()) { + if (!$this->driver()->config()->option('decodeAnimation') === true) { $native = match (true) { $this->isGifFormat($input) => @imagecreatefromstring($input), default => @imagecreatefromgif($input), diff --git a/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php b/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php index d1ae00f3..4c399c7b 100644 --- a/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php +++ b/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php @@ -40,12 +40,12 @@ class NativeObjectDecoder extends SpecializableDecoder ); // discard animation depending on config - if (!$this->driver()->config()->decodeAnimation()) { + if (!$this->driver()->config()->option('decodeAnimation') === true) { $image->modify(new RemoveAnimationModifier()); } // adjust image rotatation - if ($this->driver()->config()->autoOrientation()) { + if ($this->driver()->config()->option('autoOrientation') === true) { $image->modify(new AlignRotationModifier()); } diff --git a/src/Image.php b/src/Image.php index 124af013..ba02c061 100644 --- a/src/Image.php +++ b/src/Image.php @@ -408,7 +408,7 @@ final class Image implements ImageInterface public function blendingColor(): ColorInterface { return $this->driver()->handleInput( - $this->driver()->config()->blendingColor() + $this->driver()->config()->option('blendingColor') ); } @@ -419,7 +419,8 @@ final class Image implements ImageInterface */ public function setBlendingColor(mixed $color): ImageInterface { - $this->driver()->config()->setBlendingColor( + $this->driver()->config()->setOption( + 'blendingColor', $this->driver()->handleInput($color) ); diff --git a/src/ImageManager.php b/src/ImageManager.php index 10327ca8..18fcf8dd 100644 --- a/src/ImageManager.php +++ b/src/ImageManager.php @@ -20,9 +20,9 @@ final class ImageManager implements ImageManagerInterface * @link https://image.intervention.io/v3/basics/image-manager#create-a-new-image-manager-instance * @param string|DriverInterface $driver */ - public function __construct(string|DriverInterface $driver) + public function __construct(string|DriverInterface $driver, mixed ...$options) { - $this->driver = $this->resolveDriver($driver); + $this->driver = $this->resolveDriver($driver, ...$options); } /** @@ -32,9 +32,9 @@ final class ImageManager implements ImageManagerInterface * @param string|DriverInterface $driver * @return ImageManager */ - public static function withDriver(string|DriverInterface $driver): self + public static function withDriver(string|DriverInterface $driver, mixed ...$options): self { - return new self(self::resolveDriver($driver)); + return new self(self::resolveDriver($driver, ...$options)); } /** @@ -47,7 +47,7 @@ final class ImageManager implements ImageManagerInterface */ public static function gd(mixed ...$options): self { - return self::withDriver(new GdDriver(new Config(...$options))); + return self::withDriver(new GdDriver(), ...$options); } /** @@ -60,7 +60,7 @@ final class ImageManager implements ImageManagerInterface */ public static function imagick(mixed ...$options): self { - return self::withDriver(new ImagickDriver(new Config(...$options))); + return self::withDriver(new ImagickDriver(), ...$options); } /** @@ -115,12 +115,11 @@ final class ImageManager implements ImageManagerInterface * @param string|DriverInterface $driver * @return DriverInterface */ - private static function resolveDriver(string|DriverInterface $driver): DriverInterface + private static function resolveDriver(string|DriverInterface $driver, mixed ...$options): DriverInterface { - if (is_object($driver)) { - return $driver; - } + $driver = is_string($driver) ? new $driver() : $driver; + $driver->config()->setOptions(...$options); - return new $driver(); + return $driver; } } diff --git a/src/Interfaces/ConfigInterface.php b/src/Interfaces/ConfigInterface.php index 67cedb06..0fe2ebff 100644 --- a/src/Interfaces/ConfigInterface.php +++ b/src/Interfaces/ConfigInterface.php @@ -4,13 +4,34 @@ declare(strict_types=1); namespace Intervention\Image\Interfaces; +use Intervention\Image\Exceptions\InputException; + interface ConfigInterface { - public function decodeAnimation(): bool; + /** + * Return value of given config option + * + * @param string $name + * @return mixed + */ + public function option(string $name, mixed $default = null): mixed; - public function autoOrientation(): bool; + /** + * Set value of given config option + * + * @param string $name + * @param mixed $value + * @throws InputException + * @return ConfigInterface + */ + public function setOption(string $name, mixed $value): self; - public function blendingColor(): mixed; - - public function setBlendingColor(mixed $color): mixed; + /** + * Set values of given config options + * + * @param mixed $options + * @throws InputException + * @return ConfigInterface + */ + public function setOptions(mixed ...$options): self; } diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index 45166c7e..700a6e2e 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -13,6 +13,57 @@ final class ConfigTest extends BaseTestCase { public function testConstructor(): void { - $this->assertInstanceOf(Config::class, new Config()); + $config = new Config(); + $this->assertInstanceOf(Config::class, $config); + + $this->assertTrue($config->option('autoOrientation')); + $this->assertTrue($config->option('decodeAnimation')); + $this->assertEquals('ffffff00', $config->option('blendingColor')); + + $config = new Config( + autoOrientation: false, + decodeAnimation: false, + blendingColor: 'f00', + ); + $this->assertInstanceOf(Config::class, $config); + + $this->assertFalse($config->option('autoOrientation')); + $this->assertFalse($config->option('decodeAnimation')); + $this->assertEquals('f00', $config->option('blendingColor')); + } + + public function testGetSetOptions(): void + { + $config = new Config(); + $this->assertTrue($config->option('autoOrientation')); + $this->assertTrue($config->option('decodeAnimation')); + $this->assertEquals('ffffff00', $config->option('blendingColor')); + + $result = $config->setOptions( + autoOrientation: false, + decodeAnimation: false, + blendingColor: 'f00', + ); + + $this->assertFalse($config->option('autoOrientation')); + $this->assertFalse($config->option('decodeAnimation')); + $this->assertEquals('f00', $config->option('blendingColor')); + + $this->assertFalse($result->option('autoOrientation')); + $this->assertFalse($result->option('decodeAnimation')); + $this->assertEquals('f00', $result->option('blendingColor')); + + $result = $config->setOption('blendingColor', '000'); + + $this->assertFalse($config->option('autoOrientation')); + $this->assertFalse($config->option('decodeAnimation')); + $this->assertEquals('000', $config->option('blendingColor')); + + $this->assertFalse($result->option('autoOrientation')); + $this->assertFalse($result->option('decodeAnimation')); + $this->assertEquals('000', $result->option('blendingColor')); + + $this->assertNull($config->option('unknown')); + $this->assertEquals('test', $config->option('unknown', 'test')); } } From a086989cc959f15650531a63672c3fbae474b118 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Fri, 10 May 2024 10:26:09 +0200 Subject: [PATCH 07/25] Add constants for config options --- src/Config.php | 4 ++++ src/Drivers/Gd/Decoders/BinaryImageDecoder.php | 3 ++- src/Drivers/Gd/Decoders/FilePathImageDecoder.php | 3 ++- src/Drivers/Gd/Decoders/NativeObjectDecoder.php | 3 ++- src/Drivers/Imagick/Decoders/NativeObjectDecoder.php | 5 +++-- src/Image.php | 4 ++-- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Config.php b/src/Config.php index 5b7a4b86..654af169 100644 --- a/src/Config.php +++ b/src/Config.php @@ -9,6 +9,10 @@ use Intervention\Image\Interfaces\ConfigInterface; class Config implements ConfigInterface { + public const AUTO_ORIENTATION = 'autoOrientation'; + public const DECODE_ANIMATION = 'decodeAnimation'; + public const BLENDING_COLOR = 'blendingColor'; + /** * Create config object instance * diff --git a/src/Drivers/Gd/Decoders/BinaryImageDecoder.php b/src/Drivers/Gd/Decoders/BinaryImageDecoder.php index b775eb09..3630753e 100644 --- a/src/Drivers/Gd/Decoders/BinaryImageDecoder.php +++ b/src/Drivers/Gd/Decoders/BinaryImageDecoder.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Gd\Decoders; +use Intervention\Image\Config; use Intervention\Image\Exceptions\RuntimeException; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\DecoderInterface; @@ -60,7 +61,7 @@ class BinaryImageDecoder extends NativeObjectDecoder implements DecoderInterface } // adjust image orientation - if ($this->driver()->config()->option('autoOrientation') === true) { + if ($this->driver()->config()->option(Config::AUTO_ORIENTATION) === true) { $image->modify(new AlignRotationModifier()); } diff --git a/src/Drivers/Gd/Decoders/FilePathImageDecoder.php b/src/Drivers/Gd/Decoders/FilePathImageDecoder.php index e3f1780e..f518dcf9 100644 --- a/src/Drivers/Gd/Decoders/FilePathImageDecoder.php +++ b/src/Drivers/Gd/Decoders/FilePathImageDecoder.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Gd\Decoders; +use Intervention\Image\Config; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\DecoderInterface; @@ -50,7 +51,7 @@ class FilePathImageDecoder extends NativeObjectDecoder implements DecoderInterfa $image->setExif($this->extractExifData($input)); // adjust image orientation - if ($this->driver()->config()->option('autoOrientation') === true) { + if ($this->driver()->config()->option(Config::AUTO_ORIENTATION) === true) { $image->modify(new AlignRotationModifier()); } diff --git a/src/Drivers/Gd/Decoders/NativeObjectDecoder.php b/src/Drivers/Gd/Decoders/NativeObjectDecoder.php index d0e48381..d4edb64b 100644 --- a/src/Drivers/Gd/Decoders/NativeObjectDecoder.php +++ b/src/Drivers/Gd/Decoders/NativeObjectDecoder.php @@ -7,6 +7,7 @@ namespace Intervention\Image\Drivers\Gd\Decoders; use GdImage; use Intervention\Gif\Decoder as GifDecoder; use Intervention\Gif\Splitter as GifSplitter; +use Intervention\Image\Config; use Intervention\Image\Drivers\Gd\Core; use Intervention\Image\Drivers\Gd\Frame; use Intervention\Image\Exceptions\DecoderException; @@ -60,7 +61,7 @@ class NativeObjectDecoder extends AbstractDecoder protected function decodeGif(mixed $input): ImageInterface { // create non-animated image depending on config - if (!$this->driver()->config()->option('decodeAnimation') === true) { + if (!$this->driver()->config()->option(Config::DECODE_ANIMATION) === true) { $native = match (true) { $this->isGifFormat($input) => @imagecreatefromstring($input), default => @imagecreatefromgif($input), diff --git a/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php b/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php index 4c399c7b..cdcb43ab 100644 --- a/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php +++ b/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Imagick\Decoders; use Imagick; +use Intervention\Image\Config; use Intervention\Image\Drivers\Imagick\Core; use Intervention\Image\Drivers\SpecializableDecoder; use Intervention\Image\Exceptions\DecoderException; @@ -40,12 +41,12 @@ class NativeObjectDecoder extends SpecializableDecoder ); // discard animation depending on config - if (!$this->driver()->config()->option('decodeAnimation') === true) { + if (!$this->driver()->config()->option(Config::DECODE_ANIMATION) === true) { $image->modify(new RemoveAnimationModifier()); } // adjust image rotatation - if ($this->driver()->config()->option('autoOrientation') === true) { + if ($this->driver()->config()->option(Config::AUTO_ORIENTATION) === true) { $image->modify(new AlignRotationModifier()); } diff --git a/src/Image.php b/src/Image.php index ba02c061..4a6a37b9 100644 --- a/src/Image.php +++ b/src/Image.php @@ -408,7 +408,7 @@ final class Image implements ImageInterface public function blendingColor(): ColorInterface { return $this->driver()->handleInput( - $this->driver()->config()->option('blendingColor') + $this->driver()->config()->option(Config::BLENDING_COLOR) ); } @@ -420,7 +420,7 @@ final class Image implements ImageInterface public function setBlendingColor(mixed $color): ImageInterface { $this->driver()->config()->setOption( - 'blendingColor', + Config::BLENDING_COLOR, $this->driver()->handleInput($color) ); From 946cf79f172a2ce9dc5ab3df057f1cc04c4281be Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Fri, 10 May 2024 10:33:52 +0200 Subject: [PATCH 08/25] Add doc blocks --- src/ImageManager.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ImageManager.php b/src/ImageManager.php index 18fcf8dd..64c919a7 100644 --- a/src/ImageManager.php +++ b/src/ImageManager.php @@ -19,6 +19,7 @@ final class ImageManager implements ImageManagerInterface /** * @link https://image.intervention.io/v3/basics/image-manager#create-a-new-image-manager-instance * @param string|DriverInterface $driver + * @param mixed $options */ public function __construct(string|DriverInterface $driver, mixed ...$options) { @@ -30,6 +31,7 @@ final class ImageManager implements ImageManagerInterface * * @link https://image.intervention.io/v3/basics/image-manager * @param string|DriverInterface $driver + * @param mixed $options * @return ImageManager */ public static function withDriver(string|DriverInterface $driver, mixed ...$options): self @@ -113,6 +115,7 @@ final class ImageManager implements ImageManagerInterface * Return driver object * * @param string|DriverInterface $driver + * @param mixed $options * @return DriverInterface */ private static function resolveDriver(string|DriverInterface $driver, mixed ...$options): DriverInterface From 9ec499aa7a8f0ccf4ab7d021b95942ea1499eb11 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Fri, 10 May 2024 11:25:07 +0200 Subject: [PATCH 09/25] Add deprecation markers --- src/Interfaces/ImageInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Interfaces/ImageInterface.php b/src/Interfaces/ImageInterface.php index 49afbb10..ed5320a2 100644 --- a/src/Interfaces/ImageInterface.php +++ b/src/Interfaces/ImageInterface.php @@ -247,7 +247,7 @@ interface ImageInterface extends IteratorAggregate, Countable * Return color that is mixed with transparent areas when converting to a format which * does not support transparency. * - * @link https://image.intervention.io/v3/basics/colors#transparency + * @deprecated * @throws RuntimeException * @return ColorInterface */ @@ -257,7 +257,7 @@ interface ImageInterface extends IteratorAggregate, Countable * Set blending color will have no effect unless image is converted into a format * which does not support transparency. * - * @link https://image.intervention.io/v3/basics/colors#transparency + * @deprecated * @param mixed $color * @throws RuntimeException * @return ImageInterface From ad9ebf22f54149ce885f95c0152e84546c5022f3 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Fri, 10 May 2024 11:49:36 +0200 Subject: [PATCH 10/25] Improve documentation --- src/Interfaces/ImageInterface.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Interfaces/ImageInterface.php b/src/Interfaces/ImageInterface.php index ed5320a2..b70980ed 100644 --- a/src/Interfaces/ImageInterface.php +++ b/src/Interfaces/ImageInterface.php @@ -247,7 +247,7 @@ interface ImageInterface extends IteratorAggregate, Countable * Return color that is mixed with transparent areas when converting to a format which * does not support transparency. * - * @deprecated + * @deprecated Use configuration options of image manager instead * @throws RuntimeException * @return ColorInterface */ @@ -257,7 +257,7 @@ interface ImageInterface extends IteratorAggregate, Countable * Set blending color will have no effect unless image is converted into a format * which does not support transparency. * - * @deprecated + * @deprecated Use configuration options of image manager instead * @param mixed $color * @throws RuntimeException * @return ImageInterface @@ -434,6 +434,7 @@ interface ImageInterface extends IteratorAggregate, Countable /** * Rotate the image to be upright according to exif information * + * @link https://image.intervention.io/v3/modifying/effects#image-orientation-according-to-exif-data * @throws RuntimeException * @return ImageInterface */ @@ -442,7 +443,7 @@ interface ImageInterface extends IteratorAggregate, Countable /** * Draw text on image * - * @ink https://image.intervention.io/v3/modifying/text-fonts + * @link https://image.intervention.io/v3/modifying/text-fonts * @param string $text * @param int $x * @param int $y From 3ad426153a514f15524bcc76206b58589bd74ac9 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Fri, 10 May 2024 15:54:25 +0200 Subject: [PATCH 11/25] Split ImageManagerTest --- tests/Unit/ImageManagerTest.php | 207 ------------------------- tests/Unit/ImageManagerTestGd.php | 125 +++++++++++++++ tests/Unit/ImageManagerTestImagick.php | 125 +++++++++++++++ 3 files changed, 250 insertions(+), 207 deletions(-) delete mode 100644 tests/Unit/ImageManagerTest.php create mode 100644 tests/Unit/ImageManagerTestGd.php create mode 100644 tests/Unit/ImageManagerTestImagick.php diff --git a/tests/Unit/ImageManagerTest.php b/tests/Unit/ImageManagerTest.php deleted file mode 100644 index 2b28e09d..00000000 --- a/tests/Unit/ImageManagerTest.php +++ /dev/null @@ -1,207 +0,0 @@ -assertInstanceOf(ImageManager::class, $manager); - - $manager = new ImageManager(GdDriver::class); - $this->assertInstanceOf(ImageManager::class, $manager); - } - - public function testWithDriver(): void - { - $manager = ImageManager::withDriver(new GdDriver()); - $this->assertInstanceOf(ImageManager::class, $manager); - - $manager = ImageManager::withDriver(GdDriver::class); - $this->assertInstanceOf(ImageManager::class, $manager); - } - - public function testDriver(): void - { - $driver = new GdDriver(); - $manager = ImageManager::withDriver($driver); - $this->assertEquals($driver, $manager->driver()); - } - - public function testDriverStatics(): void - { - $manager = ImageManager::gd(); - $this->assertInstanceOf(ImageManager::class, $manager); - - $manager = ImageManager::imagick(); - $this->assertInstanceOf(ImageManager::class, $manager); - } - - #[RequiresPhpExtension('gd')] - public function testCreateGd(): void - { - $manager = new ImageManager(GdDriver::class); - $image = $manager->create(5, 4); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('gd')] - public function testAnimateGd(): void - { - $manager = new ImageManager(GdDriver::class); - $image = $manager->animate(function ($animation) { - $animation->add($this->getTestResourcePath('red.gif'), .25); - }); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('gd')] - public function testReadGd(): void - { - $manager = new ImageManager(GdDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif')); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('gd')] - public function testReadGdWithDecoderClassname(): void - { - $manager = new ImageManager(GdDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif'), FilePathImageDecoder::class); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('gd')] - public function testReadGdWithDecoderInstance(): void - { - $manager = new ImageManager(GdDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif'), new FilePathImageDecoder()); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('gd')] - public function testReadGdWithDecoderClassnameArray(): void - { - $manager = new ImageManager(GdDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif'), [FilePathImageDecoder::class]); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('gd')] - public function testReadGdWithDecoderInstanceArray(): void - { - $manager = new ImageManager(GdDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif'), [new FilePathImageDecoder()]); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('gd')] - public function testReadGdWithDecoderInstanceArrayMultiple(): void - { - $manager = new ImageManager(GdDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif'), [ - new BinaryImageDecoder(), - new FilePathImageDecoder(), - ]); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('gd')] - public function testReadGdWithRotationAdjustment(): void - { - $manager = new ImageManager(GdDriver::class); - $image = $manager->read($this->getTestResourcePath('orientation.jpg')); - $this->assertColor(255, 255, 255, 255, $image->pickColor(0, 24)); - } - - #[RequiresPhpExtension('imagick')] - public function testCreateImagick(): void - { - $manager = new ImageManager(ImagickDriver::class); - $image = $manager->create(5, 4); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('imagick')] - public function testAnimateImagick(): void - { - $manager = new ImageManager(ImagickDriver::class); - $image = $manager->animate(function ($animation) { - $animation->add($this->getTestResourcePath('red.gif'), .25); - }); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('imagick')] - public function testReadImagick(): void - { - $manager = new ImageManager(ImagickDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif')); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('imagick')] - public function testReadImagickWithDecoderClassname(): void - { - $manager = new ImageManager(ImagickDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif'), FilePathImageDecoder::class); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('imagick')] - public function testReadImagickWithDecoderInstance(): void - { - $manager = new ImageManager(ImagickDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif'), new FilePathImageDecoder()); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('imagick')] - public function testReadImagickWithDecoderClassnameArray(): void - { - $manager = new ImageManager(ImagickDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif'), [FilePathImageDecoder::class]); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('imagick')] - public function testReadImagickWithDecoderInstanceArray(): void - { - $manager = new ImageManager(ImagickDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif'), [new FilePathImageDecoder()]); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('imagick')] - public function testReadImagickWithDecoderInstanceArrayMultiple(): void - { - $manager = new ImageManager(ImagickDriver::class); - $image = $manager->read($this->getTestResourcePath('red.gif'), [ - new BinaryImageDecoder(), - new FilePathImageDecoder(), - ]); - $this->assertInstanceOf(ImageInterface::class, $image); - } - - #[RequiresPhpExtension('imagick')] - public function testReadImagickWithRotationAdjustment(): void - { - $manager = new ImageManager(ImagickDriver::class); - $image = $manager->read($this->getTestResourcePath('orientation.jpg')); - $this->assertColor(255, 255, 255, 255, $image->pickColor(0, 24)); - } -} diff --git a/tests/Unit/ImageManagerTestGd.php b/tests/Unit/ImageManagerTestGd.php new file mode 100644 index 00000000..84007c0e --- /dev/null +++ b/tests/Unit/ImageManagerTestGd.php @@ -0,0 +1,125 @@ +assertInstanceOf(ImageManager::class, $manager); + + $manager = new ImageManager(Driver::class); + $this->assertInstanceOf(ImageManager::class, $manager); + } + + public function testWithDriver(): void + { + $manager = ImageManager::withDriver(new Driver()); + $this->assertInstanceOf(ImageManager::class, $manager); + + $manager = ImageManager::withDriver(Driver::class); + $this->assertInstanceOf(ImageManager::class, $manager); + } + + public function testDriver(): void + { + $driver = new Driver(); + $manager = ImageManager::withDriver($driver); + $this->assertEquals($driver, $manager->driver()); + } + + public function testDriverStatic(): void + { + $manager = ImageManager::gd(); + $this->assertInstanceOf(ImageManager::class, $manager); + } + + public function testCreateGd(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->create(5, 4); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testAnimateGd(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->animate(function ($animation) { + $animation->add($this->getTestResourcePath('red.gif'), .25); + }); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadGd(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif')); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadGdWithDecoderClassname(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif'), FilePathImageDecoder::class); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadGdWithDecoderInstance(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif'), new FilePathImageDecoder()); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadGdWithDecoderClassnameArray(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif'), [FilePathImageDecoder::class]); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadGdWithDecoderInstanceArray(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif'), [new FilePathImageDecoder()]); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadGdWithDecoderInstanceArrayMultiple(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif'), [ + new BinaryImageDecoder(), + new FilePathImageDecoder(), + ]); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadGdWithRotationAdjustment(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('orientation.jpg')); + $this->assertColor(1, 0, 254, 255, $image->pickColor(3, 3)); + } + + public function testReadImagickWithoutRotationAdjustment(): void + { + $manager = new ImageManager(Driver::class, autoOrientation: false); + $image = $manager->read($this->getTestResourcePath('orientation.jpg')); + $this->assertColor(250, 2, 3, 255, $image->pickColor(3, 3)); + } +} diff --git a/tests/Unit/ImageManagerTestImagick.php b/tests/Unit/ImageManagerTestImagick.php new file mode 100644 index 00000000..60773397 --- /dev/null +++ b/tests/Unit/ImageManagerTestImagick.php @@ -0,0 +1,125 @@ +assertInstanceOf(ImageManager::class, $manager); + + $manager = new ImageManager(Driver::class); + $this->assertInstanceOf(ImageManager::class, $manager); + } + + public function testWithDriver(): void + { + $manager = ImageManager::withDriver(new Driver()); + $this->assertInstanceOf(ImageManager::class, $manager); + + $manager = ImageManager::withDriver(Driver::class); + $this->assertInstanceOf(ImageManager::class, $manager); + } + + public function testDriver(): void + { + $driver = new Driver(); + $manager = ImageManager::withDriver($driver); + $this->assertEquals($driver, $manager->driver()); + } + + public function testDriverStatic(): void + { + $manager = ImageManager::imagick(); + $this->assertInstanceOf(ImageManager::class, $manager); + } + + public function testCreateImagick(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->create(5, 4); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testAnimateImagick(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->animate(function ($animation) { + $animation->add($this->getTestResourcePath('red.gif'), .25); + }); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadImagick(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif')); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadImagickWithDecoderClassname(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif'), FilePathImageDecoder::class); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadImagickWithDecoderInstance(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif'), new FilePathImageDecoder()); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadImagickWithDecoderClassnameArray(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif'), [FilePathImageDecoder::class]); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadImagickWithDecoderInstanceArray(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif'), [new FilePathImageDecoder()]); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadImagickWithDecoderInstanceArrayMultiple(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('red.gif'), [ + new BinaryImageDecoder(), + new FilePathImageDecoder(), + ]); + $this->assertInstanceOf(ImageInterface::class, $image); + } + + public function testReadImagickWithRotationAdjustment(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('orientation.jpg')); + $this->assertColor(1, 0, 254, 255, $image->pickColor(3, 3)); + } + + public function testReadImagickWithoutRotationAdjustment(): void + { + $manager = new ImageManager(Driver::class, autoOrientation: false); + $image = $manager->read($this->getTestResourcePath('orientation.jpg')); + $this->assertColor(250, 2, 3, 255, $image->pickColor(3, 3)); + } +} From 64632cd2c388037a60d9b2921f0d3eea4729a17c Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Fri, 10 May 2024 16:30:22 +0200 Subject: [PATCH 12/25] Add tests, improve code --- src/Config.php | 24 ++------- .../Gd/Decoders/BinaryImageDecoder.php | 3 +- .../Gd/Decoders/FilePathImageDecoder.php | 3 +- .../Gd/Decoders/NativeObjectDecoder.php | 3 +- src/Drivers/Gd/Encoders/JpegEncoder.php | 9 +++- .../Modifiers/BlendTransparencyModifier.php | 2 +- src/Drivers/Gd/Modifiers/ContainModifier.php | 4 +- .../Gd/Modifiers/QuantizeColorsModifier.php | 6 ++- .../Imagick/Decoders/NativeObjectDecoder.php | 5 +- src/Drivers/Imagick/Encoders/JpegEncoder.php | 5 +- .../Modifiers/BlendTransparencyModifier.php | 2 +- src/Image.php | 7 ++- src/Interfaces/ConfigInterface.php | 8 --- tests/Unit/ConfigTest.php | 45 ++++++++-------- tests/Unit/Drivers/Gd/ImageTest.php | 14 +++-- tests/Unit/Drivers/Imagick/ImageTest.php | 14 +++-- tests/Unit/ImageManagerTestGd.php | 52 +++++++++++++++---- tests/Unit/ImageManagerTestImagick.php | 52 +++++++++++++++---- 18 files changed, 150 insertions(+), 108 deletions(-) diff --git a/src/Config.php b/src/Config.php index 654af169..26cedc6b 100644 --- a/src/Config.php +++ b/src/Config.php @@ -9,10 +9,6 @@ use Intervention\Image\Interfaces\ConfigInterface; class Config implements ConfigInterface { - public const AUTO_ORIENTATION = 'autoOrientation'; - public const DECODE_ANIMATION = 'decodeAnimation'; - public const BLENDING_COLOR = 'blendingColor'; - /** * Create config object instance * @@ -22,26 +18,12 @@ class Config implements ConfigInterface * @return void */ public function __construct( - protected bool $autoOrientation = true, - protected bool $decodeAnimation = true, - protected mixed $blendingColor = 'ffffff00', + public bool $autoOrientation = true, + public bool $decodeAnimation = true, + public mixed $blendingColor = 'ffffff', ) { } - /** - * {@inheritdoc} - * - * @see ConfigInterface::option() - */ - public function option(string $name, mixed $default = null): mixed - { - if (!property_exists($this, $name)) { - return $default; - } - - return $this->{$name}; - } - /** * {@inheritdoc} * diff --git a/src/Drivers/Gd/Decoders/BinaryImageDecoder.php b/src/Drivers/Gd/Decoders/BinaryImageDecoder.php index 3630753e..eda4f73e 100644 --- a/src/Drivers/Gd/Decoders/BinaryImageDecoder.php +++ b/src/Drivers/Gd/Decoders/BinaryImageDecoder.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Gd\Decoders; -use Intervention\Image\Config; use Intervention\Image\Exceptions\RuntimeException; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\DecoderInterface; @@ -61,7 +60,7 @@ class BinaryImageDecoder extends NativeObjectDecoder implements DecoderInterface } // adjust image orientation - if ($this->driver()->config()->option(Config::AUTO_ORIENTATION) === true) { + if ($this->driver()->config()->autoOrientation === true) { $image->modify(new AlignRotationModifier()); } diff --git a/src/Drivers/Gd/Decoders/FilePathImageDecoder.php b/src/Drivers/Gd/Decoders/FilePathImageDecoder.php index f518dcf9..ace598a5 100644 --- a/src/Drivers/Gd/Decoders/FilePathImageDecoder.php +++ b/src/Drivers/Gd/Decoders/FilePathImageDecoder.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Gd\Decoders; -use Intervention\Image\Config; use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\DecoderInterface; @@ -51,7 +50,7 @@ class FilePathImageDecoder extends NativeObjectDecoder implements DecoderInterfa $image->setExif($this->extractExifData($input)); // adjust image orientation - if ($this->driver()->config()->option(Config::AUTO_ORIENTATION) === true) { + if ($this->driver()->config()->autoOrientation === true) { $image->modify(new AlignRotationModifier()); } diff --git a/src/Drivers/Gd/Decoders/NativeObjectDecoder.php b/src/Drivers/Gd/Decoders/NativeObjectDecoder.php index d4edb64b..14c0058a 100644 --- a/src/Drivers/Gd/Decoders/NativeObjectDecoder.php +++ b/src/Drivers/Gd/Decoders/NativeObjectDecoder.php @@ -7,7 +7,6 @@ namespace Intervention\Image\Drivers\Gd\Decoders; use GdImage; use Intervention\Gif\Decoder as GifDecoder; use Intervention\Gif\Splitter as GifSplitter; -use Intervention\Image\Config; use Intervention\Image\Drivers\Gd\Core; use Intervention\Image\Drivers\Gd\Frame; use Intervention\Image\Exceptions\DecoderException; @@ -61,7 +60,7 @@ class NativeObjectDecoder extends AbstractDecoder protected function decodeGif(mixed $input): ImageInterface { // create non-animated image depending on config - if (!$this->driver()->config()->option(Config::DECODE_ANIMATION) === true) { + if (!$this->driver()->config()->decodeAnimation === true) { $native = match (true) { $this->isGifFormat($input) => @imagecreatefromstring($input), default => @imagecreatefromgif($input), diff --git a/src/Drivers/Gd/Encoders/JpegEncoder.php b/src/Drivers/Gd/Encoders/JpegEncoder.php index dd0a4e71..b408a197 100644 --- a/src/Drivers/Gd/Encoders/JpegEncoder.php +++ b/src/Drivers/Gd/Encoders/JpegEncoder.php @@ -14,7 +14,14 @@ class JpegEncoder extends GenericJpegEncoder implements SpecializedInterface { public function encode(ImageInterface $image): EncodedImage { - $output = Cloner::cloneBlended($image->core()->native(), background: $image->blendingColor()); + $blendingColor = $this->driver()->handleInput( + $this->driver()->config()->blendingColor + ); + + $output = Cloner::cloneBlended( + $image->core()->native(), + background: $blendingColor + ); $data = $this->buffered(function () use ($output) { imageinterlace($output, $this->progressive); diff --git a/src/Drivers/Gd/Modifiers/BlendTransparencyModifier.php b/src/Drivers/Gd/Modifiers/BlendTransparencyModifier.php index 33e3abac..c2cec921 100644 --- a/src/Drivers/Gd/Modifiers/BlendTransparencyModifier.php +++ b/src/Drivers/Gd/Modifiers/BlendTransparencyModifier.php @@ -15,7 +15,7 @@ class BlendTransparencyModifier extends GenericBlendTransparencyModifier impleme { // decode blending color $color = $this->driver()->handleInput( - $this->color ? $this->color : $image->blendingColor() + $this->color ? $this->color : $this->driver()->config()->blendingColor ); foreach ($image as $frame) { diff --git a/src/Drivers/Gd/Modifiers/ContainModifier.php b/src/Drivers/Gd/Modifiers/ContainModifier.php index 148d2c9e..8761111e 100644 --- a/src/Drivers/Gd/Modifiers/ContainModifier.php +++ b/src/Drivers/Gd/Modifiers/ContainModifier.php @@ -23,7 +23,9 @@ class ContainModifier extends GenericContainModifier implements SpecializedInter $crop = $this->getCropSize($image); $resize = $this->getResizeSize($image); $background = $this->driver()->handleInput($this->background); - $blendingColor = $image->blendingColor(); + $blendingColor = $this->driver()->handleInput( + $this->driver()->config()->blendingColor + ); foreach ($image as $frame) { $this->modify($frame, $crop, $resize, $background, $blendingColor); diff --git a/src/Drivers/Gd/Modifiers/QuantizeColorsModifier.php b/src/Drivers/Gd/Modifiers/QuantizeColorsModifier.php index 8d7aa948..36e25496 100644 --- a/src/Drivers/Gd/Modifiers/QuantizeColorsModifier.php +++ b/src/Drivers/Gd/Modifiers/QuantizeColorsModifier.php @@ -31,9 +31,13 @@ class QuantizeColorsModifier extends GenericQuantizeColorsModifier implements Sp $this->driver()->handleInput($this->background) ); + $blendingColor = $this->driver()->handleInput( + $this->driver()->config()->blendingColor + ); + foreach ($image as $frame) { // create new image for color quantization - $reduced = Cloner::cloneEmpty($frame->native(), background: $image->blendingColor()); + $reduced = Cloner::cloneEmpty($frame->native(), background: $blendingColor); // fill with background imagefill($reduced, 0, 0, $background); diff --git a/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php b/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php index cdcb43ab..79b77d41 100644 --- a/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php +++ b/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Intervention\Image\Drivers\Imagick\Decoders; use Imagick; -use Intervention\Image\Config; use Intervention\Image\Drivers\Imagick\Core; use Intervention\Image\Drivers\SpecializableDecoder; use Intervention\Image\Exceptions\DecoderException; @@ -41,12 +40,12 @@ class NativeObjectDecoder extends SpecializableDecoder ); // discard animation depending on config - if (!$this->driver()->config()->option(Config::DECODE_ANIMATION) === true) { + if (!$this->driver()->config()->decodeAnimation === true) { $image->modify(new RemoveAnimationModifier()); } // adjust image rotatation - if ($this->driver()->config()->option(Config::AUTO_ORIENTATION) === true) { + if ($this->driver()->config()->autoOrientation === true) { $image->modify(new AlignRotationModifier()); } diff --git a/src/Drivers/Imagick/Encoders/JpegEncoder.php b/src/Drivers/Imagick/Encoders/JpegEncoder.php index b70f0e36..9484f3ca 100644 --- a/src/Drivers/Imagick/Encoders/JpegEncoder.php +++ b/src/Drivers/Imagick/Encoders/JpegEncoder.php @@ -16,11 +16,14 @@ class JpegEncoder extends GenericJpegEncoder implements SpecializedInterface { $format = 'jpeg'; $compression = Imagick::COMPRESSION_JPEG; + $blendingColor = $this->driver()->handleInput( + $this->driver()->config()->blendingColor + ); // resolve blending color because jpeg has no transparency $background = $this->driver() ->colorProcessor($image->colorspace()) - ->colorToNative($image->blendingColor()); + ->colorToNative($blendingColor); // set alpha value to 1 because Imagick renders // possible full transparent colors as black diff --git a/src/Drivers/Imagick/Modifiers/BlendTransparencyModifier.php b/src/Drivers/Imagick/Modifiers/BlendTransparencyModifier.php index f6586793..72c9e653 100644 --- a/src/Drivers/Imagick/Modifiers/BlendTransparencyModifier.php +++ b/src/Drivers/Imagick/Modifiers/BlendTransparencyModifier.php @@ -15,7 +15,7 @@ class BlendTransparencyModifier extends GenericBlendTransparencyModifier impleme { // decode blending color $color = $this->driver()->handleInput( - $this->color ? $this->color : $image->blendingColor() + $this->color ? $this->color : $this->driver()->config()->blendingColor ); // get imagickpixel from color diff --git a/src/Image.php b/src/Image.php index 4a6a37b9..6e7eb7d6 100644 --- a/src/Image.php +++ b/src/Image.php @@ -408,7 +408,7 @@ final class Image implements ImageInterface public function blendingColor(): ColorInterface { return $this->driver()->handleInput( - $this->driver()->config()->option(Config::BLENDING_COLOR) + $this->driver()->config()->blendingColor ); } @@ -419,9 +419,8 @@ final class Image implements ImageInterface */ public function setBlendingColor(mixed $color): ImageInterface { - $this->driver()->config()->setOption( - Config::BLENDING_COLOR, - $this->driver()->handleInput($color) + $this->driver()->config()->setOptions( + blendingColor: $this->driver()->handleInput($color) ); return $this; diff --git a/src/Interfaces/ConfigInterface.php b/src/Interfaces/ConfigInterface.php index 0fe2ebff..5b312edf 100644 --- a/src/Interfaces/ConfigInterface.php +++ b/src/Interfaces/ConfigInterface.php @@ -8,14 +8,6 @@ use Intervention\Image\Exceptions\InputException; interface ConfigInterface { - /** - * Return value of given config option - * - * @param string $name - * @return mixed - */ - public function option(string $name, mixed $default = null): mixed; - /** * Set value of given config option * diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index 700a6e2e..8a9115e4 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -16,9 +16,9 @@ final class ConfigTest extends BaseTestCase $config = new Config(); $this->assertInstanceOf(Config::class, $config); - $this->assertTrue($config->option('autoOrientation')); - $this->assertTrue($config->option('decodeAnimation')); - $this->assertEquals('ffffff00', $config->option('blendingColor')); + $this->assertTrue($config->autoOrientation); + $this->assertTrue($config->decodeAnimation); + $this->assertEquals('ffffff', $config->blendingColor); $config = new Config( autoOrientation: false, @@ -27,17 +27,17 @@ final class ConfigTest extends BaseTestCase ); $this->assertInstanceOf(Config::class, $config); - $this->assertFalse($config->option('autoOrientation')); - $this->assertFalse($config->option('decodeAnimation')); - $this->assertEquals('f00', $config->option('blendingColor')); + $this->assertFalse($config->autoOrientation); + $this->assertFalse($config->decodeAnimation); + $this->assertEquals('f00', $config->blendingColor); } public function testGetSetOptions(): void { $config = new Config(); - $this->assertTrue($config->option('autoOrientation')); - $this->assertTrue($config->option('decodeAnimation')); - $this->assertEquals('ffffff00', $config->option('blendingColor')); + $this->assertTrue($config->autoOrientation); + $this->assertTrue($config->decodeAnimation); + $this->assertEquals('ffffff', $config->blendingColor); $result = $config->setOptions( autoOrientation: false, @@ -45,25 +45,22 @@ final class ConfigTest extends BaseTestCase blendingColor: 'f00', ); - $this->assertFalse($config->option('autoOrientation')); - $this->assertFalse($config->option('decodeAnimation')); - $this->assertEquals('f00', $config->option('blendingColor')); + $this->assertFalse($config->autoOrientation); + $this->assertFalse($config->decodeAnimation); + $this->assertEquals('f00', $config->blendingColor); - $this->assertFalse($result->option('autoOrientation')); - $this->assertFalse($result->option('decodeAnimation')); - $this->assertEquals('f00', $result->option('blendingColor')); + $this->assertFalse($result->autoOrientation); + $this->assertFalse($result->decodeAnimation); + $this->assertEquals('f00', $result->blendingColor); $result = $config->setOption('blendingColor', '000'); - $this->assertFalse($config->option('autoOrientation')); - $this->assertFalse($config->option('decodeAnimation')); - $this->assertEquals('000', $config->option('blendingColor')); + $this->assertFalse($config->autoOrientation); + $this->assertFalse($config->decodeAnimation); + $this->assertEquals('000', $config->blendingColor); - $this->assertFalse($result->option('autoOrientation')); - $this->assertFalse($result->option('decodeAnimation')); - $this->assertEquals('000', $result->option('blendingColor')); - - $this->assertNull($config->option('unknown')); - $this->assertEquals('test', $config->option('unknown', 'test')); + $this->assertFalse($result->autoOrientation); + $this->assertFalse($result->decodeAnimation); + $this->assertEquals('000', $result->blendingColor); } } diff --git a/tests/Unit/Drivers/Gd/ImageTest.php b/tests/Unit/Drivers/Gd/ImageTest.php index 68530ac6..89c0f550 100644 --- a/tests/Unit/Drivers/Gd/ImageTest.php +++ b/tests/Unit/Drivers/Gd/ImageTest.php @@ -7,7 +7,6 @@ namespace Intervention\Image\Tests\Unit\Drivers\Gd; use Intervention\Image\Analyzers\WidthAnalyzer; use Intervention\Image\Collection; use Intervention\Image\Colors\Hsl\Colorspace; -use Intervention\Image\Colors\Rgb\Color; use Intervention\Image\Drivers\Gd\Core; use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\Drivers\Gd\Frame; @@ -280,17 +279,16 @@ final class ImageTest extends GdTestCase $this->assertInstanceOf(Image::class, $this->image->text('test', 0, 0, new Font())); } - public function testSetGetBlendingColor(): void + public function testBlendTransparencyDefault(): void { $image = $this->readTestImage('gradient.gif'); - $this->assertInstanceOf(ColorInterface::class, $image->blendingColor()); - $this->assertColor(255, 255, 255, 0, $image->blendingColor()); - $result = $image->setBlendingColor(new Color(1, 2, 3, 4)); - $this->assertColor(1, 2, 3, 4, $result->blendingColor()); - $this->assertColor(1, 2, 3, 4, $image->blendingColor()); + $this->assertColor(0, 0, 0, 0, $image->pickColor(1, 0)); + $result = $image->blendTransparency(); + $this->assertColor(255, 255, 255, 255, $image->pickColor(1, 0)); + $this->assertColor(255, 255, 255, 255, $result->pickColor(1, 0)); } - public function testBlendTransparency(): void + public function testBlendTransparencyArgument(): void { $image = $this->readTestImage('gradient.gif'); $this->assertColor(0, 0, 0, 0, $image->pickColor(1, 0)); diff --git a/tests/Unit/Drivers/Imagick/ImageTest.php b/tests/Unit/Drivers/Imagick/ImageTest.php index 777fbc3e..9d05227f 100644 --- a/tests/Unit/Drivers/Imagick/ImageTest.php +++ b/tests/Unit/Drivers/Imagick/ImageTest.php @@ -8,7 +8,6 @@ use Imagick; use Intervention\Image\Analyzers\WidthAnalyzer; use Intervention\Image\Collection; use Intervention\Image\Colors\Cmyk\Colorspace as CmykColorspace; -use Intervention\Image\Colors\Rgb\Color; use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace; use Intervention\Image\Drivers\Imagick\Core; use Intervention\Image\Drivers\Imagick\Driver; @@ -263,17 +262,16 @@ final class ImageTest extends ImagickTestCase $this->assertInstanceOf(Image::class, $this->image->sharpen(12)); } - public function testSetGetBlendingColor(): void + public function testBlendTransparencyDefault(): void { $image = $this->readTestImage('gradient.gif'); - $this->assertInstanceOf(ColorInterface::class, $image->blendingColor()); - $this->assertColor(255, 255, 255, 0, $image->blendingColor()); - $result = $image->setBlendingColor(new Color(1, 2, 3, 4)); - $this->assertColor(1, 2, 3, 4, $result->blendingColor()); - $this->assertColor(1, 2, 3, 4, $image->blendingColor()); + $this->assertColor(0, 0, 0, 0, $image->pickColor(1, 0)); + $result = $image->blendTransparency(); + $this->assertColor(255, 255, 255, 255, $image->pickColor(1, 0)); + $this->assertColor(255, 255, 255, 255, $result->pickColor(1, 0)); } - public function testBlendTransparency(): void + public function testBlendTransparencyArgument(): void { $image = $this->readTestImage('gradient.gif'); $this->assertColor(0, 0, 0, 0, $image->pickColor(1, 0)); diff --git a/tests/Unit/ImageManagerTestGd.php b/tests/Unit/ImageManagerTestGd.php index 84007c0e..78352096 100644 --- a/tests/Unit/ImageManagerTestGd.php +++ b/tests/Unit/ImageManagerTestGd.php @@ -48,14 +48,14 @@ final class ImageManagerTestGd extends BaseTestCase $this->assertInstanceOf(ImageManager::class, $manager); } - public function testCreateGd(): void + public function testCreate(): void { $manager = new ImageManager(Driver::class); $image = $manager->create(5, 4); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testAnimateGd(): void + public function testAnimate(): void { $manager = new ImageManager(Driver::class); $image = $manager->animate(function ($animation) { @@ -64,42 +64,42 @@ final class ImageManagerTestGd extends BaseTestCase $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadGd(): void + public function testRead(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif')); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadGdWithDecoderClassname(): void + public function testReadWithDecoderClassname(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), FilePathImageDecoder::class); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadGdWithDecoderInstance(): void + public function testReadWithDecoderInstance(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), new FilePathImageDecoder()); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadGdWithDecoderClassnameArray(): void + public function testReadWithDecoderClassnameArray(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [FilePathImageDecoder::class]); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadGdWithDecoderInstanceArray(): void + public function testReadWithDecoderInstanceArray(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [new FilePathImageDecoder()]); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadGdWithDecoderInstanceArrayMultiple(): void + public function testReadWithDecoderInstanceArrayMultiple(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [ @@ -109,17 +109,49 @@ final class ImageManagerTestGd extends BaseTestCase $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadGdWithRotationAdjustment(): void + public function testReadWithRotationAdjustment(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('orientation.jpg')); $this->assertColor(1, 0, 254, 255, $image->pickColor(3, 3)); } - public function testReadImagickWithoutRotationAdjustment(): void + public function testReadWithoutRotationAdjustment(): void { $manager = new ImageManager(Driver::class, autoOrientation: false); $image = $manager->read($this->getTestResourcePath('orientation.jpg')); $this->assertColor(250, 2, 3, 255, $image->pickColor(3, 3)); } + + public function testReadAnimation(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('animation.gif')); + $this->assertTrue($image->isAnimated()); + } + + public function testReadAnimationDiscarded(): void + { + $manager = new ImageManager(Driver::class, decodeAnimation: false); + $image = $manager->read($this->getTestResourcePath('animation.gif')); + $this->assertFalse($image->isAnimated()); + } + + public function testApplyBlendingColorDefault(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('blocks.png')); + $result = $image->blendTransparency(); + $this->assertColor(255, 255, 255, 255, $image->pickColor(530, 0)); + $this->assertColor(255, 255, 255, 255, $result->pickColor(530, 0)); + } + + public function testApplyBlendingColorConfigured(): void + { + $manager = new ImageManager(Driver::class, blendingColor: 'ff5500'); + $image = $manager->read($this->getTestResourcePath('blocks.png')); + $result = $image->blendTransparency(); + $this->assertColor(255, 85, 0, 255, $image->pickColor(530, 0)); + $this->assertColor(255, 85, 0, 255, $result->pickColor(530, 0)); + } } diff --git a/tests/Unit/ImageManagerTestImagick.php b/tests/Unit/ImageManagerTestImagick.php index 60773397..422229b1 100644 --- a/tests/Unit/ImageManagerTestImagick.php +++ b/tests/Unit/ImageManagerTestImagick.php @@ -48,14 +48,14 @@ final class ImageManagerTestImagick extends BaseTestCase $this->assertInstanceOf(ImageManager::class, $manager); } - public function testCreateImagick(): void + public function testCreate(): void { $manager = new ImageManager(Driver::class); $image = $manager->create(5, 4); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testAnimateImagick(): void + public function testAnimate(): void { $manager = new ImageManager(Driver::class); $image = $manager->animate(function ($animation) { @@ -64,42 +64,42 @@ final class ImageManagerTestImagick extends BaseTestCase $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadImagick(): void + public function testRead(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif')); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadImagickWithDecoderClassname(): void + public function testReadWithDecoderClassname(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), FilePathImageDecoder::class); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadImagickWithDecoderInstance(): void + public function testReadWithDecoderInstance(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), new FilePathImageDecoder()); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadImagickWithDecoderClassnameArray(): void + public function testReadWithDecoderClassnameArray(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [FilePathImageDecoder::class]); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadImagickWithDecoderInstanceArray(): void + public function testReadWithDecoderInstanceArray(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [new FilePathImageDecoder()]); $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadImagickWithDecoderInstanceArrayMultiple(): void + public function testReadWithDecoderInstanceArrayMultiple(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('red.gif'), [ @@ -109,17 +109,49 @@ final class ImageManagerTestImagick extends BaseTestCase $this->assertInstanceOf(ImageInterface::class, $image); } - public function testReadImagickWithRotationAdjustment(): void + public function testReadWithRotationAdjustment(): void { $manager = new ImageManager(Driver::class); $image = $manager->read($this->getTestResourcePath('orientation.jpg')); $this->assertColor(1, 0, 254, 255, $image->pickColor(3, 3)); } - public function testReadImagickWithoutRotationAdjustment(): void + public function testReadWithoutRotationAdjustment(): void { $manager = new ImageManager(Driver::class, autoOrientation: false); $image = $manager->read($this->getTestResourcePath('orientation.jpg')); $this->assertColor(250, 2, 3, 255, $image->pickColor(3, 3)); } + + public function testReadAnimation(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('animation.gif')); + $this->assertTrue($image->isAnimated()); + } + + public function testReadAnimationDiscarded(): void + { + $manager = new ImageManager(Driver::class, decodeAnimation: false); + $image = $manager->read($this->getTestResourcePath('animation.gif')); + $this->assertFalse($image->isAnimated()); + } + + public function testApplyBlendingColor(): void + { + $manager = new ImageManager(Driver::class); + $image = $manager->read($this->getTestResourcePath('blocks.png')); + $result = $image->blendTransparency(); + $this->assertColor(255, 255, 255, 255, $image->pickColor(530, 0)); + $this->assertColor(255, 255, 255, 255, $result->pickColor(530, 0)); + } + + public function testApplyBlendingColorConfigured(): void + { + $manager = new ImageManager(Driver::class, blendingColor: 'ff5500'); + $image = $manager->read($this->getTestResourcePath('blocks.png')); + $result = $image->blendTransparency(); + $this->assertColor(255, 85, 0, 255, $image->pickColor(530, 0)); + $this->assertColor(255, 85, 0, 255, $result->pickColor(530, 0)); + } } From 59781c723ebcf8c1a25fc2fb15f20310f98ffa40 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Fri, 10 May 2024 17:01:38 +0200 Subject: [PATCH 13/25] Mess with Config object --- src/Config.php | 31 ++++++++++-------------------- src/Drivers/AbstractDriver.php | 5 ++--- src/Interfaces/ConfigInterface.php | 29 ---------------------------- src/Interfaces/DriverInterface.php | 5 +++-- tests/Unit/ConfigTest.php | 6 +----- 5 files changed, 16 insertions(+), 60 deletions(-) delete mode 100644 src/Interfaces/ConfigInterface.php diff --git a/src/Config.php b/src/Config.php index 26cedc6b..4954fdff 100644 --- a/src/Config.php +++ b/src/Config.php @@ -5,9 +5,8 @@ declare(strict_types=1); namespace Intervention\Image; use Intervention\Image\Exceptions\InputException; -use Intervention\Image\Interfaces\ConfigInterface; -class Config implements ConfigInterface +class Config { /** * Create config object instance @@ -25,30 +24,20 @@ class Config implements ConfigInterface } /** - * {@inheritdoc} + * Set values of given config options * - * @see ConfigInterface::setOption() - */ - public function setOption(string $name, mixed $value): self - { - if (!property_exists($this, $name)) { - throw new InputException('Property ' . $name . ' does not exists for ' . $this::class . '.'); - } - - $this->{$name} = $value; - - return $this; - } - - /** - * {@inheritdoc} - * - * @see COnfigInterface::setOptions() + * @param mixed $options + * @throws InputException + * @return Config */ public function setOptions(mixed ...$options): self { foreach ($options as $name => $value) { - $this->setOption($name, $value); + if (!property_exists($this, $name)) { + throw new InputException('Property ' . $name . ' does not exists for ' . $this::class . '.'); + } + + $this->{$name} = $value; } return $this; diff --git a/src/Drivers/AbstractDriver.php b/src/Drivers/AbstractDriver.php index 1ccd79fd..9741bea6 100644 --- a/src/Drivers/AbstractDriver.php +++ b/src/Drivers/AbstractDriver.php @@ -10,7 +10,6 @@ use Intervention\Image\Exceptions\NotSupportedException; use Intervention\Image\InputHandler; use Intervention\Image\Interfaces\AnalyzerInterface; use Intervention\Image\Interfaces\ColorInterface; -use Intervention\Image\Interfaces\ConfigInterface; use Intervention\Image\Interfaces\DecoderInterface; use Intervention\Image\Interfaces\DriverInterface; use Intervention\Image\Interfaces\EncoderInterface; @@ -25,7 +24,7 @@ abstract class AbstractDriver implements DriverInterface /** * Driver options */ - protected ConfigInterface $config; + protected Config $config; /** * @throws DriverException @@ -42,7 +41,7 @@ abstract class AbstractDriver implements DriverInterface * * @see DriverInterface::config() */ - public function config(): ConfigInterface + public function config(): Config { return $this->config; } diff --git a/src/Interfaces/ConfigInterface.php b/src/Interfaces/ConfigInterface.php deleted file mode 100644 index 5b312edf..00000000 --- a/src/Interfaces/ConfigInterface.php +++ /dev/null @@ -1,29 +0,0 @@ -assertFalse($result->decodeAnimation); $this->assertEquals('f00', $result->blendingColor); - $result = $config->setOption('blendingColor', '000'); + $result = $config->setOptions(blendingColor: '000'); $this->assertFalse($config->autoOrientation); $this->assertFalse($config->decodeAnimation); $this->assertEquals('000', $config->blendingColor); - - $this->assertFalse($result->autoOrientation); - $this->assertFalse($result->decodeAnimation); - $this->assertEquals('000', $result->blendingColor); } } From eed36c408101bc738afc0eab94b6e013988ffdab Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Fri, 10 May 2024 19:01:37 +0200 Subject: [PATCH 14/25] Update --- src/Drivers/Gd/Decoders/BinaryImageDecoder.php | 2 +- src/Drivers/Gd/Decoders/FilePathImageDecoder.php | 2 +- src/Drivers/Gd/Decoders/NativeObjectDecoder.php | 2 +- src/Drivers/Imagick/Decoders/NativeObjectDecoder.php | 4 ++-- tests/Unit/ConfigTest.php | 4 ++++ 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Drivers/Gd/Decoders/BinaryImageDecoder.php b/src/Drivers/Gd/Decoders/BinaryImageDecoder.php index eda4f73e..5eddf07f 100644 --- a/src/Drivers/Gd/Decoders/BinaryImageDecoder.php +++ b/src/Drivers/Gd/Decoders/BinaryImageDecoder.php @@ -60,7 +60,7 @@ class BinaryImageDecoder extends NativeObjectDecoder implements DecoderInterface } // adjust image orientation - if ($this->driver()->config()->autoOrientation === true) { + if ($this->driver()->config()->autoOrientation) { $image->modify(new AlignRotationModifier()); } diff --git a/src/Drivers/Gd/Decoders/FilePathImageDecoder.php b/src/Drivers/Gd/Decoders/FilePathImageDecoder.php index ace598a5..fcfd2c64 100644 --- a/src/Drivers/Gd/Decoders/FilePathImageDecoder.php +++ b/src/Drivers/Gd/Decoders/FilePathImageDecoder.php @@ -50,7 +50,7 @@ class FilePathImageDecoder extends NativeObjectDecoder implements DecoderInterfa $image->setExif($this->extractExifData($input)); // adjust image orientation - if ($this->driver()->config()->autoOrientation === true) { + if ($this->driver()->config()->autoOrientation) { $image->modify(new AlignRotationModifier()); } diff --git a/src/Drivers/Gd/Decoders/NativeObjectDecoder.php b/src/Drivers/Gd/Decoders/NativeObjectDecoder.php index 14c0058a..740e58e7 100644 --- a/src/Drivers/Gd/Decoders/NativeObjectDecoder.php +++ b/src/Drivers/Gd/Decoders/NativeObjectDecoder.php @@ -60,7 +60,7 @@ class NativeObjectDecoder extends AbstractDecoder protected function decodeGif(mixed $input): ImageInterface { // create non-animated image depending on config - if (!$this->driver()->config()->decodeAnimation === true) { + if (!$this->driver()->config()->decodeAnimation) { $native = match (true) { $this->isGifFormat($input) => @imagecreatefromstring($input), default => @imagecreatefromgif($input), diff --git a/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php b/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php index 79b77d41..9d65e09e 100644 --- a/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php +++ b/src/Drivers/Imagick/Decoders/NativeObjectDecoder.php @@ -40,12 +40,12 @@ class NativeObjectDecoder extends SpecializableDecoder ); // discard animation depending on config - if (!$this->driver()->config()->decodeAnimation === true) { + if (!$this->driver()->config()->decodeAnimation) { $image->modify(new RemoveAnimationModifier()); } // adjust image rotatation - if ($this->driver()->config()->autoOrientation === true) { + if ($this->driver()->config()->autoOrientation) { $image->modify(new AlignRotationModifier()); } diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index e1502c1f..6f09cdff 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -58,5 +58,9 @@ final class ConfigTest extends BaseTestCase $this->assertFalse($config->autoOrientation); $this->assertFalse($config->decodeAnimation); $this->assertEquals('000', $config->blendingColor); + + $this->assertFalse($result->autoOrientation); + $this->assertFalse($result->decodeAnimation); + $this->assertEquals('000', $result->blendingColor); } } From 4b305587ec4b4165374764e699de4f7607cb0b45 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 11 May 2024 10:02:00 +0200 Subject: [PATCH 15/25] Edit readme --- readme.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/readme.md b/readme.md index 6241fd1f..bb082054 100644 --- a/readme.md +++ b/readme.md @@ -66,22 +66,6 @@ $encoded->save('images/example.jpg'); - GD Library - Imagick PHP extension -## Development & Testing - -This package contains a Docker image for building a test suite and an analysis -container. You must have Docker installed on your system to run all tests using -the following command. - -```bash -docker-compose run --rm --build tests -``` - -Run the static analyzer on the code base. - -```bash -docker-compose run --rm --build analysis -``` - ## Security If you discover any security related issues, please email oliver@intervention.io directly. From 5947dacce0cda1ca978e66fe7e66987c8e6550b6 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 11 May 2024 10:03:27 +0200 Subject: [PATCH 16/25] Edit CONTRIBUTING guideline --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31947a5d..af59d644 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,7 +68,7 @@ testing, it must also run without errors. Check the analyzer by running the following command. ```bash -./vendor/bin/phpstan analyze --memory-limit=512M --level=4 ./src +./vendor/bin/phpstan analyze --memory-limit=512M ./src ``` Or by using the project's Docker environment. From 7a07db6cdc940c52fe84c2a25b02ef3ebb2d2651 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 11 May 2024 10:10:43 +0200 Subject: [PATCH 17/25] Correct method name --- .../Imagick/Modifiers/AlignRotationModifier.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Drivers/Imagick/Modifiers/AlignRotationModifier.php b/src/Drivers/Imagick/Modifiers/AlignRotationModifier.php index 3427d4ad..6b84b304 100644 --- a/src/Drivers/Imagick/Modifiers/AlignRotationModifier.php +++ b/src/Drivers/Imagick/Modifiers/AlignRotationModifier.php @@ -19,30 +19,30 @@ class AlignRotationModifier extends GenericAlignRotationModifier implements Spec break; case Imagick::ORIENTATION_BOTTOMRIGHT: // 3 - $image->core()->native()->rotateimage("#000", 180); + $image->core()->native()->rotateImage("#000", 180); break; case Imagick::ORIENTATION_BOTTOMLEFT: // 4 - $image->core()->native()->rotateimage("#000", 180); + $image->core()->native()->rotateImage("#000", 180); $image->core()->native()->flopImage(); break; case Imagick::ORIENTATION_LEFTTOP: // 5 - $image->core()->native()->rotateimage("#000", -270); + $image->core()->native()->rotateImage("#000", -270); $image->core()->native()->flopImage(); break; case Imagick::ORIENTATION_RIGHTTOP: // 6 - $image->core()->native()->rotateimage("#000", -270); + $image->core()->native()->rotateImage("#000", -270); break; case Imagick::ORIENTATION_RIGHTBOTTOM: // 7 - $image->core()->native()->rotateimage("#000", -90); + $image->core()->native()->rotateImage("#000", -90); $image->core()->native()->flopImage(); break; case Imagick::ORIENTATION_LEFTBOTTOM: // 8 - $image->core()->native()->rotateimage("#000", -90); + $image->core()->native()->rotateImage("#000", -90); break; } From cca6bd34bca676e86422d2248d394b3fd4632bf5 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 11 May 2024 10:59:12 +0200 Subject: [PATCH 18/25] Update exif data after orient() call for GD driver Set exif data of image to top-left orientation, marking the image as aligned and making sure the rotation correction process is not performed again. --- .../Gd/Modifiers/AlignRotationModifier.php | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Drivers/Gd/Modifiers/AlignRotationModifier.php b/src/Drivers/Gd/Modifiers/AlignRotationModifier.php index 38383b97..daf152fb 100644 --- a/src/Drivers/Gd/Modifiers/AlignRotationModifier.php +++ b/src/Drivers/Gd/Modifiers/AlignRotationModifier.php @@ -12,7 +12,7 @@ class AlignRotationModifier extends GenericAlignRotationModifier implements Spec { public function apply(ImageInterface $image): ImageInterface { - return match ($image->exif('IFD0.Orientation')) { + $image = match ($image->exif('IFD0.Orientation')) { 2 => $image->flop(), 3 => $image->rotate(180), 4 => $image->rotate(180)->flop(), @@ -22,5 +22,29 @@ class AlignRotationModifier extends GenericAlignRotationModifier implements Spec 8 => $image->rotate(90), default => $image }; + + return $this->markAligned($image); + } + + /** + * Set exif data of image to top-left orientation, marking the image as + * aligned and making sure the rotation correction process is not + * performed again. + * + * @param ImageInterface $image + * @return ImageInterface + */ + private function markAligned(ImageInterface $image): ImageInterface + { + $exif = $image->exif()->map(function ($item) { + if (is_array($item) && array_key_exists('Orientation', $item)) { + $item['Orientation'] = 1; + return $item; + } + + return $item; + }); + + return $image->setExif($exif); } } From c06e14df1bf8c7c427aa6d1b35b23d055a988a4d Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 11 May 2024 17:44:29 +0200 Subject: [PATCH 19/25] Fix @param order --- src/InputHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InputHandler.php b/src/InputHandler.php index 4e8fc2af..1b490fa9 100644 --- a/src/InputHandler.php +++ b/src/InputHandler.php @@ -65,8 +65,8 @@ class InputHandler implements InputHandlerInterface /** * Create new input handler instance with given decoder classnames * - * @param DriverInterface $driver * @param array $decoders + * @param DriverInterface $driver * @return void */ public function __construct(array $decoders = [], ?DriverInterface $driver = null) From 00b7f8a4127ffa11d45ad4bc864aba0eabec3070 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Wed, 15 May 2024 17:06:06 +0200 Subject: [PATCH 20/25] Add @throws docblock tags --- src/Interfaces/DriverInterface.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Interfaces/DriverInterface.php b/src/Interfaces/DriverInterface.php index cc755859..95503160 100644 --- a/src/Interfaces/DriverInterface.php +++ b/src/Interfaces/DriverInterface.php @@ -44,6 +44,8 @@ interface DriverInterface * Resolve array of classnames or objects into their specialized version for the current driver * * @param array $objects + * @throws NotSupportedException + * @throws DriverException * @return array */ public function specializeMultiple(array $objects): array; @@ -62,6 +64,7 @@ interface DriverInterface * Create new animated image * * @param callable $init + * @throws RuntimeException * @return ImageInterface */ public function createAnimation(callable $init): ImageInterface; From 62a869ca48c9b49acbaca5615551483a89242040 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Wed, 15 May 2024 17:07:45 +0200 Subject: [PATCH 21/25] Add @throws tag --- src/Interfaces/ImageManagerInterface.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Interfaces/ImageManagerInterface.php b/src/Interfaces/ImageManagerInterface.php index 33278194..e79d0235 100644 --- a/src/Interfaces/ImageManagerInterface.php +++ b/src/Interfaces/ImageManagerInterface.php @@ -55,6 +55,7 @@ interface ImageManagerInterface * * @link https://image.intervention.io/v3/basics/instantiation#creating-animations * @param callable $init + * @throws RuntimeException * @return ImageInterface */ public function animate(callable $init): ImageInterface; From a12b646f82545d3f23db2e423ee1c713d43b69de Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Wed, 15 May 2024 17:24:24 +0200 Subject: [PATCH 22/25] Add ability to call Config::setOptions() with single array --- src/Config.php | 30 +++++++++++++++++++++++++++++- tests/Unit/ConfigTest.php | 17 +++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Config.php b/src/Config.php index 4954fdff..4b69209e 100644 --- a/src/Config.php +++ b/src/Config.php @@ -32,7 +32,7 @@ class Config */ public function setOptions(mixed ...$options): self { - foreach ($options as $name => $value) { + foreach ($this->prepareOptions($options) as $name => $value) { if (!property_exists($this, $name)) { throw new InputException('Property ' . $name . ' does not exists for ' . $this::class . '.'); } @@ -42,4 +42,32 @@ class Config return $this; } + + /** + * This method makes it possible to call self::setOptions() with a single + * array instead of named parameters + * + * @param array $options + * @return array + */ + private function prepareOptions(array $options): array + { + if (count($options) === 0) { + return $options; + } + + if (count($options) > 1) { + return $options; + } + + if (!array_key_exists(0, $options)) { + return $options; + } + + if (!is_array($options[0])) { + return $options; + } + + return $options[0]; + } } diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index 6f09cdff..6a1683bd 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -63,4 +63,21 @@ final class ConfigTest extends BaseTestCase $this->assertFalse($result->decodeAnimation); $this->assertEquals('000', $result->blendingColor); } + + public function testSetOptionsWithArray(): void + { + $config = new Config(); + $result = $config->setOptions([ + 'autoOrientation' => false, + 'decodeAnimation' => false, + 'blendingColor' => 'f00', + ]); + + $this->assertFalse($config->autoOrientation); + $this->assertFalse($config->decodeAnimation); + $this->assertEquals('f00', $config->blendingColor); + $this->assertFalse($result->autoOrientation); + $this->assertFalse($result->decodeAnimation); + $this->assertEquals('f00', $result->blendingColor); + } } From 25058825c253d76dffca19ba94d7d375437fdfab Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Wed, 15 May 2024 18:05:34 +0200 Subject: [PATCH 23/25] Add InputHandlerTest --- src/Colors/Cmyk/Color.php | 4 +-- src/Colors/Hsl/Color.php | 4 +-- src/Colors/Hsv/Color.php | 4 +-- src/Colors/Rgb/Color.php | 4 +-- src/Drivers/AbstractDriver.php | 2 +- src/InputHandler.php | 12 +++++++ tests/Unit/InputHandlerTest.php | 62 +++++++++++++++++++++++++++++++++ 7 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 tests/Unit/InputHandlerTest.php diff --git a/src/Colors/Cmyk/Color.php b/src/Colors/Cmyk/Color.php index dabacbbf..5aad25a4 100644 --- a/src/Colors/Cmyk/Color.php +++ b/src/Colors/Cmyk/Color.php @@ -44,9 +44,9 @@ class Color extends AbstractColor */ public static function create(mixed $input): ColorInterface { - return (new InputHandler([ + return InputHandler::withDecoders([ Decoders\StringColorDecoder::class, - ]))->handle($input); + ])->handle($input); } /** diff --git a/src/Colors/Hsl/Color.php b/src/Colors/Hsl/Color.php index 2871963f..e682f9a2 100644 --- a/src/Colors/Hsl/Color.php +++ b/src/Colors/Hsl/Color.php @@ -43,9 +43,9 @@ class Color extends AbstractColor */ public static function create(mixed $input): ColorInterface { - return (new InputHandler([ + return InputHandler::withDecoders([ Decoders\StringColorDecoder::class, - ]))->handle($input); + ])->handle($input); } /** diff --git a/src/Colors/Hsv/Color.php b/src/Colors/Hsv/Color.php index d4580149..9c7f285c 100644 --- a/src/Colors/Hsv/Color.php +++ b/src/Colors/Hsv/Color.php @@ -43,9 +43,9 @@ class Color extends AbstractColor */ public static function create(mixed $input): ColorInterface { - return (new InputHandler([ + return InputHandler::withDecoders([ Decoders\StringColorDecoder::class, - ]))->handle($input); + ])->handle($input); } /** diff --git a/src/Colors/Rgb/Color.php b/src/Colors/Rgb/Color.php index 40d831ac..f97a9241 100644 --- a/src/Colors/Rgb/Color.php +++ b/src/Colors/Rgb/Color.php @@ -53,12 +53,12 @@ class Color extends AbstractColor */ public static function create(mixed $input): ColorInterface { - return (new InputHandler([ + return InputHandler::withDecoders([ Decoders\HexColorDecoder::class, Decoders\StringColorDecoder::class, Decoders\TransparentColorDecoder::class, Decoders\HtmlColornameDecoder::class, - ]))->handle($input); + ])->handle($input); } /** diff --git a/src/Drivers/AbstractDriver.php b/src/Drivers/AbstractDriver.php index 9741bea6..8bf8cf40 100644 --- a/src/Drivers/AbstractDriver.php +++ b/src/Drivers/AbstractDriver.php @@ -53,7 +53,7 @@ abstract class AbstractDriver implements DriverInterface */ public function handleInput(mixed $input, array $decoders = []): ImageInterface|ColorInterface { - return (new InputHandler($decoders, $this))->handle($input); + return InputHandler::withDecoders($decoders, $this)->handle($input); } /** diff --git a/src/InputHandler.php b/src/InputHandler.php index 1b490fa9..4c9eeed7 100644 --- a/src/InputHandler.php +++ b/src/InputHandler.php @@ -75,6 +75,18 @@ class InputHandler implements InputHandlerInterface $this->driver = $driver; } + /** + * Static factory method + * + * @param array $decoders + * @param null|DriverInterface $driver + * @return InputHandler + */ + public static function withDecoders(array $decoders, ?DriverInterface $driver = null): self + { + return new self($decoders, $driver); + } + /** * {@inheritdoc} * diff --git a/tests/Unit/InputHandlerTest.php b/tests/Unit/InputHandlerTest.php new file mode 100644 index 00000000..68a7191c --- /dev/null +++ b/tests/Unit/InputHandlerTest.php @@ -0,0 +1,62 @@ +assertInstanceOf($outputClassname, $handler->handle($input)); + } else { + $this->expectException($outputClassname); + $handler->handle($input); + } + } + + public static function testHandleProvider(): array + { + $base = [ + [null, DecoderException::class], + ['', DecoderException::class], + ['fff', ColorInterface::class], + ['rgba(0, 0, 0, 0)', ColorInterface::class], + ['cmyk(0, 0, 0, 0)', ColorInterface::class], + ['hsv(0, 0, 0)', ColorInterface::class], + ['hsl(0, 0, 0)', ColorInterface::class], + ['transparent', ColorInterface::class], + ['steelblue', ColorInterface::class], + [self::getTestResourcePath(), ImageInterface::class], + [file_get_contents(self::getTestResourcePath()), ImageInterface::class], + ]; + + $data = []; + $drivers = [GdDriver::class, ImagickDriver::class]; + foreach ($drivers as $driver) { + foreach ($base as $line) { + array_unshift($line, $driver); // prepend driver + $data[] = $line; + } + } + + return $data; + } +} From 7b8b9e1e935d50f83dbd544ffaa55c007e1570b8 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Mon, 20 May 2024 10:30:14 +0200 Subject: [PATCH 24/25] Add tests --- tests/Unit/Drivers/Imagick/ImageTest.php | 11 +++++++++++ tests/Unit/InputHandlerTest.php | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/tests/Unit/Drivers/Imagick/ImageTest.php b/tests/Unit/Drivers/Imagick/ImageTest.php index 9d05227f..cc92b349 100644 --- a/tests/Unit/Drivers/Imagick/ImageTest.php +++ b/tests/Unit/Drivers/Imagick/ImageTest.php @@ -23,6 +23,7 @@ use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ResolutionInterface; use Intervention\Image\Interfaces\SizeInterface; use Intervention\Image\Modifiers\GreyscaleModifier; +use Intervention\Image\Origin; use Intervention\Image\Tests\ImagickTestCase; final class ImageTest extends ImagickTestCase @@ -96,6 +97,16 @@ final class ImageTest extends ImagickTestCase $this->assertEquals(10, $this->image->loops()); } + public function testSetGetOrigin(): void + { + $origin = $this->image->origin(); + $this->assertInstanceOf(Origin::class, $origin); + $this->image->setOrigin(new Origin('test1', 'test2')); + $this->assertInstanceOf(Origin::class, $this->image->origin()); + $this->assertEquals('test1', $this->image->origin()->mimetype()); + $this->assertEquals('test2', $this->image->origin()->filePath()); + } + public function testRemoveAnimation(): void { $this->assertTrue($this->image->isAnimated()); diff --git a/tests/Unit/InputHandlerTest.php b/tests/Unit/InputHandlerTest.php index 68a7191c..f231d8f1 100644 --- a/tests/Unit/InputHandlerTest.php +++ b/tests/Unit/InputHandlerTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Intervention\Image\Tests\Unit; +use Intervention\Image\Colors\Rgb\Decoders\HexColorDecoder; use Intervention\Image\Drivers\Gd\Driver as GdDriver; use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver; use Intervention\Image\Exceptions\DecoderException; @@ -59,4 +60,15 @@ class InputHandlerTest extends BaseTestCase return $data; } + + public function testResolveWithoutDriver(): void + { + $handler = new InputHandler([new HexColorDecoder()]); + $result = $handler->handle('fff'); + $this->assertInstanceOf(ColorInterface::class, $result); + + $handler = new InputHandler([HexColorDecoder::class]); + $result = $handler->handle('fff'); + $this->assertInstanceOf(ColorInterface::class, $result); + } } From 27fd48170b56d56429c7c5c5df72194abc14cdb7 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 25 May 2024 10:34:15 +0200 Subject: [PATCH 25/25] Move ColorObjectDecoder & ImageObjectDecoder Move ColorObjectDecoder & ImageObjectDecoder from drivers context because they can operate without any specific drivers information must not be specialized. --- src/Decoders/ColorObjectDecoder.php | 20 +++++++++++++++-- src/Decoders/ImageObjectDecoder.php | 20 +++++++++++++++-- .../Gd/Decoders/ColorObjectDecoder.php | 22 ------------------- .../Gd/Decoders/ImageObjectDecoder.php | 22 ------------------- .../Imagick/Decoders/ColorObjectDecoder.php | 22 ------------------- .../Imagick/Decoders/ImageObjectDecoder.php | 22 ------------------- .../Gd/Decoders/ImageObjectDecoderTest.php | 4 ++-- .../Decoders/ImageObjectDecoderTest.php | 4 ++-- 8 files changed, 40 insertions(+), 96 deletions(-) delete mode 100644 src/Drivers/Gd/Decoders/ColorObjectDecoder.php delete mode 100644 src/Drivers/Gd/Decoders/ImageObjectDecoder.php delete mode 100644 src/Drivers/Imagick/Decoders/ColorObjectDecoder.php delete mode 100644 src/Drivers/Imagick/Decoders/ImageObjectDecoder.php diff --git a/src/Decoders/ColorObjectDecoder.php b/src/Decoders/ColorObjectDecoder.php index f81f7170..60480361 100644 --- a/src/Decoders/ColorObjectDecoder.php +++ b/src/Decoders/ColorObjectDecoder.php @@ -4,8 +4,24 @@ declare(strict_types=1); namespace Intervention\Image\Decoders; -use Intervention\Image\Drivers\SpecializableDecoder; +use Intervention\Image\Drivers\AbstractDecoder; +use Intervention\Image\Exceptions\DecoderException; +use Intervention\Image\Interfaces\ColorInterface; +use Intervention\Image\Interfaces\ImageInterface; -class ColorObjectDecoder extends SpecializableDecoder +class ColorObjectDecoder extends AbstractDecoder { + /** + * {@inheritdoc} + * + * @see DecoderInterface::decode() + */ + public function decode(mixed $input): ImageInterface|ColorInterface + { + if (!is_a($input, ColorInterface::class)) { + throw new DecoderException('Unable to decode input'); + } + + return $input; + } } diff --git a/src/Decoders/ImageObjectDecoder.php b/src/Decoders/ImageObjectDecoder.php index ea01091c..2a98c2e8 100644 --- a/src/Decoders/ImageObjectDecoder.php +++ b/src/Decoders/ImageObjectDecoder.php @@ -4,8 +4,24 @@ declare(strict_types=1); namespace Intervention\Image\Decoders; -use Intervention\Image\Drivers\SpecializableDecoder; +use Intervention\Image\Drivers\AbstractDecoder; +use Intervention\Image\Exceptions\DecoderException; +use Intervention\Image\Interfaces\ImageInterface; +use Intervention\Image\Interfaces\ColorInterface; -class ImageObjectDecoder extends SpecializableDecoder +class ImageObjectDecoder extends AbstractDecoder { + /** + * {@inheritdoc} + * + * @see DecoderInterface::decode() + */ + public function decode(mixed $input): ImageInterface|ColorInterface + { + if (!is_a($input, ImageInterface::class)) { + throw new DecoderException('Unable to decode input'); + } + + return $input; + } } diff --git a/src/Drivers/Gd/Decoders/ColorObjectDecoder.php b/src/Drivers/Gd/Decoders/ColorObjectDecoder.php deleted file mode 100644 index 51abb3c0..00000000 --- a/src/Drivers/Gd/Decoders/ColorObjectDecoder.php +++ /dev/null @@ -1,22 +0,0 @@ -