├── README.md
├── LICENSE
├── composer.json
├── .phpstorm.meta.php
└── src
└── Image.php
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Aplus Framework Image Library
4 |
5 | - [Home](https://aplus-framework.com/packages/image)
6 | - [User Guide](https://docs.aplus-framework.com/guides/libraries/image/index.html)
7 | - [API Documentation](https://docs.aplus-framework.com/packages/image.html)
8 |
9 | [](https://github.com/aplus-framework/image/actions/workflows/tests.yml)
10 | [](https://coveralls.io/github/aplus-framework/image?branch=master)
11 | [](https://packagist.org/packages/aplus/image)
12 | [](https://aplus-framework.com/sponsor)
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Natan Felles
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aplus/image",
3 | "description": "Aplus Framework Image Library",
4 | "license": "MIT",
5 | "type": "library",
6 | "keywords": [
7 | "image",
8 | "opacity",
9 | "watermark",
10 | "filter",
11 | "flatten",
12 | "gd"
13 | ],
14 | "authors": [
15 | {
16 | "name": "Natan Felles",
17 | "email": "natanfelles@gmail.com",
18 | "homepage": "https://natanfelles.github.io"
19 | }
20 | ],
21 | "homepage": "https://aplus-framework.com/packages/image",
22 | "support": {
23 | "email": "support@aplus-framework.com",
24 | "issues": "https://github.com/aplus-framework/image/issues",
25 | "forum": "https://aplus-framework.com/forum",
26 | "source": "https://github.com/aplus-framework/image",
27 | "docs": "https://docs.aplus-framework.com/guides/libraries/image/"
28 | },
29 | "funding": [
30 | {
31 | "type": "Aplus Sponsor",
32 | "url": "https://aplus-framework.com/sponsor"
33 | }
34 | ],
35 | "require": {
36 | "php": ">=8.3",
37 | "ext-gd": "*",
38 | "ext-json": "*"
39 | },
40 | "require-dev": {
41 | "ext-xdebug": "*",
42 | "aplus/coding-standard": "^2.8",
43 | "ergebnis/composer-normalize": "^2.15",
44 | "jetbrains/phpstorm-attributes": "^1.0",
45 | "phpmd/phpmd": "^2.13",
46 | "phpstan/phpstan": "^1.11",
47 | "phpunit/phpunit": "^10.5"
48 | },
49 | "minimum-stability": "dev",
50 | "prefer-stable": true,
51 | "autoload": {
52 | "psr-4": {
53 | "Framework\\Image\\": "src/"
54 | }
55 | },
56 | "autoload-dev": {
57 | "psr-4": {
58 | "Tests\\Image\\": "tests/"
59 | }
60 | },
61 | "config": {
62 | "allow-plugins": {
63 | "ergebnis/composer-normalize": true
64 | },
65 | "optimize-autoloader": true,
66 | "preferred-install": "dist",
67 | "sort-packages": true
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/.phpstorm.meta.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace PHPSTORM_META;
11 |
12 | registerArgumentsSet(
13 | 'flip_directions',
14 | 'b',
15 | 'both',
16 | 'h',
17 | 'horizontal',
18 | 'v',
19 | 'vertical',
20 | );
21 | registerArgumentsSet(
22 | 'filter_types',
23 | \IMG_FILTER_NEGATE,
24 | \IMG_FILTER_GRAYSCALE,
25 | \IMG_FILTER_BRIGHTNESS,
26 | \IMG_FILTER_CONTRAST,
27 | \IMG_FILTER_COLORIZE,
28 | \IMG_FILTER_EDGEDETECT,
29 | \IMG_FILTER_EMBOSS,
30 | \IMG_FILTER_GAUSSIAN_BLUR,
31 | \IMG_FILTER_SELECTIVE_BLUR,
32 | \IMG_FILTER_MEAN_REMOVAL,
33 | \IMG_FILTER_SMOOTH,
34 | \IMG_FILTER_PIXELATE,
35 | \IMG_FILTER_SCATTER,
36 | );
37 | registerArgumentsSet(
38 | 'filter_arg1',
39 | \IMG_FILTER_BRIGHTNESS,
40 | \IMG_FILTER_CONTRAST,
41 | \IMG_FILTER_COLORIZE,
42 | \IMG_FILTER_SMOOTH,
43 | \IMG_FILTER_PIXELATE,
44 | \IMG_FILTER_SCATTER,
45 | );
46 | registerArgumentsSet(
47 | 'filter_arg2',
48 | \IMG_FILTER_COLORIZE,
49 | \IMG_FILTER_PIXELATE,
50 | \IMG_FILTER_SCATTER,
51 | );
52 | registerArgumentsSet(
53 | 'filter_arg3',
54 | \IMG_FILTER_COLORIZE,
55 | \IMG_FILTER_SCATTER,
56 | );
57 | registerArgumentsSet(
58 | 'filter_arg4',
59 | \IMG_FILTER_COLORIZE,
60 | );
61 | expectedArguments(
62 | \Framework\Image\Image::flip(),
63 | 0,
64 | argumentsSet('flip_directions')
65 | );
66 | expectedArguments(
67 | \Framework\Image\Image::filter(),
68 | 0,
69 | argumentsSet('filter_types')
70 | );
71 | expectedArguments(
72 | \Framework\Image\Image::filter(),
73 | 1,
74 | argumentsSet('filter_arg1')
75 | );
76 | expectedArguments(
77 | \Framework\Image\Image::filter(),
78 | 2,
79 | argumentsSet('filter_arg2')
80 | );
81 | expectedArguments(
82 | \Framework\Image\Image::filter(),
83 | 3,
84 | argumentsSet('filter_arg3')
85 | );
86 | expectedArguments(
87 | \Framework\Image\Image::filter(),
88 | 4,
89 | argumentsSet('filter_arg4')
90 | );
91 |
--------------------------------------------------------------------------------
/src/Image.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Framework\Image;
11 |
12 | use GdImage;
13 | use InvalidArgumentException;
14 | use JetBrains\PhpStorm\ArrayShape;
15 | use JetBrains\PhpStorm\Pure;
16 | use LogicException;
17 | use RuntimeException;
18 |
19 | /**
20 | * Class Image.
21 | *
22 | * @package image
23 | */
24 | class Image implements \JsonSerializable, \Stringable
25 | {
26 | /**
27 | * Path to the image file.
28 | */
29 | protected string $filename;
30 | /**
31 | * Image type. One of IMAGETYPE_* constants.
32 | */
33 | protected int $type;
34 | /**
35 | * MIME type.
36 | */
37 | protected string $mime;
38 | /**
39 | * GdImage instance.
40 | */
41 | protected GdImage $instance;
42 | /**
43 | * The image quality/compression level.
44 | *
45 | * 0 to 9 on PNG, default is 6. 0 to 100 on JPEG, default is 75.
46 | * Null to update to the default when getQuality is called.
47 | *
48 | * @see Image::getQuality()
49 | */
50 | protected ?int $quality = null;
51 |
52 | /**
53 | * Image constructor.
54 | *
55 | * @param string $filename Path to the image file.
56 | * Acceptable types are: GIF, JPEG and PNG
57 | *
58 | * @throws InvalidArgumentException for invalid file
59 | * @throws RuntimeException for unsupported image type of could not get image info
60 | */
61 | public function __construct(string $filename)
62 | {
63 | $realpath = \realpath($filename);
64 | if ($realpath === false || !\is_file($realpath) || !\is_readable($realpath)) {
65 | throw new InvalidArgumentException('File does not exists or is not readable: ' . $filename);
66 | }
67 | $this->filename = $realpath;
68 | $info = \getimagesize($this->filename);
69 | if ($info === false) {
70 | throw new RuntimeException(
71 | 'Could not get image info from the given filename: ' . $this->filename
72 | );
73 | }
74 | if (!(\imagetypes() & $info[2])) {
75 | throw new RuntimeException('Unsupported image type: ' . $info[2]);
76 | }
77 | $this->type = $info[2];
78 | $this->mime = $info['mime'];
79 | $instance = match ($this->type) {
80 | \IMAGETYPE_PNG => \imagecreatefrompng($this->filename),
81 | \IMAGETYPE_JPEG => \imagecreatefromjpeg($this->filename),
82 | \IMAGETYPE_GIF => \imagecreatefromgif($this->filename),
83 | default => throw new RuntimeException('Image type is not acceptable: ' . $this->type),
84 | };
85 | if (!$instance instanceof GdImage) {
86 | throw new RuntimeException(
87 | "Image of type '{$this->type}' does not returned a GdImage instance"
88 | );
89 | }
90 | $this->instance = $instance;
91 | }
92 |
93 | public function __toString() : string
94 | {
95 | return $this->getDataUrl();
96 | }
97 |
98 | /**
99 | * Destroys the GdImage instance.
100 | *
101 | * @return bool
102 | *
103 | * @deprecated
104 | *
105 | * @codeCoverageIgnore
106 | */
107 | public function destroy() : bool
108 | {
109 | return \imagedestroy($this->instance);
110 | }
111 |
112 | /**
113 | * Gets the GdImage instance.
114 | *
115 | * @return GdImage GdImage instance
116 | */
117 | #[Pure]
118 | public function getInstance() : GdImage
119 | {
120 | return $this->instance;
121 | }
122 |
123 | /**
124 | * Sets the GdImage instance.
125 | *
126 | * @param GdImage $instance GdImage instance
127 | *
128 | * @return static
129 | */
130 | public function setInstance(GdImage $instance) : static
131 | {
132 | $this->instance = $instance;
133 | return $this;
134 | }
135 |
136 | /**
137 | * Gets the image quality/compression level.
138 | *
139 | * @return int|null An integer for PNG and JPEG types or null for GIF
140 | */
141 | public function getQuality() : ?int
142 | {
143 | if ($this->quality === null) {
144 | if ($this->type === \IMAGETYPE_PNG) {
145 | $this->quality = 6;
146 | } elseif ($this->type === \IMAGETYPE_JPEG) {
147 | $this->quality = 75;
148 | }
149 | }
150 | return $this->quality;
151 | }
152 |
153 | /**
154 | * Sets the image quality/compression level.
155 | *
156 | * @param int $quality The quality/compression level
157 | *
158 | * @throws LogicException when trying to set a quality value for a GIF image
159 | * @throws InvalidArgumentException if the image type is PNG and the value
160 | * is not between 0 and 9 or if the image type is JPEG and the value is not
161 | * between 0 and 100
162 | *
163 | * @return static
164 | */
165 | public function setQuality(int $quality) : static
166 | {
167 | if ($this->type === \IMAGETYPE_GIF) {
168 | throw new LogicException(
169 | 'GIF images does not receive a quality value'
170 | );
171 | }
172 | if ($this->type === \IMAGETYPE_PNG && ($quality < 0 || $quality > 9)) {
173 | throw new InvalidArgumentException(
174 | 'PNG images must receive a quality value between 0 and 9, ' . $quality . ' given'
175 | );
176 | }
177 | if ($this->type === \IMAGETYPE_JPEG && ($quality < 0 || $quality > 100)) {
178 | throw new InvalidArgumentException(
179 | 'JPEG images must receive a quality value between 0 and 100, ' . $quality . ' given'
180 | );
181 | }
182 | $this->quality = $quality;
183 | return $this;
184 | }
185 |
186 | /**
187 | * Gets the image resolution.
188 | *
189 | * @throws RuntimeException for image could not get resolution
190 | *
191 | * @return array Returns an array containing two keys, horizontal and
192 | * vertical, with integers as values
193 | */
194 | #[ArrayShape(['horizontal' => 'int', 'vertical' => 'int'])]
195 | public function getResolution() : array
196 | {
197 | $resolution = \imageresolution($this->instance);
198 | if ($resolution === false) {
199 | throw new RuntimeException('Image could not to get resolution');
200 | }
201 | return [
202 | 'horizontal' => $resolution[0], // @phpstan-ignore-line
203 | // @phpstan-ignore-next-line
204 | 'vertical' => $resolution[1],
205 | ];
206 | }
207 |
208 | /**
209 | * Sets the image resolution.
210 | *
211 | * @param int $horizontal The horizontal resolution in DPI
212 | * @param int $vertical The vertical resolution in DPI
213 | *
214 | * @throws RuntimeException for image could not to set resolution
215 | *
216 | * @return static
217 | */
218 | public function setResolution(int $horizontal = 96, int $vertical = 96) : static
219 | {
220 | $set = \imageresolution($this->instance, $horizontal, $vertical);
221 | if ($set === false) {
222 | throw new RuntimeException('Image could not to set resolution');
223 | }
224 | return $this;
225 | }
226 |
227 | /**
228 | * Gets the image height.
229 | *
230 | * @return int
231 | */
232 | #[Pure]
233 | public function getHeight() : int
234 | {
235 | return \imagesy($this->instance);
236 | }
237 |
238 | /**
239 | * Gets the image width.
240 | *
241 | * @return int
242 | */
243 | #[Pure]
244 | public function getWidth() : int
245 | {
246 | return \imagesx($this->instance);
247 | }
248 |
249 | /**
250 | * Gets the file extension for image type.
251 | *
252 | * @return false|string a string with the extension corresponding to the
253 | * given image type or false on fail
254 | */
255 | #[Pure]
256 | public function getExtension() : false | string
257 | {
258 | return \image_type_to_extension($this->type);
259 | }
260 |
261 | /**
262 | * Gets the image MIME type.
263 | *
264 | * @return string
265 | */
266 | #[Pure]
267 | public function getMime() : string
268 | {
269 | return $this->mime;
270 | }
271 |
272 | /**
273 | * Saves the image contents to a given filename.
274 | *
275 | * @param string|null $filename Optional filename or null to use the original
276 | *
277 | * @return bool
278 | */
279 | public function save(?string $filename = null) : bool
280 | {
281 | $filename ??= $this->filename;
282 | return match ($this->type) {
283 | \IMAGETYPE_PNG => \imagepng($this->instance, $filename, $this->getQuality()),
284 | \IMAGETYPE_JPEG => \imagejpeg($this->instance, $filename, $this->getQuality()),
285 | \IMAGETYPE_GIF => \imagegif($this->instance, $filename),
286 | default => false,
287 | };
288 | }
289 |
290 | /**
291 | * Sends the image contents to the output buffer.
292 | *
293 | * @return bool
294 | */
295 | public function send() : bool
296 | {
297 | if (\in_array($this->type, [\IMAGETYPE_PNG, \IMAGETYPE_GIF], true)) {
298 | \imagesavealpha($this->instance, true);
299 | }
300 | return match ($this->type) {
301 | \IMAGETYPE_PNG => \imagepng($this->instance, null, $this->getQuality()),
302 | \IMAGETYPE_JPEG => \imagejpeg($this->instance, null, $this->getQuality()),
303 | \IMAGETYPE_GIF => \imagegif($this->instance),
304 | default => false,
305 | };
306 | }
307 |
308 | /**
309 | * Renders the image contents.
310 | *
311 | * @throws RuntimeException for image could not be rendered
312 | *
313 | * @return string The image contents
314 | */
315 | public function render() : string
316 | {
317 | \ob_start();
318 | $status = $this->send();
319 | $contents = \ob_get_clean();
320 | if ($status === false || $contents === false) {
321 | throw new RuntimeException('Image could not be rendered');
322 | }
323 | return $contents;
324 | }
325 |
326 | /**
327 | * Crops the image.
328 | *
329 | * @param int $width Width in pixels
330 | * @param int $height Height in pixels
331 | * @param int $marginLeft Margin left in pixels
332 | * @param int $marginTop Margin top in pixels
333 | *
334 | * @throws RuntimeException for image could not to crop
335 | *
336 | * @return static
337 | */
338 | public function crop(int $width, int $height, int $marginLeft = 0, int $marginTop = 0) : static
339 | {
340 | $crop = \imagecrop($this->instance, [
341 | 'x' => $marginLeft,
342 | 'y' => $marginTop,
343 | 'width' => $width,
344 | 'height' => $height,
345 | ]);
346 | if ($crop === false) {
347 | throw new RuntimeException('Image could not to crop');
348 | }
349 | $this->instance = $crop;
350 | return $this;
351 | }
352 |
353 | /**
354 | * Flips the image.
355 | *
356 | * @param string $direction Allowed values are: h or horizontal. v or vertical. b or both.
357 | *
358 | * @throws InvalidArgumentException for invalid image flip direction
359 | * @throws RuntimeException for image could not to flip
360 | *
361 | * @return static
362 | */
363 | public function flip(string $direction = 'horizontal') : static
364 | {
365 | $direction = match ($direction) {
366 | 'h', 'horizontal' => \IMG_FLIP_HORIZONTAL,
367 | 'v', 'vertical' => \IMG_FLIP_VERTICAL,
368 | 'b', 'both' => \IMG_FLIP_BOTH,
369 | default => throw new InvalidArgumentException('Invalid image flip direction: ' . $direction),
370 | };
371 | $flip = \imageflip($this->instance, $direction);
372 | if ($flip === false) {
373 | throw new RuntimeException('Image could not to flip');
374 | }
375 | return $this;
376 | }
377 |
378 | /**
379 | * Applies a filter to the image.
380 | *
381 | * @param int $type IMG_FILTER_* constants
382 | * @param int ...$arguments Arguments for the filter type
383 | *
384 | * @see https://www.php.net/manual/en/function.imagefilter.php
385 | *
386 | * @throws RuntimeException for image could not apply the filter
387 | *
388 | * @return static
389 | */
390 | public function filter(int $type, int ...$arguments) : static
391 | {
392 | $filtered = \imagefilter($this->instance, $type, ...$arguments);
393 | if ($filtered === false) {
394 | throw new RuntimeException('Image could not apply the filter');
395 | }
396 | return $this;
397 | }
398 |
399 | /**
400 | * Flattens the image.
401 | *
402 | * Replaces transparency with an RGB color.
403 | *
404 | * @param int $red
405 | * @param int $green
406 | * @param int $blue
407 | *
408 | * @throws RuntimeException for could not create a true color image, could
409 | * not allocate a color or image could not to flatten
410 | *
411 | * @return static
412 | */
413 | public function flatten(int $red = 255, int $green = 255, int $blue = 255) : static
414 | {
415 | \imagesavealpha($this->instance, false);
416 | $image = \imagecreatetruecolor($this->getWidth(), $this->getHeight());
417 | if ($image === false) {
418 | throw new RuntimeException('Could not create a true color image');
419 | }
420 | $color = \imagecolorallocate($image, $red, $green, $blue);
421 | if ($color === false) {
422 | throw new RuntimeException('Image could not allocate a color');
423 | }
424 | \imagefilledrectangle(
425 | $image,
426 | 0,
427 | 0,
428 | $this->getWidth(),
429 | $this->getHeight(),
430 | $color
431 | );
432 | $copied = \imagecopy(
433 | $image,
434 | $this->instance,
435 | 0,
436 | 0,
437 | 0,
438 | 0,
439 | $this->getWidth(),
440 | $this->getHeight()
441 | );
442 | if ($copied === false) {
443 | throw new RuntimeException('Image could not to flatten');
444 | }
445 | $this->instance = $image;
446 | return $this;
447 | }
448 |
449 | /**
450 | * Sets the image opacity level.
451 | *
452 | * @param int $opacity Opacity percentage: from 0 to 100
453 | *
454 | * @return static
455 | */
456 | public function opacity(int $opacity = 100) : static
457 | {
458 | if ($opacity < 0 || $opacity > 100) {
459 | throw new InvalidArgumentException(
460 | 'Opacity percentage must be between 0 and 100, ' . $opacity . ' given'
461 | );
462 | }
463 | if ($opacity === 100) {
464 | \imagealphablending($this->instance, true);
465 | return $this;
466 | }
467 | $opacity = (int) \round(\abs(($opacity * 127 / 100) - 127));
468 | \imagelayereffect($this->instance, \IMG_EFFECT_OVERLAY);
469 | $color = \imagecolorallocatealpha($this->instance, 127, 127, 127, $opacity);
470 | if ($color === false) {
471 | throw new RuntimeException('Image could not allocate a color');
472 | }
473 | \imagefilledrectangle(
474 | $this->instance,
475 | 0,
476 | 0,
477 | $this->getWidth(),
478 | $this->getHeight(),
479 | $color
480 | );
481 | \imagesavealpha($this->instance, true);
482 | \imagealphablending($this->instance, false);
483 | return $this;
484 | }
485 |
486 | /**
487 | * Rotates the image with a given angle.
488 | *
489 | * @param float $angle Rotation angle, in degrees. Clockwise direction.
490 | *
491 | * @throws RuntimeException for image could not allocate a color or could not rotate
492 | *
493 | * @return static
494 | */
495 | public function rotate(float $angle) : static
496 | {
497 | if (\in_array($this->type, [\IMAGETYPE_PNG, \IMAGETYPE_GIF], true)) {
498 | \imagealphablending($this->instance, false);
499 | \imagesavealpha($this->instance, true);
500 | $background = \imagecolorallocatealpha($this->instance, 0, 0, 0, 127);
501 | } else {
502 | $background = \imagecolorallocate($this->instance, 255, 255, 255);
503 | }
504 | if ($background === false) {
505 | throw new RuntimeException('Image could not allocate a color');
506 | }
507 | $rotate = \imagerotate($this->instance, -1 * $angle, $background);
508 | if ($rotate === false) {
509 | throw new RuntimeException('Image could not to rotate');
510 | }
511 | $this->instance = $rotate;
512 | return $this;
513 | }
514 |
515 | /**
516 | * Scales the image.
517 | *
518 | * @param int $width Width in pixels
519 | * @param int $height Height in pixels. Use -1 to use a proportional height
520 | * based on the width.
521 | *
522 | * @throws RuntimeException for image could not to scale
523 | *
524 | * @return static
525 | */
526 | public function scale(int $width, int $height = -1) : static
527 | {
528 | $scale = \imagescale($this->instance, $width, $height);
529 | if ($scale === false) {
530 | throw new RuntimeException('Image could not to scale');
531 | }
532 | $this->instance = $scale;
533 | return $this;
534 | }
535 |
536 | /**
537 | * Adds a watermark to the image.
538 | *
539 | * @param Image $watermark The image to use as watermark
540 | * @param int $horizontalPosition Horizontal position
541 | * @param int $verticalPosition Vertical position
542 | *
543 | * @throws RuntimeException for image could not to create watermark
544 | *
545 | * @return static
546 | */
547 | public function watermark(
548 | Image $watermark,
549 | int $horizontalPosition = 0,
550 | int $verticalPosition = 0
551 | ) : static {
552 | if ($horizontalPosition < 0) {
553 | $horizontalPosition = $this->getWidth()
554 | - (-1 * $horizontalPosition + $watermark->getWidth());
555 | }
556 | if ($verticalPosition < 0) {
557 | $verticalPosition = $this->getHeight()
558 | - (-1 * $verticalPosition + $watermark->getHeight());
559 | }
560 | $copied = \imagecopy(
561 | $this->instance,
562 | $watermark->getInstance(),
563 | $horizontalPosition,
564 | $verticalPosition,
565 | 0,
566 | 0,
567 | $watermark->getWidth(),
568 | $watermark->getHeight()
569 | );
570 | if ($copied === false) {
571 | throw new RuntimeException('Image could not to create watermark');
572 | }
573 | return $this;
574 | }
575 |
576 | /**
577 | * Allow embed the image contents in a document.
578 | *
579 | * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
580 | * @see https://datatracker.ietf.org/doc/html/rfc2397
581 | *
582 | * @return string The image "data" URL
583 | */
584 | public function getDataUrl() : string
585 | {
586 | return 'data:' . $this->getMime() . ';base64,' . \base64_encode($this->render());
587 | }
588 |
589 | /**
590 | * @return string
591 | */
592 | public function jsonSerialize() : string
593 | {
594 | return $this->getDataUrl();
595 | }
596 |
597 | /**
598 | * Indicates if a given filename has an acceptable image type.
599 | *
600 | * @param string $filename
601 | *
602 | * @return bool
603 | */
604 | public static function isAcceptable(string $filename) : bool
605 | {
606 | $filename = \realpath($filename);
607 | if ($filename === false || !\is_file($filename) || !\is_readable($filename)) {
608 | return false;
609 | }
610 | $info = \getimagesize($filename);
611 | if ($info === false) {
612 | return false;
613 | }
614 | return match ($info[2]) {
615 | \IMAGETYPE_PNG, \IMAGETYPE_JPEG, \IMAGETYPE_GIF => true,
616 | default => false,
617 | };
618 | }
619 | }
620 |
--------------------------------------------------------------------------------