1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-31 01:29:51 +02:00

Add Analyzers

This commit is contained in:
Oliver Vogel
2023-11-22 18:01:33 +01:00
parent fca3da7fcd
commit 5433141598
36 changed files with 502 additions and 62 deletions

View File

@@ -0,0 +1,14 @@
<?php
namespace Intervention\Image\Analyzers;
use Intervention\Image\Interfaces\AnalyzerInterface;
use Intervention\Image\Interfaces\ImageInterface;
abstract class AbstractAnalyzer implements AnalyzerInterface
{
public function analyze(ImageInterface $image): mixed
{
return $image->analyze($this);
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Intervention\Image\Analyzers;
class ColorspaceAnalyzer extends AbstractAnalyzer
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Intervention\Image\Analyzers;
class HeightAnalyzer extends AbstractAnalyzer
{
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Intervention\Image\Analyzers;
class PixelColorAnalyzer extends AbstractAnalyzer
{
public function __construct(
public int $x,
public int $y,
public int $frame_key = 0
) {
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Intervention\Image\Analyzers;
class PixelColorsAnalyzer extends AbstractAnalyzer
{
public function __construct(
public int $x,
public int $y
) {
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Intervention\Image\Analyzers;
class ProfileAnalyzer extends AbstractAnalyzer
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Intervention\Image\Analyzers;
class ResolutionAnalyzer extends AbstractAnalyzer
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Intervention\Image\Analyzers;
class WidthAnalyzer extends AbstractAnalyzer
{
}

View File

@@ -2,6 +2,7 @@
namespace Intervention\Image\Drivers;
use Intervention\Image\Exceptions\MissingDriverComponentException;
use Intervention\Image\Interfaces\DriverInterface;
use ReflectionClass;
@@ -17,6 +18,7 @@ abstract class AbstractDriver implements DriverInterface
$department = match ($department) {
'Modifier', 'Writer' => '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);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Intervention\Image\Drivers;
use Intervention\Image\Interfaces\AnalyzerInterface;
use Intervention\Image\Interfaces\DriverInterface;
abstract class DriverAnalyzer implements AnalyzerInterface
{
public function __construct(
protected AnalyzerInterface $analyzer,
protected DriverInterface $driver
) {
}
public function driver(): DriverInterface
{
return $this->driver;
}
/**
* Magic method to read attributes of underlying analyzer
*
* @param string $name
* @return mixed
*/
public function __get(string $name): mixed
{
return $this->analyzer->$name;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Intervention\Image\Drivers\Gd\Analyzers;
use Intervention\Image\Colors\Rgb\Colorspace;
use Intervention\Image\Drivers\DriverAnalyzer;
use Intervention\Image\Interfaces\ImageInterface;
class ColorspaceAnalyzer extends DriverAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
return new Colorspace();
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Intervention\Image\Drivers\Gd\Analyzers;
use Intervention\Image\Drivers\DriverAnalyzer;
use Intervention\Image\Interfaces\ImageInterface;
class HeightAnalyzer extends DriverAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
return imagesy($image->core()->native());
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Intervention\Image\Drivers\Gd\Analyzers;
use GdImage;
use Intervention\Image\Drivers\DriverAnalyzer;
use Intervention\Image\Exceptions\GeometryException;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorspaceInterface;
use Intervention\Image\Interfaces\ImageInterface;
class PixelColorAnalyzer extends DriverAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
return $this->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);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Intervention\Image\Drivers\Gd\Analyzers;
use Intervention\Image\Collection;
use Intervention\Image\Interfaces\ImageInterface;
class PixelColorsAnalyzer extends PixelColorAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
$colors = new Collection();
$colorspace = $image->colorspace();
foreach ($image as $frame) {
$colors->push(
parent::colorAt($colorspace, $frame->native())
);
}
return $colors;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Intervention\Image\Drivers\Gd\Analyzers;
use Intervention\Image\Drivers\DriverAnalyzer;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Resolution;
class ResolutionAnalyzer extends DriverAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
return new Resolution(...imageresolution($image->core()->native()));
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Intervention\Image\Drivers\Gd\Analyzers;
use Intervention\Image\Drivers\DriverAnalyzer;
use Intervention\Image\Interfaces\ImageInterface;
class WidthAnalyzer extends DriverAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
return imagesx($image->core()->native());
}
}

View File

@@ -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).

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -0,0 +1,20 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Analyzers;
use Imagick;
use Intervention\Image\Drivers\DriverAnalyzer;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Colors\Cmyk\Colorspace as CmykColorspace;
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
class ColorspaceAnalyzer extends DriverAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
return match ($image->core()->native()->getImageColorspace()) {
Imagick::COLORSPACE_CMYK => new CmykColorspace(),
default => new RgbColorspace(),
};
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Analyzers;
use Intervention\Image\Drivers\DriverAnalyzer;
use Intervention\Image\Interfaces\ImageInterface;
class HeightAnalyzer extends DriverAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
return $image->core()->native()->getImageHeight();
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Analyzers;
use Imagick;
use Intervention\Image\Drivers\DriverAnalyzer;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorspaceInterface;
use Intervention\Image\Interfaces\ImageInterface;
class PixelColorAnalyzer extends DriverAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
return $this->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)
);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Analyzers;
use Intervention\Image\Collection;
use Intervention\Image\Interfaces\ImageInterface;
class PixelColorsAnalyzer extends PixelColorAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
$colors = new Collection();
$colorspace = $image->colorspace();
foreach ($image as $frame) {
$colors->push(
parent::colorAt($colorspace, $frame->native())
);
}
return $colors;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Analyzers;
use Intervention\Image\Colors\Profile;
use Intervention\Image\Drivers\DriverAnalyzer;
use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Interfaces\ImageInterface;
class ProfileAnalyzer extends DriverAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
$profiles = $image->core()->native()->getImageProfiles('icc');
if (!array_key_exists('icc', $profiles)) {
throw new ColorException('No ICC profile found in image.');
}
return new Profile($profiles['icc']);
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Analyzers;
use Intervention\Image\Drivers\DriverAnalyzer;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Resolution;
class ResolutionAnalyzer extends DriverAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
return new Resolution(...$image->core()->native()->getImageResolution());
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Analyzers;
use Intervention\Image\Drivers\DriverAnalyzer;
use Intervention\Image\Interfaces\ImageInterface;
class WidthAnalyzer extends DriverAnalyzer
{
public function analyze(ImageInterface $image): mixed
{
return $image->core()->native()->getImageWidth();
}
}

View File

@@ -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),
]),
};
}
}

