├── .codecov.yml
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .php-cs-fixer.dist.php
├── LICENSE
├── README.md
├── composer.json
├── phpstan.neon
├── phpunit.xml.dist
└── src
├── Image
├── Dimensions.php
├── Hash
│ └── ThumbHash.php
├── Transformer.php
└── Transformer
│ ├── FileTransformer.php
│ ├── GdImageTransformer.php
│ ├── ImagickTransformer.php
│ ├── ImagineTransformer.php
│ ├── InterventionTransformer.php
│ ├── MultiTransformer.php
│ └── SpatieImageTransformer.php
└── ImageFileInfo.php
/.codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | project:
4 | default:
5 | target: auto
6 | threshold: 1%
7 | patch:
8 | default:
9 | target: auto
10 | threshold: 50%
11 |
12 | comment: false
13 | github_checks:
14 | annotations: false
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.yml]
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /.github export-ignore
2 | /tests export-ignore
3 | phpunit.dist.xml export-ignore
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /composer.lock
2 | /phpunit.xml
3 | /vendor/
4 | /build/
5 | /var/
6 | /.php-cs-fixer.cache
7 | /.phpunit.result.cache
8 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | [!NOTE]
18 | > `Zenstruck\ImageFileInfo` extends `\SplFileInfo`.
19 |
20 | ```php
21 | use Zenstruck\ImageFileInfo;
22 |
23 | $image = ImageFileInfo::wrap('some/local.jpg'); // create from local file
24 | $image = ImageFileInfo::from($resource); // create from resource/stream (in a temp file)
25 |
26 | // dimensional information
27 | $image->dimensions()->height(); // int
28 | $image->dimensions()->width(); // int
29 | $image->dimensions()->aspectRatio(); // float
30 | $image->dimensions()->pixels(); // int
31 | $image->dimensions()->isSquare(); // bool
32 | $image->dimensions()->isLandscape(); // bool
33 | $image->dimensions()->isPortrait(); // bool
34 |
35 | // other metadata
36 | $image->mimeType(); // string (ie "image/jpeg")
37 | $image->guessExtension(); // string - the extension if available or guess from mime-type
38 | $image->iptc(); // array - IPTC data (if the image supports)
39 | $image->exif(); // array - EXIF data (if the image supports)
40 |
41 | // utility
42 | $image->refresh(); // self - clear any cached metadata
43 | $image->delete(); // void - delete the image file
44 |
45 | // access any \SplFileInfo methods
46 | $image->getMTime();
47 | ```
48 |
49 | > [!NOTE]
50 | > Images created with `ImageFileInfo::from()` are created in unique temporary files
51 | > and deleted at the end of the script.
52 |
53 | ### Transformations
54 |
55 | The following transformers are available:
56 |
57 | - [GD](https://www.php.net/manual/en/book.image.php)
58 | - [Imagick](https://www.php.net/manual/en/book.imagick.php)
59 | - [intervention\image](https://github.com/Intervention/image)
60 | - [imagine\imagine](https://github.com/php-imagine/Imagine)
61 | - [spatie\image](https://github.com/spatie/image)
62 |
63 | To use the desired transformer, type-hint the first parameter of the callable
64 | passed to `Zenstruck\ImageFileInfo::transform()` with the desired transformer's
65 | _image object_:
66 |
67 | - **GD**: `\GdImage`
68 | - **Imagick**: `\Imagick`
69 | - **intervention\image**: `Intervention\Image\Image`
70 | - **imagine\imagine**: `Imagine\Image\ImageInterface`
71 | - **spatie\image**: `Spatie\Image\Image`
72 |
73 | > [!NOTE]
74 | > The return value of the callable must be the same as the passed parameter.
75 |
76 | The following example uses `\GdImage` but any of the above type-hints can be used.
77 |
78 | ```php
79 | /** @var Zenstruck\ImageFileInfo $image */
80 |
81 | $resized = $image->transform(function(\GdImage $image): \GdImage {
82 | // perform desired manipulations...
83 |
84 | return $image;
85 | }); // a new temporary Zenstruck\ImageFileInfo instance (deleted at the end of the script)
86 |
87 | // configure the format
88 | $resized = $image->transform(
89 | function(\GdImage $image): \GdImage {
90 | // perform desired manipulations...
91 |
92 | return $image;
93 | },
94 | ['format' => 'png']
95 | );
96 |
97 | // configure the path for the created file
98 | $resized = $image->transform(
99 | function(\GdImage $image): \GdImage {
100 | // perform desired manipulations...
101 |
102 | return $image;
103 | },
104 | ['output' => 'path/to/file.jpg']
105 | );
106 | ```
107 |
108 | #### Transform "In Place"
109 |
110 | ```php
111 | /** @var Zenstruck\ImageFileInfo $image */
112 |
113 | $resized = $image->transformInPlace(function(\GdImage $image): \GdImage {
114 | // perform desired manipulations...
115 |
116 | return $image;
117 | }); // overwrites the original image file
118 | ```
119 |
120 | #### Filter Objects
121 |
122 | Both _Imagine_ and _Intervention_ have the concept of _filters_. These are objects
123 | that can be passed directly to `transform()` and `transformInPlace()`:
124 |
125 | ```php
126 | /** @var Imagine\Filter\FilterInterface $imagineFilter */
127 | /** @var Intervention\Image\Filters\FilterInterface|Intervention\Image\Interfaces\ModifierInterface $interventionFilter */
128 | /** @var Zenstruck\ImageFileInfo $image */
129 |
130 | $transformed = $image->transform($imagineFilter);
131 | $transformed = $image->transform($interventionFilter);
132 |
133 | $image->transformInPlace($imagineFilter);
134 | $image->transformInPlace($interventionFilter);
135 | ```
136 |
137 | ##### Custom Filter Objects
138 |
139 | Because `transform()` and `transformInPlace()` accept any callable, you can wrap complex
140 | transformations into invokable _filter objects_:
141 |
142 | ```php
143 | class GreyscaleThumbnail
144 | {
145 | public function __construct(private int $width, private int $height)
146 | {
147 | }
148 |
149 | public function __invoke(\GdImage $image): \GdImage
150 | {
151 | // greyscale and resize to $this->width/$this->height
152 |
153 | return $image;
154 | }
155 | }
156 | ```
157 |
158 | To use, pass a new instance to `transform()` or `transformInPlace()`:
159 |
160 | ```php
161 | /** @var Zenstruck\ImageFileInfo $image */
162 |
163 | $thumbnail = $image->transform(new GreyscaleThumbnail(200, 200));
164 |
165 | $image->transformInPlace(new GreyscaleThumbnail(200, 200));
166 | ```
167 |
168 | #### Transformation Object
169 |
170 | `Zenstruck\ImageFileInfo::as()` returns a new instance of the desired
171 | transformation library's _image object_:
172 |
173 | ```php
174 | use Imagine\Image\ImageInterface;
175 |
176 | /** @var Zenstruck\ImageFileInfo $image */
177 |
178 | $image->as(ImageInterface::class); // ImageInterface object for this image
179 | $image->as(\Imagick::class); // \Imagick object for this image
180 | ```
181 |
182 | ### ThumbHash
183 |
184 | > A very compact representation of an image placeholder. Store it inline with your data and show
185 | > it while the real image is loading for a smoother loading experience.
186 | >
187 | > **-- [evanw.github.io/thumbhash](https://evanw.github.io/thumbhash/)**
188 |
189 | > [!NOTE]
190 | > [`srwiez/thumbhash`](https://github.com/SRWieZ/thumbhash) is required for this feature
191 | > (install with `composer require srwiez/thumbhash`).
192 |
193 | > [!NOTE]
194 | > [`Imagick`](https://www.php.net/manual/en/book.imagick.php) is required for this feature.
195 |
196 | #### Generate from Image
197 |
198 | ```php
199 | use Zenstruck\Image\Hash\ThumbHash;
200 |
201 | /** @var Zenstruck\ImageFileInfo $image */
202 |
203 | $thumbHash = $image->thumbHash(); // ThumbHash
204 |
205 | $thumbHash->dataUri(); // string - the ThumbHash as a data-uri
206 | $thumbHash->approximateAspectRatio(); // float - the approximate aspect ratio
207 | $thumbHash->key(); // string - small string representation that can be cached/stored in a database
208 | ```
209 |
210 | > [!CAUTION]
211 | > Generating from an image can be slow depending on the size of the source image. It is recommended
212 | > to cache the data-uri and/or key for subsequent requests of the same ThumbHash image.
213 |
214 | #### Generate from Key
215 |
216 | When generating from an image, the `ThumbHash::key()` method returns a small string that
217 | can be stored for later use. This key can be used to generate the ThumbHash without
218 | needing to re-process the image.
219 |
220 | ```php
221 | use Zenstruck\Image\Hash\ThumbHash;
222 |
223 | /** @var string $key */
224 |
225 | $thumbHash = ThumbHash::fromKey($key); // ThumbHash
226 |
227 | $thumbHash->dataUri(); // string - the ThumbHash as a data-uri
228 | $thumbHash->approximateAspectRatio(); // float - the approximate aspect ratio
229 | ```
230 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zenstruck/image",
3 | "description": "Image file wrapper with generic transformation support.",
4 | "homepage": "https://github.com/zenstruck/image",
5 | "type": "library",
6 | "license": "MIT",
7 | "keywords": ["image", "transformation", "manipulation", "gd", "imagick", "imagine", "intervention"],
8 | "authors": [
9 | {
10 | "name": "Kevin Bond",
11 | "email": "kevinbond@gmail.com"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=8.0",
16 | "symfony/polyfill-php81": "^1.26",
17 | "zenstruck/temp-file": "^1.0"
18 | },
19 | "require-dev": {
20 | "imagine/imagine": "^1.3",
21 | "intervention/image": "^2.7|^3.0",
22 | "phpstan/phpstan": "^1.4",
23 | "phpunit/phpunit": "^9.6.19",
24 | "psr/container": "^1.0|^2.0",
25 | "spatie/image": "^2.0|^3.2",
26 | "srwiez/thumbhash": "^1.2",
27 | "symfony/phpunit-bridge": "^6.1|^7.0",
28 | "symfony/var-dumper": "^5.4|^6.0|^7.0"
29 | },
30 | "config": {
31 | "preferred-install": "dist",
32 | "sort-packages": true
33 | },
34 | "autoload": {
35 | "psr-4": { "Zenstruck\\": ["src/"] }
36 | },
37 | "autoload-dev": {
38 | "psr-4": { "Zenstruck\\Image\\Tests\\": ["tests/"] }
39 | },
40 | "suggest": {
41 | "imagine/imagine": "To use the Imagine image transformer.",
42 | "intervention/image": "To use the Intervention image transformer.",
43 | "srwiez/thumbhash": "To generate ThumbHashes."
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 8
3 | paths:
4 | - src
5 | ignoreErrors:
6 | - '#no value type specified in iterable type array#'
7 |
8 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | tests
19 |
20 |
21 |
22 |
23 |
24 | src
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/Image/Dimensions.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Zenstruck\Image;
13 |
14 | /**
15 | * @author Kevin Bond
16 | */
17 | final class Dimensions implements \JsonSerializable
18 | {
19 | /** @var array{0:int,1:int} */
20 | private array $normalizedValues;
21 |
22 | /**
23 | * @param array{0:int,1:int}|array{width:int,height:int}|callable():(array{0:int,1:int}|array{width:int,height:int}) $values
24 | */
25 | public function __construct(private $values)
26 | {
27 | }
28 |
29 | public function jsonSerialize(): array
30 | {
31 | return [
32 | 'width' => $this->width(),
33 | 'height' => $this->height(),
34 | ];
35 | }
36 |
37 | public function width(): int
38 | {
39 | return $this->values()[0];
40 | }
41 |
42 | public function height(): int
43 | {
44 | return $this->values()[1];
45 | }
46 |
47 | public function aspectRatio(): float
48 | {
49 | return $this->width() / $this->height();
50 | }
51 |
52 | public function pixels(): int
53 | {
54 | return $this->width() * $this->height();
55 | }
56 |
57 | public function isSquare(): bool
58 | {
59 | return $this->width() === $this->height();
60 | }
61 |
62 | public function isPortrait(): bool
63 | {
64 | return $this->height() > $this->width();
65 | }
66 |
67 | public function isLandscape(): bool
68 | {
69 | return $this->width() > $this->height();
70 | }
71 |
72 | /**
73 | * @return array{0:int,1:int}
74 | */
75 | private function values(): array
76 | {
77 | if (isset($this->normalizedValues)) {
78 | return $this->normalizedValues;
79 | }
80 |
81 | if (\is_callable($this->values)) {
82 | $this->values = ($this->values)();
83 | }
84 |
85 | return $this->normalizedValues = [
86 | $this->values['width'] ?? $this->values[0] ?? throw new \InvalidArgumentException('Could not determine width.'),
87 | $this->values['height'] ?? $this->values[1] ?? throw new \InvalidArgumentException('Could not determine height.'),
88 | ];
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Image/Hash/ThumbHash.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Zenstruck\Image\Hash;
13 |
14 | use Zenstruck\ImageFileInfo;
15 |
16 | /**
17 | * @author Kevin Bond
18 | */
19 | final class ThumbHash
20 | {
21 | /** @var list */
22 | private array $hash;
23 | private string $dataUri;
24 |
25 | private function __construct(private \SplFileInfo|string $source)
26 | {
27 | if (!\class_exists(\Thumbhash\Thumbhash::class)) {
28 | throw new \LogicException(\sprintf('"%s" requires the "srwiez/thumbhash" package to be installed. Run "composer require srwiez/thumbhash".', self::class));
29 | }
30 |
31 | if (!\class_exists(\Imagick::class)) {
32 | throw new \LogicException(\sprintf('"%s" requires the "imagick" extension to be installed.', self::class));
33 | }
34 | }
35 |
36 | /**
37 | * Create from either an \SplFileInfo or a "key" string.
38 | */
39 | public static function from(\SplFileInfo|string $source): self
40 | {
41 | return new self($source);
42 | }
43 |
44 | public function dataUri(): string
45 | {
46 | return $this->dataUri ??= \Thumbhash\Thumbhash::toDataURL($this->hash());
47 | }
48 |
49 | public function key(): string
50 | {
51 | if (\is_string($this->source)) {
52 | return $this->source;
53 | }
54 |
55 | return $this->source = \Thumbhash\Thumbhash::convertHashToString($this->hash());
56 | }
57 |
58 | /**
59 | * @return list
60 | */
61 | public function hash(): array
62 | {
63 | if (isset($this->hash)) {
64 | return $this->hash;
65 | }
66 |
67 | if (\is_string($this->source)) {
68 | return $this->hash = \Thumbhash\Thumbhash::convertStringToHash($this->source);
69 | }
70 |
71 | [$width, $height, $pixels] = self::extractSizeAndPixels($this->source);
72 |
73 | return $this->hash = \Thumbhash\Thumbhash::RGBAToHash($width, $height, $pixels);
74 | }
75 |
76 | public function approximateAspectRatio(): float
77 | {
78 | return \Thumbhash\Thumbhash::toApproximateAspectRatio($this->hash());
79 | }
80 |
81 | /**
82 | * @see \Thumbhash\extract_size_and_pixels_with_imagick()
83 | *
84 | * @return array{int, int, array}
85 | */
86 | private static function extractSizeAndPixels(\SplFileInfo $file): array
87 | {
88 | $image = ImageFileInfo::wrap($file)->as(\Imagick::class);
89 |
90 | if ($image->getImageWidth() > 100 || $image->getImageHeight() > 100) {
91 | $image->scaleImage(100, 100, bestfit: true);
92 | }
93 |
94 | $width = $image->getImageWidth();
95 | $height = $image->getImageHeight();
96 | $pixels = [];
97 |
98 | for ($y = 0; $y < $height; ++$y) {
99 | for ($x = 0; $x < $width; ++$x) {
100 | $pixel = $image->getImagePixelColor($x, $y);
101 | $colors = $pixel->getColor(2);
102 | $pixels[] = $colors['r'];
103 | $pixels[] = $colors['g'];
104 | $pixels[] = $colors['b'];
105 | $pixels[] = $colors['a'];
106 | }
107 | }
108 |
109 | return [$width, $height, $pixels];
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Image/Transformer.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Zenstruck\Image;
13 |
14 | /**
15 | * @author Kevin Bond
16 | *
17 | * @internal
18 | *
19 | * @template T of object
20 | */
21 | interface Transformer
22 | {
23 | /**
24 | * @param object|callable(T):T $filter
25 | */
26 | public function transform(\SplFileInfo $image, callable|object $filter, array $options = []): \SplFileInfo;
27 |
28 | /**
29 | * @return T
30 | */
31 | public function object(\SplFileInfo $image): object;
32 | }
33 |
--------------------------------------------------------------------------------
/src/Image/Transformer/FileTransformer.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Zenstruck\Image\Transformer;
13 |
14 | use Zenstruck\Image\Transformer;
15 | use Zenstruck\ImageFileInfo;
16 | use Zenstruck\TempFile;
17 |
18 | /**
19 | * @author Kevin Bond
20 | *
21 | * @internal
22 | *
23 | * @template T of object
24 | * @implements Transformer
25 | */
26 | abstract class FileTransformer implements Transformer
27 | {
28 | final public function transform(\SplFileInfo $image, callable|object $filter, array $options = []): \SplFileInfo
29 | {
30 | $filter = static::normalizeFilter($filter);
31 | $image = ImageFileInfo::wrap($image);
32 | $options['format'] ??= $image->guessExtension();
33 | $output = $options['output'] ??= TempFile::withExtension($options['format']);
34 | $options['output'] = (string) $options['output'];
35 |
36 | $transformed = $filter($this->object($image));
37 |
38 | if (!\is_a($transformed, static::expectedClass())) {
39 | throw new \LogicException(\sprintf('Filter callback must return a "%s" object.', static::expectedClass()));
40 | }
41 |
42 | $this->save($transformed, $options);
43 |
44 | return ImageFileInfo::wrap($output);
45 | }
46 |
47 | /**
48 | * @param object|callable(T):T $filter
49 | *
50 | * @return callable(T):T
51 | */
52 | public static function normalizeFilter(callable|object $filter): callable
53 | {
54 | return \is_callable($filter) ? $filter : throw new \InvalidArgumentException(\sprintf('"%s" does not support "%s".', self::class, $filter::class));
55 | }
56 |
57 | /**
58 | * @return class-string
59 | */
60 | abstract protected static function expectedClass(): string;
61 |
62 | /**
63 | * @param T $object
64 | * @param array{format:string,output:string}|array $options
65 | */
66 | abstract protected function save(object $object, array $options): void;
67 | }
68 |
--------------------------------------------------------------------------------
/src/Image/Transformer/GdImageTransformer.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Zenstruck\Image\Transformer;
13 |
14 | /**
15 | * @author Jakub Caban
16 | * @author Kevin Bond
17 | *
18 | * @internal
19 | *
20 | * @extends FileTransformer<\GdImage>
21 | */
22 | final class GdImageTransformer extends FileTransformer
23 | {
24 | public function __construct()
25 | {
26 | if (!\class_exists(\GdImage::class)) {
27 | throw new \LogicException('GD extension not available.');
28 | }
29 | }
30 |
31 | public function object(\SplFileInfo $image): object
32 | {
33 | return @\imagecreatefromstring(\file_get_contents($image) ?: throw new \RuntimeException(\sprintf('Unable to read "%s".', $image))) ?: throw new \RuntimeException(\sprintf('Unable to create GdImage for "%s".', $image));
34 | }
35 |
36 | protected static function expectedClass(): string
37 | {
38 | return \GdImage::class;
39 | }
40 |
41 | protected function save(object $object, array $options): void
42 | {
43 | /** @var string&callable $function */
44 | $function = match ($options['format']) {
45 | 'png' => 'imagepng',
46 | 'jpg', 'jpeg' => 'imagejpeg',
47 | 'gif' => 'imagegif',
48 | 'webp' => 'imagewebp',
49 | 'avif' => 'imageavif',
50 | default => throw new \LogicException(\sprintf('Image format "%s" is invalid.', $options['format'])),
51 | };
52 |
53 | if (!\function_exists($function)) {
54 | throw new \LogicException(\sprintf('The "%s" gd extension function is not available.', $function));
55 | }
56 |
57 | $function($object, $options['output']);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Image/Transformer/ImagickTransformer.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Zenstruck\Image\Transformer;
13 |
14 | /**
15 | * @author Kevin Bond
16 | * @author Jakub Caban
17 | *
18 | * @internal
19 | *
20 | * @extends FileTransformer<\Imagick>
21 | */
22 | final class ImagickTransformer extends FileTransformer
23 | {
24 | public function __construct()
25 | {
26 | if (!\class_exists(\Imagick::class)) {
27 | throw new \LogicException('Imagick extension not available.');
28 | }
29 | }
30 |
31 | public function object(\SplFileInfo $image): object
32 | {
33 | $imagick = new \Imagick();
34 | $imagick->readImage((string) $image);
35 |
36 | return $imagick;
37 | }
38 |
39 | protected static function expectedClass(): string
40 | {
41 | return \Imagick::class;
42 | }
43 |
44 | protected function save(object $object, array $options): void
45 | {
46 | $object->setImageFormat($options['format']);
47 | $object->writeImage($options['output']);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Image/Transformer/ImagineTransformer.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Zenstruck\Image\Transformer;
13 |
14 | use Imagine\Filter\FilterInterface;
15 | use Imagine\Gd\Image as GdImage;
16 | use Imagine\Gd\Imagine as GdImagine;
17 | use Imagine\Gmagick\Image as GmagickImage;
18 | use Imagine\Gmagick\Imagine as GmagickImagine;
19 | use Imagine\Image\ImageInterface;
20 | use Imagine\Image\ImagineInterface;
21 | use Imagine\Imagick\Image as ImagickImage;
22 | use Imagine\Imagick\Imagine as ImagickImagine;
23 |
24 | /**
25 | * @author Kevin Bond
26 | *
27 | * @internal
28 | *
29 | * @extends FileTransformer
30 | */
31 | final class ImagineTransformer extends FileTransformer
32 | {
33 | public function __construct(private ImagineInterface $imagine)
34 | {
35 | }
36 |
37 | /**
38 | * @template T of ImageInterface
39 | *
40 | * @param class-string $class
41 | */
42 | public static function createFor(string $class): self
43 | {
44 | if (!\interface_exists(ImageInterface::class)) {
45 | throw new \LogicException('imagine/imagine required. Install with "composer require imagine/imagine".');
46 | }
47 |
48 | return match ($class) {
49 | ImageInterface::class, GdImage::class => new self(new GdImagine()),
50 | ImagickImage::class => new self(new ImagickImagine()),
51 | GmagickImage::class => new self(new GmagickImagine()),
52 | default => throw new \InvalidArgumentException('invalid class'),
53 | };
54 | }
55 |
56 | public static function normalizeFilter(callable|object $filter): callable
57 | {
58 | if ($filter instanceof FilterInterface) {
59 | $filter = static fn(ImageInterface $i) => $filter->apply($i);
60 | }
61 |
62 | return parent::normalizeFilter($filter);
63 | }
64 |
65 | public function object(\SplFileInfo $image): object
66 | {
67 | return $this->imagine->open($image);
68 | }
69 |
70 | protected static function expectedClass(): string
71 | {
72 | return ImageInterface::class;
73 | }
74 |
75 | protected function save(object $object, array $options): void
76 | {
77 | $object->save($options['output'], $options);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Image/Transformer/InterventionTransformer.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Zenstruck\Image\Transformer;
13 |
14 | use Intervention\Image\Filters\FilterInterface;
15 | use Intervention\Image\Image as InterventionImage;
16 | use Intervention\Image\ImageManager;
17 | use Intervention\Image\ImageManagerStatic;
18 | use Intervention\Image\Interfaces\ImageInterface;
19 | use Intervention\Image\Interfaces\ModifierInterface;
20 |
21 | /**
22 | * @author Kevin Bond
23 | * @author Jakub Caban
24 | *
25 | * @internal
26 | *
27 | * @extends FileTransformer
28 | */
29 | final class InterventionTransformer extends FileTransformer
30 | {
31 | public function __construct(private ?ImageManager $manager = null)
32 | {
33 | if (!\class_exists(ImageManager::class)) {
34 | throw new \LogicException('intervention/image required. Install with "composer require intervention/image".');
35 | }
36 | }
37 |
38 | public static function normalizeFilter(callable|object $filter): callable
39 | {
40 | if ($filter instanceof FilterInterface) { // @phpstan-ignore-line
41 | $filter = static fn(InterventionImage $i) => $i->filter($filter); // @phpstan-ignore-line
42 | }
43 |
44 | if ($filter instanceof ModifierInterface) {
45 | $filter = static fn(InterventionImage $i) => $i->modify($filter);
46 | }
47 |
48 | return parent::normalizeFilter($filter);
49 | }
50 |
51 | public function object(\SplFileInfo $image): object
52 | {
53 | if (\interface_exists(ImageInterface::class)) {
54 | return $this->manager ? $this->manager->read($image) : ImageManager::gd()->read($image);
55 | }
56 |
57 | return $this->manager ? $this->manager->make($image) : ImageManagerStatic::make($image); // @phpstan-ignore-line
58 | }
59 |
60 | protected static function expectedClass(): string
61 | {
62 | if (\interface_exists(ImageInterface::class)) {
63 | return ImageInterface::class;
64 | }
65 |
66 | return InterventionImage::class;
67 | }
68 |
69 | protected function save(object $object, array $options): void
70 | {
71 | $object->save($options['output'], $options['quality'] ?? null, $options['format']);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Image/Transformer/MultiTransformer.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Zenstruck\Image\Transformer;
13 |
14 | use Imagine\Filter\FilterInterface as ImagineFilter;
15 | use Imagine\Gd\Image as GdImagineImage;
16 | use Imagine\Gmagick\Image as GmagickImagineImage;
17 | use Imagine\Image\ImageInterface as ImagineImage;
18 | use Imagine\Imagick\Image as ImagickImagineImage;
19 | use Intervention\Image\Filters\FilterInterface as InterventionFilter;
20 | use Intervention\Image\Image as InterventionImage;
21 | use Intervention\Image\Interfaces\ImageInterface as InterventionImageInterface;
22 | use Intervention\Image\Interfaces\ModifierInterface as InterventionModifier;
23 | use Psr\Container\ContainerInterface;
24 | use Spatie\Image\Image as SpatieImage;
25 | use Zenstruck\Image\Transformer;
26 |
27 | /**
28 | * @author Kevin Bond
29 | *
30 | * @internal
31 | *
32 | * @implements Transformer