1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-29 16:50:07 +02:00

Add DriverInterface::handleImageInput() & handleColorInput()

This commit is contained in:
Oliver Vogel
2025-08-01 16:24:53 +02:00
parent bfe5e93420
commit 9bca7b2dbd
26 changed files with 234 additions and 65 deletions

View File

@@ -60,6 +60,50 @@ abstract class AbstractDriver implements DriverInterface
return InputHandler::withDecoders($decoders, $this)->handle($input);
}
/**
* {@inheritdoc}
*
* @see DriverInterface::handleImageInput()
*
* @throws DriverException|DecoderException|NotSupportedException|RuntimeException
*/
public function handleImageInput(mixed $input, array $decoders = []): ImageInterface
{
$handler = count($decoders) ?
InputHandler::withDecoders($decoders, $this) :
InputHandler::withImageDecoders($this);
$result = $handler->handle($input);
if (!($result instanceof ImageInterface)) {
throw new DecoderException('Unable to decode input to instance of ImageInterface.');
}
return $result;
}
/**
* {@inheritdoc}
*
* @see DriverInterface::handleColorInput()
*
* @throws DriverException|DecoderException|NotSupportedException|RuntimeException
*/
public function handleColorInput(mixed $input, array $decoders = []): ColorInterface
{
$handler = count($decoders) ?
InputHandler::withDecoders($decoders, $this) :
InputHandler::withColorDecoders($this);
$result = $handler->handle($input);
if (!($result instanceof ColorInterface)) {
throw new DecoderException('Unable to decode input to instance of ColorInterface.');
}
return $result;
}
/**
* {@inheritdoc}
*

View File

@@ -6,6 +6,7 @@ namespace Intervention\Image\Drivers\Gd;
use Intervention\Image\Exceptions\AnimationException;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Exceptions\RuntimeException;
use Intervention\Image\Image;
use Intervention\Image\Interfaces\AnimationFactoryInterface;
use Intervention\Image\Interfaces\CoreInterface;
@@ -24,11 +25,12 @@ class AnimationFactory implements AnimationFactoryInterface
/**
* @throws AnimationException
* @throws DecoderException
* @throws RuntimeException
*/
public function add(mixed $source, float $delay = 1): self
{
$this->core->add(
$this->driver->handleInput($source)->core()->first()->setDelay($delay)
$this->driver->handleImageInput($source)->core()->first()->setDelay($delay)
);
return $this;

View File

@@ -19,7 +19,7 @@ class JpegEncoder extends GenericJpegEncoder implements SpecializedInterface
*/
public function encode(ImageInterface $image): EncodedImage
{
$backgroundColor = $this->driver()->handleInput(
$backgroundColor = $this->driver()->handleColorInput(
$this->driver()->config()->backgroundColor
);

View File

@@ -18,7 +18,7 @@ class DrawPixelModifier extends GenericDrawPixelModifier implements SpecializedI
public function apply(ImageInterface $image): ImageInterface
{
$color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->driver()->handleInput($this->color)
$this->driver()->handleColorInput($this->color)
);
foreach ($image as $frame) {

View File

@@ -38,7 +38,7 @@ class FillModifier extends GenericFillModifier implements SpecializedInterface
private function color(ImageInterface $image): int
{
return $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->driver()->handleInput($this->color)
$this->driver()->handleColorInput($this->color)
);
}

View File

@@ -20,7 +20,7 @@ class PlaceModifier extends GenericPlaceModifier implements SpecializedInterface
*/
public function apply(ImageInterface $image): ImageInterface
{
$watermark = $this->driver()->handleInput($this->element);
$watermark = $this->driver()->handleImageInput($this->element);
$position = $this->position($image, $watermark);
foreach ($image as $frame) {

View File

@@ -7,6 +7,7 @@ namespace Intervention\Image\Drivers\Imagick;
use Imagick;
use Intervention\Image\Exceptions\AnimationException;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Exceptions\RuntimeException;
use Intervention\Image\Image;
use Intervention\Image\Interfaces\AnimationFactoryInterface;
use Intervention\Image\Interfaces\DriverInterface;
@@ -24,10 +25,11 @@ class AnimationFactory implements AnimationFactoryInterface
/**
* @throws AnimationException
* @throws DecoderException
* @throws RuntimeException
*/
public function add(mixed $source, float $delay = 1): self
{
$native = $this->driver->handleInput($source)->core()->native();
$native = $this->driver->handleImageInput($source)->core()->native();
$native->setImageDelay(intval(round($delay * 100)));
$this->imagick->addImage($native);

View File

@@ -18,7 +18,7 @@ class JpegEncoder extends GenericJpegEncoder implements SpecializedInterface
{
$format = 'JPEG';
$compression = Imagick::COMPRESSION_JPEG;
$backgroundColor = $this->driver()->handleInput(
$backgroundColor = $this->driver()->handleColorInput(
$this->driver()->config()->backgroundColor
);

View File

@@ -14,7 +14,7 @@ class DrawPixelModifier extends GenericDrawPixelModifier implements SpecializedI
public function apply(ImageInterface $image): ImageInterface
{
$color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->driver()->handleInput($this->color)
$this->driver()->handleColorInput($this->color)
);
$pixel = new ImagickDraw();

View File

@@ -16,7 +16,7 @@ class FillModifier extends ModifiersFillModifier implements SpecializedInterface
public function apply(ImageInterface $image): ImageInterface
{
$pixel = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->driver()->handleInput($this->color)
$this->driver()->handleColorInput($this->color)
);
foreach ($image->core()->native() as $frame) {

View File

@@ -13,7 +13,7 @@ class PlaceModifier extends GenericPlaceModifier implements SpecializedInterface
{
public function apply(ImageInterface $image): ImageInterface
{
$watermark = $this->driver()->handleInput($this->element);
$watermark = $this->driver()->handleImageInput($this->element);
$position = $this->position($image, $watermark);
// set opacity of watermark

View File

@@ -59,7 +59,7 @@ class TextModifier extends GenericTextModifier implements SpecializedInterface
*/
private function imagickDrawText(ImageInterface $image, FontInterface $font): ImagickDraw
{
$color = $this->driver()->handleInput($font->color());
$color = $this->driver()->handleColorInput($font->color());
if ($font->hasStrokeEffect() && $color->isTransparent()) {
throw new ColorException(
@@ -87,7 +87,7 @@ class TextModifier extends GenericTextModifier implements SpecializedInterface
return null;
}
$color = $this->driver()->handleInput($font->strokeColor());
$color = $this->driver()->handleColorInput($font->strokeColor());
if ($color->isTransparent()) {
throw new ColorException(

View File

@@ -407,7 +407,7 @@ final class Image implements ImageInterface
*/
public function backgroundColor(): ColorInterface
{
return $this->driver()->handleInput(
return $this->driver()->handleColorInput(
$this->driver()->config()->backgroundColor
);
}
@@ -420,7 +420,7 @@ final class Image implements ImageInterface
public function setBackgroundColor(string|ColorInterface $color): ImageInterface
{
$this->driver()->config()->setOptions(
backgroundColor: $this->driver()->handleInput($color)
backgroundColor: $this->driver()->handleColorInput($color)
);
return $this;

View File

@@ -91,7 +91,7 @@ final class ImageManager implements ImageManagerInterface
*/
public function readPath(string $path): ImageInterface
{
return $this->driver->handleInput($path, [FilePathImageDecoder::class]);
return $this->driver->handleImageInput($path, [FilePathImageDecoder::class]);
}
/**
@@ -101,7 +101,7 @@ final class ImageManager implements ImageManagerInterface
*/
public function readBinary(string $data): ImageInterface
{
return $this->driver->handleInput($data, [BinaryImageDecoder::class]);
return $this->driver->handleImageInput($data, [BinaryImageDecoder::class]);
}
/**
@@ -111,7 +111,7 @@ final class ImageManager implements ImageManagerInterface
*/
public function readBase64(string $data): ImageInterface
{
return $this->driver->handleInput($data, [Base64ImageDecoder::class]);
return $this->driver->handleImageInput($data, [Base64ImageDecoder::class]);
}
/**
@@ -121,7 +121,7 @@ final class ImageManager implements ImageManagerInterface
*/
public function readDataUri(string $uri): ImageInterface
{
return $this->driver->handleInput($uri, [DataUriImageDecoder::class]);
return $this->driver->handleImageInput($uri, [DataUriImageDecoder::class]);
}
/**
@@ -131,7 +131,7 @@ final class ImageManager implements ImageManagerInterface
*/
public function readStream(mixed $stream): ImageInterface
{
return $this->driver->handleInput($stream, [FilePointerImageDecoder::class]);
return $this->driver->handleImageInput($stream, [FilePointerImageDecoder::class]);
}
/**
@@ -141,7 +141,7 @@ final class ImageManager implements ImageManagerInterface
*/
public function readSplFileInfo(SplFileInfo $file): ImageInterface
{
return $this->driver->handleInput($file, [SplFileInfoImageDecoder::class]);
return $this->driver->handleImageInput($file, [SplFileInfoImageDecoder::class]);
}
/**
@@ -151,7 +151,7 @@ final class ImageManager implements ImageManagerInterface
*/
public function read(mixed $input, string|array|DecoderInterface $decoders = []): ImageInterface
{
return $this->driver->handleInput(
return $this->driver->handleImageInput(
$input,
match (true) {
is_string($decoders), is_a($decoders, DecoderInterface::class) => [$decoders],

View File

@@ -33,22 +33,9 @@ use Intervention\Image\Interfaces\InputHandlerInterface;
class InputHandler implements InputHandlerInterface
{
/**
* Decoder classnames in hierarchical order
*
* @var array<string|DecoderInterface>
*/
protected array $decoders = [
public const IMAGE_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,
@@ -58,6 +45,24 @@ class InputHandler implements InputHandlerInterface
EncodedImageObjectDecoder::class,
];
public const COLOR_DECODERS = [
ColorObjectDecoder::class,
RgbHexColorDecoder::class,
RgbStringColorDecoder::class,
CmykStringColorDecoder::class,
HsvStringColorDecoder::class,
HslStringColorDecoder::class,
TransparentColorDecoder::class,
HtmlColornameDecoder::class,
];
/**
* Decoder classnames in hierarchical order
*
* @var array<string|DecoderInterface>
*/
protected array $decoders = [];
/**
* Driver with which the decoder classes are specialized
*/
@@ -71,12 +76,12 @@ class InputHandler implements InputHandlerInterface
*/
public function __construct(array $decoders = [], ?DriverInterface $driver = null)
{
$this->decoders = count($decoders) ? $decoders : $this->decoders;
$this->decoders = count($decoders) ? $decoders : array_merge(self::COLOR_DECODERS, self::IMAGE_DECODERS);
$this->driver = $driver;
}
/**
* Static factory method
* Static factory method to create input handler for both image and color handling
*
* @param array<string|DecoderInterface> $decoders
*/
@@ -85,6 +90,22 @@ class InputHandler implements InputHandlerInterface
return new self($decoders, $driver);
}
/**
* Static factory method to create input handler for image handling
*/
public static function withImageDecoders(?DriverInterface $driver = null): self
{
return new self(self::IMAGE_DECODERS, $driver);
}
/**
* Static factory method to create input handler for color handling
*/
public static function withColorDecoders(?DriverInterface $driver = null): self
{
return new self(self::COLOR_DECODERS, $driver);
}
/**
* {@inheritdoc}
*

View File

@@ -67,6 +67,36 @@ interface DriverInterface
*/
public function handleInput(mixed $input, array $decoders = []): ImageInterface|ColorInterface;
/**
* Handle given image source by decoding it to ImageInterface
*
* Image sources can be as follows:
*
* - Path in filesystem
* - Raw binary image data
* - Base64 encoded image data
* - Data Uri
* - File Pointer resource
* - SplFileInfo object
* - Intervention Image Instance (Intervention\Image\Image)
* - Encoded Intervention Image (Intervention\Image\EncodedImage)
* - Driver-specific image (instance of GDImage or Imagick)
*
* @param array<string|DecoderInterface> $decoders
* @throws DecoderException
* @throws RuntimeException
*/
public function handleImageInput(mixed $input, array $decoders = []): ImageInterface;
/**
* Handle given image source by decoding it to ColorInterface
*
* @param array<string|DecoderInterface> $decoders
* @throws DecoderException
* @throws RuntimeException
*/
public function handleColorInput(mixed $input, array $decoders = []): ColorInterface;
/**
* Return color processor for the given colorspace
*/

View File

@@ -23,6 +23,7 @@ interface ImageManagerInterface
* Create new image instance from given file path
*
* @throws DecoderException
* @throws RuntimeException
*/
public function readPath(string $path): ImageInterface;
@@ -30,6 +31,7 @@ interface ImageManagerInterface
* Create new image instance from given image binary data
*
* @throws DecoderException
* @throws RuntimeException
*/
public function readBinary(string $data): ImageInterface;
@@ -37,6 +39,7 @@ interface ImageManagerInterface
* Create new image instance from given base64 encoded image data
*
* @throws DecoderException
* @throws RuntimeException
*/
public function readBase64(string $data): ImageInterface;
@@ -44,6 +47,7 @@ interface ImageManagerInterface
* Create new image instance from given data uri encoded image data
*
* @throws DecoderException
* @throws RuntimeException
*/
public function readDataUri(string $uri): ImageInterface;
@@ -51,6 +55,7 @@ interface ImageManagerInterface
* Create new image instance from given image stream resource
*
* @throws DecoderException
* @throws RuntimeException
*/
public function readStream(mixed $stream): ImageInterface;
@@ -58,6 +63,7 @@ interface ImageManagerInterface
* Create new image instance from given SplFileInfo image object
*
* @throws DecoderException
* @throws RuntimeException
*/
public function readSplFileInfo(SplFileInfo $file): ImageInterface;

View File

@@ -23,9 +23,9 @@ abstract class AbstractDrawModifier extends SpecializableModifier
public function backgroundColor(): ColorInterface
{
try {
$color = $this->driver()->handleInput($this->drawable()->backgroundColor());
$color = $this->driver()->handleColorInput($this->drawable()->backgroundColor());
} catch (DecoderException) {
return $this->driver()->handleInput('transparent');
return $this->driver()->handleColorInput('transparent');
}
return $color;
@@ -37,9 +37,9 @@ abstract class AbstractDrawModifier extends SpecializableModifier
public function borderColor(): ColorInterface
{
try {
$color = $this->driver()->handleInput($this->drawable()->borderColor());
$color = $this->driver()->handleColorInput($this->drawable()->borderColor());
} catch (DecoderException) {
return $this->driver()->handleInput('transparent');
return $this->driver()->handleColorInput('transparent');
}
return $color;

View File

@@ -36,7 +36,7 @@ class BackgroundModifier extends SpecializableModifier
protected function backgroundColor(DriverInterface $driver): ColorInterface
{
// decode background color
$color = $driver->handleInput(
$color = $driver->handleColorInput(
$this->color ?: $driver->config()->backgroundColor
);

View File

@@ -54,6 +54,6 @@ class ContainModifier extends SpecializableModifier
*/
protected function backgroundColor(): ColorInterface
{
return $this->driver()->handleInput($this->background ?? $this->driver()->config()->backgroundColor);
return $this->driver()->handleColorInput($this->background ?? $this->driver()->config()->backgroundColor);
}
}

View File

@@ -51,6 +51,6 @@ class CropModifier extends SpecializableModifier
*/
protected function backgroundColor(): ColorInterface
{
return $this->driver()->handleInput($this->background ?? $this->driver()->config()->backgroundColor);
return $this->driver()->handleColorInput($this->background ?? $this->driver()->config()->backgroundColor);
}
}

View File

@@ -29,6 +29,8 @@ class QuantizeColorsModifier extends SpecializableModifier
*/
protected function backgroundColor(): ColorInterface
{
return $this->driver()->handleInput($this->background ?? $this->driver()->config()->backgroundColor);
return $this->driver()->handleColorInput(
$this->background ?? $this->driver()->config()->backgroundColor
);
}
}

View File

@@ -56,6 +56,8 @@ class ResizeCanvasModifier extends SpecializableModifier
*/
protected function backgroundColor(): ColorInterface
{
return $this->driver()->handleInput($this->background ?? $this->driver()->config()->backgroundColor);
return $this->driver()->handleColorInput(
$this->background ?? $this->driver()->config()->backgroundColor,
);
}
}

View File

@@ -31,6 +31,8 @@ class RotateModifier extends SpecializableModifier
*/
protected function backgroundColor(): ColorInterface
{
return $this->driver()->handleInput($this->background ?? $this->driver()->config()->backgroundColor);
return $this->driver()->handleColorInput(
$this->background ?? $this->driver()->config()->backgroundColor,
);
}
}

View File

@@ -40,7 +40,7 @@ class TextModifier extends SpecializableModifier
*/
protected function textColor(): ColorInterface
{
$color = $this->driver()->handleInput($this->font->color());
$color = $this->driver()->handleColorInput($this->font->color());
if ($this->font->hasStrokeEffect() && $color->isTransparent()) {
throw new ColorException(
@@ -59,7 +59,7 @@ class TextModifier extends SpecializableModifier
*/
protected function strokeColor(): ColorInterface
{
$color = $this->driver()->handleInput($this->font->strokeColor());
$color = $this->driver()->handleColorInput($this->font->strokeColor());
if ($color->isTransparent()) {
throw new ColorException(

View File

@@ -15,12 +15,14 @@ use Intervention\Image\Drivers\Gd\Driver;
use Intervention\Image\Drivers\Gd\Encoders\PngEncoder;
use Intervention\Image\Drivers\Gd\Modifiers\ResizeModifier;
use Intervention\Image\Encoders\PngEncoder as GenericPngEncoder;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Exceptions\NotSupportedException;
use Intervention\Image\FileExtension;
use Intervention\Image\Format;
use Intervention\Image\Interfaces\AnalyzerInterface;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorProcessorInterface;
use Intervention\Image\Interfaces\DecoderInterface;
use Intervention\Image\Interfaces\DriverInterface;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SpecializableInterface;
@@ -69,32 +71,88 @@ final class DriverTest extends BaseTestCase
$this->assertEquals(2, $image->count());
}
public function testHandleInputImage(): void
/**
* @param array<string|DecoderInterface> $decoders
*/
#[DataProvider('handleImageInputDataProvider')]
#[DataProvider('handleColorInputDataProvider')]
public function testHandleInput(mixed $input, array $decoders, string $resultClassname): void
{
$result = $this->driver->handleInput($this->getTestResourcePath('test.jpg'));
$this->assertInstanceOf(ImageInterface::class, $result);
$this->assertInstanceOf($resultClassname, $this->driver->handleInput($input, $decoders));
}
public function testHandleInputColor(): void
/**
* @param array<string|DecoderInterface> $decoders
*/
#[DataProvider('handleImageInputDataProvider')]
public function testHandleImageInput(mixed $input, array $decoders, string $resultClassname): void
{
$result = $this->driver->handleInput('ffffff');
$this->assertInstanceOf(ColorInterface::class, $result);
$this->assertInstanceOf($resultClassname, $this->driver->handleImageInput($input, $decoders));
}
public function testHandleInputObjects(): void
/**
* @param array<string|DecoderInterface> $decoders
*/
#[DataProvider('handleColorInputDataProvider')]
public function testHandleColorInput(mixed $input, array $decoders, string $resultClassname): void
{
$result = $this->driver->handleInput('ffffff', [
new HexColorDecoder()
]);
$this->assertInstanceOf(ColorInterface::class, $result);
$this->assertInstanceOf($resultClassname, $this->driver->handleColorInput($input, $decoders));
}
public function testHandleInputClassnames(): void
/**
* @param array<string|DecoderInterface> $decoders
*/
#[DataProvider('handleImageInputDataProvider')]
public function testHandleColorInputFail(mixed $input, array $decoders): void
{
$result = $this->driver->handleInput('ffffff', [
HexColorDecoder::class
]);
$this->assertInstanceOf(ColorInterface::class, $result);
$this->expectException(DecoderException::class);
$this->driver->handleColorInput($input, $decoders);
}
/**
* @param array<string|DecoderInterface> $decoders
*/
#[DataProvider('handleColorInputDataProvider')]
public function testHandleImageInputFail(mixed $input, array $decoders): void
{
$this->expectException(DecoderException::class);
$this->driver->handleImageInput($input, $decoders);
}
public static function handleImageInputDataProvider(): Generator
{
yield [
self::getTestResourcePath('test.jpg'),
[],
ImageInterface::class,
];
yield [
self::getTestResourceData('test.jpg'),
[],
ImageInterface::class,
];
}
public static function handleColorInputDataProvider(): Generator
{
yield [
'ffffff',
[],
ColorInterface::class,
];
yield [
'ffffff',
[new HexColorDecoder()],
ColorInterface::class,
];
yield [
'ffffff',
[HexColorDecoder::class],
ColorInterface::class,
];
}
public function testColorProcessor(): void