From 54331415980738349514bc28471636af3537f437 Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Wed, 22 Nov 2023 18:01:33 +0100 Subject: [PATCH] Add Analyzers --- src/Analyzers/AbstractAnalyzer.php | 14 ++++ src/Analyzers/ColorspaceAnalyzer.php | 7 ++ src/Analyzers/HeightAnalyzer.php | 7 ++ src/Analyzers/PixelColorAnalyzer.php | 13 +++ src/Analyzers/PixelColorsAnalyzer.php | 12 +++ src/Analyzers/ProfileAnalyzer.php | 7 ++ src/Analyzers/ResolutionAnalyzer.php | 7 ++ src/Analyzers/WidthAnalyzer.php | 7 ++ src/Drivers/AbstractDriver.php | 8 ++ src/Drivers/DriverAnalyzer.php | 31 +++++++ .../Gd/Analyzers/ColorspaceAnalyzer.php | 15 ++++ src/Drivers/Gd/Analyzers/HeightAnalyzer.php | 14 ++++ .../Gd/Analyzers/PixelColorAnalyzer.php | 34 ++++++++ .../Gd/Analyzers/PixelColorsAnalyzer.php | 23 ++++++ .../Gd/Analyzers/ResolutionAnalyzer.php | 15 ++++ src/Drivers/Gd/Analyzers/WidthAnalyzer.php | 14 ++++ src/Drivers/Gd/ColorProcessor.php | 20 +++++ src/Drivers/Gd/Core.php | 17 ---- src/Drivers/Gd/Driver.php | 11 +++ .../Imagick/Analyzers/ColorspaceAnalyzer.php | 20 +++++ .../Imagick/Analyzers/HeightAnalyzer.php | 14 ++++ .../Imagick/Analyzers/PixelColorAnalyzer.php | 29 +++++++ .../Imagick/Analyzers/PixelColorsAnalyzer.php | 23 ++++++ .../Imagick/Analyzers/ProfileAnalyzer.php | 22 +++++ .../Imagick/Analyzers/ResolutionAnalyzer.php | 15 ++++ .../Imagick/Analyzers/WidthAnalyzer.php | 14 ++++ src/Drivers/Imagick/ColorProcessor.php | 20 +++++ src/Drivers/Imagick/Core.php | 21 ----- src/Drivers/Imagick/Driver.php | 11 +++ .../Imagick/Modifiers/SharpenModifier.php | 2 +- src/Image.php | 82 ++++++++++++++----- src/Interfaces/AnalyzerInterface.php | 8 ++ src/Interfaces/ColorProcessorInterface.php | 1 + src/Interfaces/CoreInterface.php | 3 - src/Interfaces/DriverInterface.php | 2 + src/Interfaces/ImageInterface.php | 1 + 36 files changed, 502 insertions(+), 62 deletions(-) create mode 100644 src/Analyzers/AbstractAnalyzer.php create mode 100644 src/Analyzers/ColorspaceAnalyzer.php create mode 100644 src/Analyzers/HeightAnalyzer.php create mode 100644 src/Analyzers/PixelColorAnalyzer.php create mode 100644 src/Analyzers/PixelColorsAnalyzer.php create mode 100644 src/Analyzers/ProfileAnalyzer.php create mode 100644 src/Analyzers/ResolutionAnalyzer.php create mode 100644 src/Analyzers/WidthAnalyzer.php create mode 100644 src/Drivers/DriverAnalyzer.php create mode 100644 src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php create mode 100644 src/Drivers/Gd/Analyzers/HeightAnalyzer.php create mode 100644 src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php create mode 100644 src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php create mode 100644 src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php create mode 100644 src/Drivers/Gd/Analyzers/WidthAnalyzer.php create mode 100644 src/Drivers/Imagick/Analyzers/ColorspaceAnalyzer.php create mode 100644 src/Drivers/Imagick/Analyzers/HeightAnalyzer.php create mode 100644 src/Drivers/Imagick/Analyzers/PixelColorAnalyzer.php create mode 100644 src/Drivers/Imagick/Analyzers/PixelColorsAnalyzer.php create mode 100644 src/Drivers/Imagick/Analyzers/ProfileAnalyzer.php create mode 100644 src/Drivers/Imagick/Analyzers/ResolutionAnalyzer.php create mode 100644 src/Drivers/Imagick/Analyzers/WidthAnalyzer.php create mode 100644 src/Interfaces/AnalyzerInterface.php diff --git a/src/Analyzers/AbstractAnalyzer.php b/src/Analyzers/AbstractAnalyzer.php new file mode 100644 index 00000000..d633c429 --- /dev/null +++ b/src/Analyzers/AbstractAnalyzer.php @@ -0,0 +1,14 @@ +analyze($this); + } +} diff --git a/src/Analyzers/ColorspaceAnalyzer.php b/src/Analyzers/ColorspaceAnalyzer.php new file mode 100644 index 00000000..6c560a31 --- /dev/null +++ b/src/Analyzers/ColorspaceAnalyzer.php @@ -0,0 +1,7 @@ + 'Modifiers', 'Encoder' => 'Encoders', + 'Analyzer' => 'Analyzers', default => null, }; @@ -28,6 +30,12 @@ abstract class AbstractDriver implements DriverInterface return !empty($dept); })); + if (! class_exists($specialized)) { + throw new MissingDriverComponentException( + $classname . " is not supported by " . $this->id() . " driver." + ); + } + return new $specialized($input, $this); } } diff --git a/src/Drivers/DriverAnalyzer.php b/src/Drivers/DriverAnalyzer.php new file mode 100644 index 00000000..4a9443be --- /dev/null +++ b/src/Drivers/DriverAnalyzer.php @@ -0,0 +1,31 @@ +driver; + } + + /** + * Magic method to read attributes of underlying analyzer + * + * @param string $name + * @return mixed + */ + public function __get(string $name): mixed + { + return $this->analyzer->$name; + } +} diff --git a/src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php b/src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php new file mode 100644 index 00000000..657d08f1 --- /dev/null +++ b/src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php @@ -0,0 +1,15 @@ +core()->native()); + } +} diff --git a/src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php b/src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php new file mode 100644 index 00000000..bf135c4f --- /dev/null +++ b/src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php @@ -0,0 +1,34 @@ +colorAt( + $image->colorspace(), + $image->core()->frame($this->frame_key)->native() + ); + } + + protected function colorAt(ColorspaceInterface $colorspace, GdImage $gd): ColorInterface + { + $index = @imagecolorat($gd, $this->x, $this->y); + + if ($index === false) { + throw new GeometryException( + 'The specified position is not in the valid image area.' + ); + } + + return $this->driver()->colorProcessor($colorspace)->nativeToColor($index); + } +} diff --git a/src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php b/src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php new file mode 100644 index 00000000..ef47c394 --- /dev/null +++ b/src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php @@ -0,0 +1,23 @@ +colorspace(); + + foreach ($image as $frame) { + $colors->push( + parent::colorAt($colorspace, $frame->native()) + ); + } + + return $colors; + } +} diff --git a/src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php b/src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php new file mode 100644 index 00000000..9a147a3e --- /dev/null +++ b/src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php @@ -0,0 +1,15 @@ +core()->native())); + } +} diff --git a/src/Drivers/Gd/Analyzers/WidthAnalyzer.php b/src/Drivers/Gd/Analyzers/WidthAnalyzer.php new file mode 100644 index 00000000..8c710045 --- /dev/null +++ b/src/Drivers/Gd/Analyzers/WidthAnalyzer.php @@ -0,0 +1,14 @@ +core()->native()); + } +} diff --git a/src/Drivers/Gd/ColorProcessor.php b/src/Drivers/Gd/ColorProcessor.php index a2e019f4..9bb5570f 100644 --- a/src/Drivers/Gd/ColorProcessor.php +++ b/src/Drivers/Gd/ColorProcessor.php @@ -6,7 +6,9 @@ use Intervention\Image\Colors\Rgb\Channels\Alpha; 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\Color; use Intervention\Image\Colors\Rgb\Colorspace; +use Intervention\Image\Exceptions\ColorException; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorProcessorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; @@ -31,6 +33,24 @@ class ColorProcessor implements ColorProcessorInterface return ($a << 24) + ($r << 16) + ($g << 8) + $b; } + public function nativeToColor(mixed $value): ColorInterface + { + if (! is_int($value)) { + throw new ColorException("GD driver can only decode colors in integer format."); + } + + $a = ($value >> 24) & 0xFF; + $r = ($value >> 16) & 0xFF; + $g = ($value >> 8) & 0xFF; + $b = $value & 0xFF; + + // convert gd apha integer to intervention alpha integer + // ([opaque]0-127[transparent]) to ([opaque]255-0[transparent]) + $a = (int) static::convertRange($a, 127, 0, 0, 255); + + return new Color($r, $g, $b, $a); + } + /** * Convert input in range (min) to (max) to the corresponding value * in target range (targetMin) to (targetMax). diff --git a/src/Drivers/Gd/Core.php b/src/Drivers/Gd/Core.php index 5f220132..eed6f911 100644 --- a/src/Drivers/Gd/Core.php +++ b/src/Drivers/Gd/Core.php @@ -3,8 +3,6 @@ namespace Intervention\Image\Drivers\Gd; use Intervention\Image\Collection; -use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace; -use Intervention\Image\Interfaces\ColorspaceInterface; use Intervention\Image\Interfaces\CoreInterface; use Intervention\Image\Interfaces\FrameInterface; @@ -17,16 +15,6 @@ class Core extends Collection implements CoreInterface return $this->first()->native(); } - public function width(): int - { - return imagesx($this->native()); - } - - public function height(): int - { - return imagesy($this->native()); - } - public function frame(int $position): FrameInterface { return $this->getAtPosition($position); @@ -43,9 +31,4 @@ class Core extends Collection implements CoreInterface return $this; } - - public function colorspace(): ColorspaceInterface - { - return new RgbColorspace(); - } } diff --git a/src/Drivers/Gd/Driver.php b/src/Drivers/Gd/Driver.php index bb25729a..75cb00d6 100644 --- a/src/Drivers/Gd/Driver.php +++ b/src/Drivers/Gd/Driver.php @@ -5,11 +5,17 @@ namespace Intervention\Image\Drivers\Gd; use Intervention\Image\Drivers\AbstractDriver; use Intervention\Image\Image; use Intervention\Image\Interfaces\ColorInterface; +use Intervention\Image\Interfaces\ColorProcessorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; use Intervention\Image\Interfaces\ImageInterface; class Driver extends AbstractDriver { + public function id(): string + { + return 'GD'; + } + public function createImage(int $width, int $height): ImageInterface { // build new transparent GDImage @@ -33,6 +39,11 @@ class Driver extends AbstractDriver return (new InputHandler())->handle($input); } + public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface + { + return new ColorProcessor($colorspace); + } + public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed { return (new ColorProcessor($colorspace))->colorToNative($color); diff --git a/src/Drivers/Imagick/Analyzers/ColorspaceAnalyzer.php b/src/Drivers/Imagick/Analyzers/ColorspaceAnalyzer.php new file mode 100644 index 00000000..b958ac68 --- /dev/null +++ b/src/Drivers/Imagick/Analyzers/ColorspaceAnalyzer.php @@ -0,0 +1,20 @@ +core()->native()->getImageColorspace()) { + Imagick::COLORSPACE_CMYK => new CmykColorspace(), + default => new RgbColorspace(), + }; + } +} diff --git a/src/Drivers/Imagick/Analyzers/HeightAnalyzer.php b/src/Drivers/Imagick/Analyzers/HeightAnalyzer.php new file mode 100644 index 00000000..5612d310 --- /dev/null +++ b/src/Drivers/Imagick/Analyzers/HeightAnalyzer.php @@ -0,0 +1,14 @@ +core()->native()->getImageHeight(); + } +} diff --git a/src/Drivers/Imagick/Analyzers/PixelColorAnalyzer.php b/src/Drivers/Imagick/Analyzers/PixelColorAnalyzer.php new file mode 100644 index 00000000..366899ec --- /dev/null +++ b/src/Drivers/Imagick/Analyzers/PixelColorAnalyzer.php @@ -0,0 +1,29 @@ +colorAt( + $image->colorspace(), + $image->core()->frame($this->frame_key)->native() + ); + } + + protected function colorAt(ColorspaceInterface $colorspace, Imagick $imagick): ColorInterface + { + return $this->driver() + ->colorProcessor($colorspace) + ->nativeToColor( + $imagick->getImagePixelColor($this->x, $this->x) + ); + } +} diff --git a/src/Drivers/Imagick/Analyzers/PixelColorsAnalyzer.php b/src/Drivers/Imagick/Analyzers/PixelColorsAnalyzer.php new file mode 100644 index 00000000..6a187490 --- /dev/null +++ b/src/Drivers/Imagick/Analyzers/PixelColorsAnalyzer.php @@ -0,0 +1,23 @@ +colorspace(); + + foreach ($image as $frame) { + $colors->push( + parent::colorAt($colorspace, $frame->native()) + ); + } + + return $colors; + } +} diff --git a/src/Drivers/Imagick/Analyzers/ProfileAnalyzer.php b/src/Drivers/Imagick/Analyzers/ProfileAnalyzer.php new file mode 100644 index 00000000..0892c5ae --- /dev/null +++ b/src/Drivers/Imagick/Analyzers/ProfileAnalyzer.php @@ -0,0 +1,22 @@ +core()->native()->getImageProfiles('icc'); + + if (!array_key_exists('icc', $profiles)) { + throw new ColorException('No ICC profile found in image.'); + } + + return new Profile($profiles['icc']); + } +} diff --git a/src/Drivers/Imagick/Analyzers/ResolutionAnalyzer.php b/src/Drivers/Imagick/Analyzers/ResolutionAnalyzer.php new file mode 100644 index 00000000..4da3ca6b --- /dev/null +++ b/src/Drivers/Imagick/Analyzers/ResolutionAnalyzer.php @@ -0,0 +1,15 @@ +core()->native()->getImageResolution()); + } +} diff --git a/src/Drivers/Imagick/Analyzers/WidthAnalyzer.php b/src/Drivers/Imagick/Analyzers/WidthAnalyzer.php new file mode 100644 index 00000000..dce4c82f --- /dev/null +++ b/src/Drivers/Imagick/Analyzers/WidthAnalyzer.php @@ -0,0 +1,14 @@ +core()->native()->getImageWidth(); + } +} diff --git a/src/Drivers/Imagick/ColorProcessor.php b/src/Drivers/Imagick/ColorProcessor.php index c92c6842..fba6f38e 100644 --- a/src/Drivers/Imagick/ColorProcessor.php +++ b/src/Drivers/Imagick/ColorProcessor.php @@ -2,7 +2,9 @@ namespace Intervention\Image\Drivers\Imagick; +use Imagick; use ImagickPixel; +use Intervention\Image\Colors\Cmyk\Colorspace as CmykColorspace; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorProcessorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; @@ -19,4 +21,22 @@ class ColorProcessor implements ColorProcessorInterface (string) $color->convertTo($this->colorspace) ); } + + public function nativeToColor(mixed $native): ColorInterface + { + return match (get_class($this->colorspace)) { + CmykColorspace::class => $this->colorspace->colorFromNormalized([ + $native->getColorValue(Imagick::COLOR_CYAN), + $native->getColorValue(Imagick::COLOR_MAGENTA), + $native->getColorValue(Imagick::COLOR_YELLOW), + $native->getColorValue(Imagick::COLOR_BLACK), + ]), + default => $this->colorspace->colorFromNormalized([ + $native->getColorValue(Imagick::COLOR_RED), + $native->getColorValue(Imagick::COLOR_GREEN), + $native->getColorValue(Imagick::COLOR_BLUE), + $native->getColorValue(Imagick::COLOR_ALPHA), + ]), + }; + } } diff --git a/src/Drivers/Imagick/Core.php b/src/Drivers/Imagick/Core.php index 264d74d8..c457d822 100644 --- a/src/Drivers/Imagick/Core.php +++ b/src/Drivers/Imagick/Core.php @@ -5,10 +5,7 @@ namespace Intervention\Image\Drivers\Imagick; use Imagick; use ImagickException; use Iterator; -use Intervention\Image\Interfaces\ColorspaceInterface; use Intervention\Image\Interfaces\CoreInterface; -use Intervention\Image\Colors\Cmyk\Colorspace as CmykColorspace; -use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace; use Intervention\Image\Exceptions\AnimationException; use Intervention\Image\Interfaces\FrameInterface; @@ -63,16 +60,6 @@ class Core implements CoreInterface, Iterator return $this->imagick; } - public function width(): int - { - return $this->imagick->getImageWidth(); - } - - public function height(): int - { - return $this->imagick->getImageHeight(); - } - public function frame(int $position): FrameInterface { foreach ($this->imagick as $core) { @@ -96,12 +83,4 @@ class Core implements CoreInterface, Iterator return $this; } - - public function colorspace(): ColorspaceInterface - { - return match ($this->imagick->getImageColorspace()) { - Imagick::COLORSPACE_CMYK => new CmykColorspace(), - default => new RgbColorspace(), - }; - } } diff --git a/src/Drivers/Imagick/Driver.php b/src/Drivers/Imagick/Driver.php index e1d46ee7..682d2d45 100644 --- a/src/Drivers/Imagick/Driver.php +++ b/src/Drivers/Imagick/Driver.php @@ -7,11 +7,17 @@ use ImagickPixel; use Intervention\Image\Drivers\AbstractDriver; use Intervention\Image\Image; use Intervention\Image\Interfaces\ColorInterface; +use Intervention\Image\Interfaces\ColorProcessorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; use Intervention\Image\Interfaces\ImageInterface; class Driver extends AbstractDriver { + public function id(): string + { + return 'Imagick'; + } + public function createImage(int $width, int $height): ImageInterface { $background = new ImagickPixel('rgba(0, 0, 0, 0)'); @@ -31,6 +37,11 @@ class Driver extends AbstractDriver return (new InputHandler())->handle($input); } + public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface + { + return new ColorProcessor($colorspace); + } + public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed { return (new ColorProcessor($colorspace))->colorToNative($color); diff --git a/src/Drivers/Imagick/Modifiers/SharpenModifier.php b/src/Drivers/Imagick/Modifiers/SharpenModifier.php index 6f91f929..229b8d37 100644 --- a/src/Drivers/Imagick/Modifiers/SharpenModifier.php +++ b/src/Drivers/Imagick/Modifiers/SharpenModifier.php @@ -10,7 +10,7 @@ class SharpenModifier extends DriverModifier public function apply(ImageInterface $image): ImageInterface { foreach ($image as $frame) { - $frame->data()->unsharpMaskImage(1, 1, $this->amount / 6.25, 0); + $frame->native()->unsharpMaskImage(1, 1, $this->amount / 6.25, 0); } return $image; diff --git a/src/Image.php b/src/Image.php index 0edb8ac5..a63627f2 100644 --- a/src/Image.php +++ b/src/Image.php @@ -3,16 +3,28 @@ namespace Intervention\Image; use Countable; +use Intervention\Image\Analyzers\ColorspaceAnalyzer; +use Intervention\Image\Analyzers\HeightAnalyzer; +use Intervention\Image\Analyzers\PixelColorAnalyzer; +use Intervention\Image\Analyzers\PixelColorsAnalyzer; +use Intervention\Image\Analyzers\ProfileAnalyzer; +use Intervention\Image\Analyzers\ResolutionAnalyzer; +use Intervention\Image\Analyzers\WidthAnalyzer; use Traversable; use Intervention\Image\Geometry\Rectangle; +use Intervention\Image\Interfaces\AnalyzerInterface; use Intervention\Image\Interfaces\CollectionInterface; +use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; use Intervention\Image\Interfaces\CoreInterface; use Intervention\Image\Interfaces\DriverInterface; use Intervention\Image\Interfaces\EncoderInterface; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ModifierInterface; +use Intervention\Image\Interfaces\ProfileInterface; +use Intervention\Image\Interfaces\ResolutionInterface; use Intervention\Image\Interfaces\SizeInterface; +use Intervention\Image\Modifiers\SharpenModifier; class Image implements ImageInterface, Countable { @@ -33,21 +45,6 @@ class Image implements ImageInterface, Countable return $this->core; } - public function width(): int - { - return $this->core->width(); - } - - public function height(): int - { - return $this->core->height(); - } - - public function size(): SizeInterface - { - return new Rectangle($this->width(), $this->height()); - } - public function count(): int { return $this->core->count(); @@ -68,11 +65,6 @@ class Image implements ImageInterface, Countable return $this->core->loops(); } - public function colorspace(): ColorspaceInterface - { - return $this->core->colorspace(); - } - public function exif(?string $query = null): mixed { return is_null($query) ? $this->exif : $this->exif->get($query); @@ -83,8 +75,58 @@ class Image implements ImageInterface, Countable return $this->driver->resolve($modifier)->apply($this); } + public function analyze(AnalyzerInterface $analyzer): mixed + { + return $this->driver->resolve($analyzer)->analyze($this); + } + public function encode(EncoderInterface $encoder): EncodedImage { return $this->driver->resolve($encoder)->encode($this); } + + public function width(): int + { + return $this->analyze(new WidthAnalyzer()); + } + + public function height(): int + { + return $this->analyze(new HeightAnalyzer()); + } + + public function size(): SizeInterface + { + return new Rectangle($this->width(), $this->height()); + } + + public function colorspace(): ColorspaceInterface + { + return $this->analyze(new ColorspaceAnalyzer()); + } + + public function resolution(): ResolutionInterface + { + return $this->analyze(new ResolutionAnalyzer()); + } + + public function pickColor(int $x, int $y, int $frame_key = 0): ColorInterface + { + return $this->analyze(new PixelColorAnalyzer($x, $y, $frame_key)); + } + + public function pickColors(int $x, int $y): CollectionInterface + { + return $this->analyze(new PixelColorsAnalyzer($x, $y)); + } + + public function profile(): ProfileInterface + { + return $this->analyze(new ProfileAnalyzer()); + } + + public function sharpen(int $amount = 10): ImageInterface + { + return $this->modify(new SharpenModifier($amount)); + } } diff --git a/src/Interfaces/AnalyzerInterface.php b/src/Interfaces/AnalyzerInterface.php new file mode 100644 index 00000000..3466afef --- /dev/null +++ b/src/Interfaces/AnalyzerInterface.php @@ -0,0 +1,8 @@ +