PluginProbe ʕ •ᴥ•ʔ
FAPI Member / trunk
FAPI Member vtrunk
2.2.33 2.2.32 trunk 1.9.47 2.1.18 2.2.24 2.2.25 2.2.26 2.2.28 2.2.29 2.2.30 2.2.31
fapi-member / libs / nette / utils / src / Utils / Image.php
fapi-member / libs / nette / utils / src / Utils Last commit date
ArrayHash.php 2 years ago ArrayList.php 2 years ago Arrays.php 2 years ago Callback.php 2 years ago DateTime.php 2 years ago FileSystem.php 2 years ago Floats.php 2 years ago Helpers.php 2 years ago Html.php 2 years ago Image.php 2 years ago Json.php 2 years ago ObjectHelpers.php 2 years ago ObjectMixin.php 2 years ago Paginator.php 2 years ago Random.php 2 years ago Reflection.php 2 years ago Strings.php 2 years ago Type.php 2 years ago Validators.php 2 years ago exceptions.php 2 years ago
Image.php
612 lines
1 <?php
2
3 /**
4 * This file is part of the Nette Framework (https://nette.org)
5 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6 */
7 declare (strict_types=1);
8 namespace FapiMember\Library\Nette\Utils;
9
10 use FapiMember\Library\Nette;
11 /**
12 * Basic manipulation with images. Supported types are JPEG, PNG, GIF, WEBP, AVIF and BMP.
13 *
14 * <code>
15 * $image = Image::fromFile('nette.jpg');
16 * $image->resize(150, 100);
17 * $image->sharpen();
18 * $image->send();
19 * </code>
20 *
21 * @method Image affine(array $affine, array $clip = null)
22 * @method array affineMatrixConcat(array $m1, array $m2)
23 * @method array affineMatrixGet(int $type, mixed $options = null)
24 * @method void alphaBlending(bool $on)
25 * @method void antialias(bool $on)
26 * @method void arc($x, $y, $w, $h, $start, $end, $color)
27 * @method void char(int $font, $x, $y, string $char, $color)
28 * @method void charUp(int $font, $x, $y, string $char, $color)
29 * @method int colorAllocate($red, $green, $blue)
30 * @method int colorAllocateAlpha($red, $green, $blue, $alpha)
31 * @method int colorAt($x, $y)
32 * @method int colorClosest($red, $green, $blue)
33 * @method int colorClosestAlpha($red, $green, $blue, $alpha)
34 * @method int colorClosestHWB($red, $green, $blue)
35 * @method void colorDeallocate($color)
36 * @method int colorExact($red, $green, $blue)
37 * @method int colorExactAlpha($red, $green, $blue, $alpha)
38 * @method void colorMatch(Image $image2)
39 * @method int colorResolve($red, $green, $blue)
40 * @method int colorResolveAlpha($red, $green, $blue, $alpha)
41 * @method void colorSet($index, $red, $green, $blue)
42 * @method array colorsForIndex($index)
43 * @method int colorsTotal()
44 * @method int colorTransparent($color = null)
45 * @method void convolution(array $matrix, float $div, float $offset)
46 * @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH)
47 * @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
48 * @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
49 * @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
50 * @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
51 * @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1)
52 * @method void ellipse($cx, $cy, $w, $h, $color)
53 * @method void fill($x, $y, $color)
54 * @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style)
55 * @method void filledEllipse($cx, $cy, $w, $h, $color)
56 * @method void filledPolygon(array $points, $numPoints, $color)
57 * @method void filledRectangle($x1, $y1, $x2, $y2, $color)
58 * @method void fillToBorder($x, $y, $border, $color)
59 * @method void filter($filtertype)
60 * @method void flip(int $mode)
61 * @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = null)
62 * @method void gammaCorrect(float $inputgamma, float $outputgamma)
63 * @method array getClip()
64 * @method int interlace($interlace = null)
65 * @method bool isTrueColor()
66 * @method void layerEffect($effect)
67 * @method void line($x1, $y1, $x2, $y2, $color)
68 * @method void openPolygon(array $points, int $num_points, int $color)
69 * @method void paletteCopy(Image $source)
70 * @method void paletteToTrueColor()
71 * @method void polygon(array $points, $numPoints, $color)
72 * @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null)
73 * @method void rectangle($x1, $y1, $x2, $y2, $col)
74 * @method mixed resolution(int $res_x = null, int $res_y = null)
75 * @method Image rotate(float $angle, $backgroundColor)
76 * @method void saveAlpha(bool $saveflag)
77 * @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED)
78 * @method void setBrush(Image $brush)
79 * @method void setClip(int $x1, int $y1, int $x2, int $y2)
80 * @method void setInterpolation(int $method = IMG_BILINEAR_FIXED)
81 * @method void setPixel($x, $y, $color)
82 * @method void setStyle(array $style)
83 * @method void setThickness($thickness)
84 * @method void setTile(Image $tile)
85 * @method void string($font, $x, $y, string $s, $col)
86 * @method void stringUp($font, $x, $y, string $s, $col)
87 * @method void trueColorToPalette(bool $dither, $ncolors)
88 * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
89 * @property-read positive-int $width
90 * @property-read positive-int $height
91 * @property-read resource|\GdImage $imageResource
92 */
93 class Image
94 {
95 use Nette\SmartObject;
96 /** Prevent from getting resized to a bigger size than the original */
97 public const SHRINK_ONLY = 0b1;
98 /** Resizes to a specified width and height without keeping aspect ratio */
99 public const STRETCH = 0b10;
100 /** Resizes to fit into a specified width and height and preserves aspect ratio */
101 public const FIT = 0b0;
102 /** Resizes while bounding the smaller dimension to the specified width or height and preserves aspect ratio */
103 public const FILL = 0b100;
104 /** Resizes to the smallest possible size to completely cover specified width and height and reserves aspect ratio */
105 public const EXACT = 0b1000;
106 /** image types */
107 public const JPEG = \IMAGETYPE_JPEG, PNG = \IMAGETYPE_PNG, GIF = \IMAGETYPE_GIF, WEBP = \IMAGETYPE_WEBP, AVIF = 19, BMP = \IMAGETYPE_BMP;
108 public const EMPTY_GIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
109 private const Formats = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp', self::AVIF => 'avif', self::BMP => 'bmp'];
110 /** @var resource|\GdImage */
111 private $image;
112 /**
113 * Returns RGB color (0..255) and transparency (0..127).
114 */
115 public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array
116 {
117 return ['red' => max(0, min(255, $red)), 'green' => max(0, min(255, $green)), 'blue' => max(0, min(255, $blue)), 'alpha' => max(0, min(127, $transparency))];
118 }
119 /**
120 * Reads an image from a file and returns its type in $type.
121 * @throws Nette\NotSupportedException if gd extension is not loaded
122 * @throws UnknownImageFileException if file not found or file type is not known
123 * @return static
124 */
125 public static function fromFile(string $file, ?int &$type = null)
126 {
127 if (!extension_loaded('gd')) {
128 throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
129 }
130 $type = self::detectTypeFromFile($file);
131 if (!$type) {
132 throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '{$file}'." : "File '{$file}' not found.");
133 }
134 return self::invokeSafe('imagecreatefrom' . self::Formats[$type], $file, "Unable to open file '{$file}'.", __METHOD__);
135 }
136 /**
137 * Reads an image from a string and returns its type in $type.
138 * @return static
139 * @throws Nette\NotSupportedException if gd extension is not loaded
140 * @throws ImageException
141 */
142 public static function fromString(string $s, ?int &$type = null)
143 {
144 if (!extension_loaded('gd')) {
145 throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
146 }
147 $type = self::detectTypeFromString($s);
148 if (!$type) {
149 throw new UnknownImageFileException('Unknown type of image.');
150 }
151 return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__);
152 }
153 private static function invokeSafe(string $func, string $arg, string $message, string $callee): self
154 {
155 $errors = [];
156 $res = Callback::invokeSafe($func, [$arg], function (string $message) use (&$errors): void {
157 $errors[] = $message;
158 });
159 if (!$res) {
160 throw new ImageException($message . ' Errors: ' . implode(', ', $errors));
161 } elseif ($errors) {
162 trigger_error($callee . '(): ' . implode(', ', $errors), \E_USER_WARNING);
163 }
164 return new static($res);
165 }
166 /**
167 * Creates a new true color image of the given dimensions. The default color is black.
168 * @param positive-int $width
169 * @param positive-int $height
170 * @return static
171 * @throws Nette\NotSupportedException if gd extension is not loaded
172 */
173 public static function fromBlank(int $width, int $height, ?array $color = null)
174 {
175 if (!extension_loaded('gd')) {
176 throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
177 }
178 if ($width < 1 || $height < 1) {
179 throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.');
180 }
181 $image = imagecreatetruecolor($width, $height);
182 if ($color) {
183 $color += ['alpha' => 0];
184 $color = imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']);
185 imagealphablending($image, \false);
186 imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color);
187 imagealphablending($image, \true);
188 }
189 return new static($image);
190 }
191 /**
192 * Returns the type of image from file.
193 */
194 public static function detectTypeFromFile(string $file, &$width = null, &$height = null): ?int
195 {
196 [$width, $height, $type] = @getimagesize($file);
197 // @ - files smaller than 12 bytes causes read error
198 return isset(self::Formats[$type]) ? $type : null;
199 }
200 /**
201 * Returns the type of image from string.
202 */
203 public static function detectTypeFromString(string $s, &$width = null, &$height = null): ?int
204 {
205 [$width, $height, $type] = @getimagesizefromstring($s);
206 // @ - strings smaller than 12 bytes causes read error
207 return isset(self::Formats[$type]) ? $type : null;
208 }
209 /**
210 * Returns the file extension for the given `Image::XXX` constant.
211 */
212 public static function typeToExtension(int $type): string
213 {
214 if (!isset(self::Formats[$type])) {
215 throw new Nette\InvalidArgumentException("Unsupported image type '{$type}'.");
216 }
217 return self::Formats[$type];
218 }
219 /**
220 * Returns the `Image::XXX` constant for given file extension.
221 */
222 public static function extensionToType(string $extension): int
223 {
224 $extensions = array_flip(self::Formats) + ['jpg' => self::JPEG];
225 $extension = strtolower($extension);
226 if (!isset($extensions[$extension])) {
227 throw new Nette\InvalidArgumentException("Unsupported file extension '{$extension}'.");
228 }
229 return $extensions[$extension];
230 }
231 /**
232 * Returns the mime type for the given `Image::XXX` constant.
233 */
234 public static function typeToMimeType(int $type): string
235 {
236 return 'image/' . self::typeToExtension($type);
237 }
238 /**
239 * Wraps GD image.
240 * @param resource|\GdImage $image
241 */
242 public function __construct($image)
243 {
244 $this->setImageResource($image);
245 imagesavealpha($image, \true);
246 }
247 /**
248 * Returns image width.
249 * @return positive-int
250 */
251 public function getWidth(): int
252 {
253 return imagesx($this->image);
254 }
255 /**
256 * Returns image height.
257 * @return positive-int
258 */
259 public function getHeight(): int
260 {
261 return imagesy($this->image);
262 }
263 /**
264 * Sets image resource.
265 * @param resource|\GdImage $image
266 * @return static
267 */
268 protected function setImageResource($image)
269 {
270 if (!$image instanceof \GdImage && !(is_resource($image) && get_resource_type($image) === 'gd')) {
271 throw new Nette\InvalidArgumentException('Image is not valid.');
272 }
273 $this->image = $image;
274 return $this;
275 }
276 /**
277 * Returns image GD resource.
278 * @return resource|\GdImage
279 */
280 public function getImageResource()
281 {
282 return $this->image;
283 }
284 /**
285 * Scales an image.
286 * @param int|string|null $width in pixels or percent
287 * @param int|string|null $height in pixels or percent
288 * @return static
289 */
290 public function resize($width, $height, int $flags = self::FIT)
291 {
292 if ($flags & self::EXACT) {
293 return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height);
294 }
295 [$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags);
296 if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) {
297 // resize
298 $newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource();
299 imagecopyresampled($newImage, $this->image, 0, 0, 0, 0, $newWidth, $newHeight, $this->getWidth(), $this->getHeight());
300 $this->image = $newImage;
301 }
302 if ($width < 0 || $height < 0) {
303 imageflip($this->image, ($width < 0) ? ($height < 0) ? \IMG_FLIP_BOTH : \IMG_FLIP_HORIZONTAL : \IMG_FLIP_VERTICAL);
304 }
305 return $this;
306 }
307 /**
308 * Calculates dimensions of resized image.
309 * @param int|string|null $newWidth in pixels or percent
310 * @param int|string|null $newHeight in pixels or percent
311 */
312 public static function calculateSize(int $srcWidth, int $srcHeight, $newWidth, $newHeight, int $flags = self::FIT): array
313 {
314 if ($newWidth === null) {
315 } elseif (self::isPercent($newWidth)) {
316 $newWidth = (int) round($srcWidth / 100 * abs($newWidth));
317 $percents = \true;
318 } else {
319 $newWidth = abs($newWidth);
320 }
321 if ($newHeight === null) {
322 } elseif (self::isPercent($newHeight)) {
323 $newHeight = (int) round($srcHeight / 100 * abs($newHeight));
324 $flags |= empty($percents) ? 0 : self::STRETCH;
325 } else {
326 $newHeight = abs($newHeight);
327 }
328 if ($flags & self::STRETCH) {
329 // non-proportional
330 if (!$newWidth || !$newHeight) {
331 throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.');
332 }
333 if ($flags & self::SHRINK_ONLY) {
334 $newWidth = (int) round($srcWidth * min(1, $newWidth / $srcWidth));
335 $newHeight = (int) round($srcHeight * min(1, $newHeight / $srcHeight));
336 }
337 } else {
338 // proportional
339 if (!$newWidth && !$newHeight) {
340 throw new Nette\InvalidArgumentException('At least width or height must be specified.');
341 }
342 $scale = [];
343 if ($newWidth > 0) {
344 // fit width
345 $scale[] = $newWidth / $srcWidth;
346 }
347 if ($newHeight > 0) {
348 // fit height
349 $scale[] = $newHeight / $srcHeight;
350 }
351 if ($flags & self::FILL) {
352 $scale = [max($scale)];
353 }
354 if ($flags & self::SHRINK_ONLY) {
355 $scale[] = 1;
356 }
357 $scale = min($scale);
358 $newWidth = (int) round($srcWidth * $scale);
359 $newHeight = (int) round($srcHeight * $scale);
360 }
361 return [max($newWidth, 1), max($newHeight, 1)];
362 }
363 /**
364 * Crops image.
365 * @param int|string $left in pixels or percent
366 * @param int|string $top in pixels or percent
367 * @param int|string $width in pixels or percent
368 * @param int|string $height in pixels or percent
369 * @return static
370 */
371 public function crop($left, $top, $width, $height)
372 {
373 [$r['x'], $r['y'], $r['width'], $r['height']] = static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height);
374 if (gd_info()['GD Version'] === 'bundled (2.1.0 compatible)') {
375 $this->image = imagecrop($this->image, $r);
376 imagesavealpha($this->image, \true);
377 } else {
378 $newImage = static::fromBlank($r['width'], $r['height'], self::RGB(0, 0, 0, 127))->getImageResource();
379 imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']);
380 $this->image = $newImage;
381 }
382 return $this;
383 }
384 /**
385 * Calculates dimensions of cutout in image.
386 * @param int|string $left in pixels or percent
387 * @param int|string $top in pixels or percent
388 * @param int|string $newWidth in pixels or percent
389 * @param int|string $newHeight in pixels or percent
390 */
391 public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $top, $newWidth, $newHeight): array
392 {
393 if (self::isPercent($newWidth)) {
394 $newWidth = (int) round($srcWidth / 100 * $newWidth);
395 }
396 if (self::isPercent($newHeight)) {
397 $newHeight = (int) round($srcHeight / 100 * $newHeight);
398 }
399 if (self::isPercent($left)) {
400 $left = (int) round(($srcWidth - $newWidth) / 100 * $left);
401 }
402 if (self::isPercent($top)) {
403 $top = (int) round(($srcHeight - $newHeight) / 100 * $top);
404 }
405 if ($left < 0) {
406 $newWidth += $left;
407 $left = 0;
408 }
409 if ($top < 0) {
410 $newHeight += $top;
411 $top = 0;
412 }
413 $newWidth = min($newWidth, $srcWidth - $left);
414 $newHeight = min($newHeight, $srcHeight - $top);
415 return [$left, $top, $newWidth, $newHeight];
416 }
417 /**
418 * Sharpens image a little bit.
419 * @return static
420 */
421 public function sharpen()
422 {
423 imageconvolution($this->image, [
424 // my magic numbers ;)
425 [-1, -1, -1],
426 [-1, 24, -1],
427 [-1, -1, -1],
428 ], 16, 0);
429 return $this;
430 }
431 /**
432 * Puts another image into this image.
433 * @param int|string $left in pixels or percent
434 * @param int|string $top in pixels or percent
435 * @param int<0, 100> $opacity 0..100
436 * @return static
437 */
438 public function place(self $image, $left = 0, $top = 0, int $opacity = 100)
439 {
440 $opacity = max(0, min(100, $opacity));
441 if ($opacity === 0) {
442 return $this;
443 }
444 $width = $image->getWidth();
445 $height = $image->getHeight();
446 if (self::isPercent($left)) {
447 $left = (int) round(($this->getWidth() - $width) / 100 * $left);
448 }
449 if (self::isPercent($top)) {
450 $top = (int) round(($this->getHeight() - $height) / 100 * $top);
451 }
452 $output = $input = $image->image;
453 if ($opacity < 100) {
454 $tbl = [];
455 for ($i = 0; $i < 128; $i++) {
456 $tbl[$i] = round(127 - (127 - $i) * $opacity / 100);
457 }
458 $output = imagecreatetruecolor($width, $height);
459 imagealphablending($output, \false);
460 if (!$image->isTrueColor()) {
461 $input = $output;
462 imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127));
463 imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height);
464 }
465 for ($x = 0; $x < $width; $x++) {
466 for ($y = 0; $y < $height; $y++) {
467 $c = \imagecolorat($input, $x, $y);
468 $c = ($c & 0xffffff) + ($tbl[$c >> 24] << 24);
469 \imagesetpixel($output, $x, $y, $c);
470 }
471 }
472 imagealphablending($output, \true);
473 }
474 imagecopy($this->image, $output, $left, $top, 0, 0, $width, $height);
475 return $this;
476 }
477 /**
478 * Saves image to the file. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
479 * @throws ImageException
480 */
481 public function save(string $file, ?int $quality = null, ?int $type = null): void
482 {
483 $type = $type ?? self::extensionToType(pathinfo($file, \PATHINFO_EXTENSION));
484 $this->output($type, $quality, $file);
485 }
486 /**
487 * Outputs image to string. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
488 */
489 public function toString(int $type = self::JPEG, ?int $quality = null): string
490 {
491 return Helpers::capture(function () use ($type, $quality) {
492 $this->output($type, $quality);
493 });
494 }
495 /**
496 * Outputs image to string.
497 */
498 public function __toString(): string
499 {
500 try {
501 return $this->toString();
502 } catch (\Throwable $e) {
503 if (func_num_args() || \PHP_VERSION_ID >= 70400) {
504 throw $e;
505 }
506 trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", \E_USER_ERROR);
507 return '';
508 }
509 }
510 /**
511 * Outputs image to browser. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
512 * @throws ImageException
513 */
514 public function send(int $type = self::JPEG, ?int $quality = null): void
515 {
516 header('Content-Type: ' . self::typeToMimeType($type));
517 $this->output($type, $quality);
518 }
519 /**
520 * Outputs image to browser or file.
521 * @throws ImageException
522 */
523 private function output(int $type, ?int $quality, ?string $file = null): void
524 {
525 switch ($type) {
526 case self::JPEG:
527 $quality = ($quality === null) ? 85 : max(0, min(100, $quality));
528 $success = @imagejpeg($this->image, $file, $quality);
529 // @ is escalated to exception
530 break;
531 case self::PNG:
532 $quality = ($quality === null) ? 9 : max(0, min(9, $quality));
533 $success = @imagepng($this->image, $file, $quality);
534 // @ is escalated to exception
535 break;
536 case self::GIF:
537 $success = @imagegif($this->image, $file);
538 // @ is escalated to exception
539 break;
540 case self::WEBP:
541 $quality = ($quality === null) ? 80 : max(0, min(100, $quality));
542 $success = @imagewebp($this->image, $file, $quality);
543 // @ is escalated to exception
544 break;
545 case self::AVIF:
546 $quality = ($quality === null) ? 30 : max(0, min(100, $quality));
547 $success = @imageavif($this->image, $file, $quality);
548 // @ is escalated to exception
549 break;
550 case self::BMP:
551 $success = @imagebmp($this->image, $file);
552 // @ is escalated to exception
553 break;
554 default:
555 throw new Nette\InvalidArgumentException("Unsupported image type '{$type}'.");
556 }
557 if (!$success) {
558 throw new ImageException(Helpers::getLastError() ?: 'Unknown error');
559 }
560 }
561 /**
562 * Call to undefined method.
563 * @return mixed
564 * @throws Nette\MemberAccessException
565 */
566 public function __call(string $name, array $args)
567 {
568 $function = 'image' . $name;
569 if (!function_exists($function)) {
570 ObjectHelpers::strictCall(static::class, $name);
571 }
572 foreach ($args as $key => $value) {
573 if ($value instanceof self) {
574 $args[$key] = $value->getImageResource();
575 } elseif (is_array($value) && isset($value['red'])) {
576 // rgb
577 $args[$key] = imagecolorallocatealpha($this->image, $value['red'], $value['green'], $value['blue'], $value['alpha']) ?: imagecolorresolvealpha($this->image, $value['red'], $value['green'], $value['blue'], $value['alpha']);
578 }
579 }
580 $res = $function($this->image, ...$args);
581 return ($res instanceof \GdImage || is_resource($res) && get_resource_type($res) === 'gd') ? $this->setImageResource($res) : $res;
582 }
583 public function __clone()
584 {
585 ob_start(function () {
586 });
587 imagepng($this->image, null, 0);
588 $this->setImageResource(imagecreatefromstring(ob_get_clean()));
589 }
590 /**
591 * @param int|string $num in pixels or percent
592 */
593 private static function isPercent(&$num): bool
594 {
595 if (is_string($num) && substr($num, -1) === '%') {
596 $num = (float) substr($num, 0, -1);
597 return \true;
598 } elseif (is_int($num) || $num === (string) (int) $num) {
599 $num = (int) $num;
600 return \false;
601 }
602 throw new Nette\InvalidArgumentException("Expected dimension in int|string, '{$num}' given.");
603 }
604 /**
605 * Prevents serialization.
606 */
607 public function __sleep(): array
608 {
609 throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.');
610 }
611 }
612