View File

@@ -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(),
};
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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));
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Intervention\Image\Interfaces;
interface AnalyzerInterface
{
public function analyze(ImageInterface $image): mixed;
}

View File

@@ -5,4 +5,5 @@ namespace Intervention\Image\Interfaces;
interface ColorProcessorInterface
{
public function colorToNative(ColorInterface $color);
public function nativeToColor(mixed $native): ColorInterface;
}

View File

@@ -8,10 +8,7 @@ interface CoreInterface extends Traversable
{
public function native();
public function count(): int;
public function width(): int;
public function height(): int;
public function frame(int $position): FrameInterface;
public function loops(): int;
public function setLoops(int $loops): CoreInterface;
public function colorspace(): ColorspaceInterface;
}

View File

@@ -4,8 +4,10 @@ namespace Intervention\Image\Interfaces;
interface DriverInterface
{
public function id(): string;
public function resolve(object $input): object;
public function createImage(int $width, int $height): ImageInterface;
public function handleInput(mixed $input): ImageInterface|ColorInterface;
public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface;
public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed;
}

View File

@@ -15,6 +15,7 @@ interface ImageInterface extends IteratorAggregate, Countable
public function size(): SizeInterface;
public function encode(EncoderInterface $encoder): EncodedImage;
public function modify(ModifierInterface $modifier): ImageInterface;
public function analyze(AnalyzerInterface $analyzer): mixed;
public function isAnimated(): bool;
public function loops(): int;
public function exif(?string $query = null): mixed;