Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 150 | Ganze Datei anzeigen | Leerzeichen ignorieren | Details | Blame | Letzte Änderung | Log anzeigen | RSS feed

Revision 150 Revision 399
Zeile 90... Zeile 90...
90
 * @method void stringUp($font, $x, $y, string $s, $col)
90
 * @method void stringUp($font, $x, $y, string $s, $col)
91
 * @method void trueColorToPalette(bool $dither, $ncolors)
91
 * @method void trueColorToPalette(bool $dither, $ncolors)
92
 * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
92
 * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
93
 * @property-read int $width
93
 * @property-read int $width
94
 * @property-read int $height
94
 * @property-read int $height
95
 * @property-read resource|\GdImage $imageResource
95
 * @property-read \GdImage $imageResource
96
 */
96
 */
97
class Image
97
class Image
98
{
98
{
99
	use Nette\SmartObject;
99
	use Nette\SmartObject;
Zeile 100... Zeile 100...
100
 
100
 
101
	/** {@link resize()} only shrinks images */
101
	/** Prevent from getting resized to a bigger size than the original */
Zeile 102... Zeile 102...
102
	public const SHRINK_ONLY = 0b0001;
102
	public const ShrinkOnly = 0b0001;
103
 
103
 
Zeile 104... Zeile 104...
104
	/** {@link resize()} will ignore aspect ratio */
104
	/** Resizes to a specified width and height without keeping aspect ratio */
105
	public const STRETCH = 0b0010;
105
	public const Stretch = 0b0010;
Zeile 106... Zeile 106...
106
 
106
 
107
	/** {@link resize()} fits in given area so its dimensions are less than or equal to the required dimensions */
107
	/** Resizes to fit into a specified width and height and preserves aspect ratio */
Zeile -... Zeile 108...
-
 
108
	public const OrSmaller = 0b0000;
-
 
109
 
-
 
110
	/** Resizes while bounding the smaller dimension to the specified width or height and preserves aspect ratio */
108
	public const FIT = 0b0000;
111
	public const OrBigger = 0b0100;
-
 
112
 
-
 
113
	/** Resizes to the smallest possible size to completely cover specified width and height and reserves aspect ratio */
-
 
114
	public const Cover = 0b1000;
-
 
115
 
-
 
116
	/** @deprecated use Image::ShrinkOnly */
-
 
117
	public const SHRINK_ONLY = self::ShrinkOnly;
-
 
118
 
-
 
119
	/** @deprecated use Image::Stretch */
-
 
120
	public const STRETCH = self::Stretch;
-
 
121
 
-
 
122
	/** @deprecated use Image::OrSmaller */
-
 
123
	public const FIT = self::OrSmaller;
109
 
124
 
-
 
125
	/** @deprecated use Image::OrBigger */
-
 
126
	public const FILL = self::OrBigger;
-
 
127
 
Zeile 110... Zeile 128...
110
	/** {@link resize()} fills given area so its dimensions are greater than or equal to the required dimensions */
128
	/** @deprecated use Image::Cover */
111
	public const FILL = 0b0100;
129
	public const EXACT = self::Cover;
112
 
130
 
113
	/** {@link resize()} fills given area exactly */
131
	/** @deprecated use Image::EmptyGIF */
114
	public const EXACT = 0b1000;
132
	public const EMPTY_GIF = self::EmptyGIF;
115
 
133
 
116
	/** image types */
134
	/** image types */
117
	public const
135
	public const
Zeile 118... Zeile 136...
118
		JPEG = IMAGETYPE_JPEG,
136
		JPEG = IMAGETYPE_JPEG,
Zeile 119... Zeile 137...
119
		PNG = IMAGETYPE_PNG,
137
		PNG = IMAGETYPE_PNG,
Zeile 120... Zeile -...
120
		GIF = IMAGETYPE_GIF,
-
 
121
		WEBP = IMAGETYPE_WEBP,
138
		GIF = IMAGETYPE_GIF,
Zeile 122... Zeile 139...
122
		AVIF = 19, // IMAGETYPE_AVIF,
139
		WEBP = IMAGETYPE_WEBP,
123
		BMP = IMAGETYPE_BMP;
140
		AVIF = 19, // IMAGETYPE_AVIF,
124
 
141
		BMP = IMAGETYPE_BMP;
Zeile 146... Zeile 163...
146
 
163
 
147
	/**
164
	/**
148
	 * Reads an image from a file and returns its type in $type.
165
	 * Reads an image from a file and returns its type in $type.
149
	 * @throws Nette\NotSupportedException if gd extension is not loaded
166
	 * @throws Nette\NotSupportedException if gd extension is not loaded
150
	 * @throws UnknownImageFileException if file not found or file type is not known
-
 
151
	 * @return static
167
	 * @throws UnknownImageFileException if file not found or file type is not known
152
	 */
168
	 */
153
	public static function fromFile(string $file, ?int &$type = null)
169
	public static function fromFile(string $file, ?int &$type = null): static
154
	{
170
	{
155
		if (!extension_loaded('gd')) {
171
		if (!extension_loaded('gd')) {
156
			throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
172
			throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
Zeile 165... Zeile 181...
165
	}
181
	}
Zeile 166... Zeile 182...
166
 
182
 
167
 
183
 
168
	/**
-
 
169
	 * Reads an image from a string and returns its type in $type.
184
	/**
170
	 * @return static
185
	 * Reads an image from a string and returns its type in $type.
171
	 * @throws Nette\NotSupportedException if gd extension is not loaded
186
	 * @throws Nette\NotSupportedException if gd extension is not loaded
172
	 * @throws ImageException
187
	 * @throws ImageException
173
	 */
188
	 */
174
	public static function fromString(string $s, ?int &$type = null)
189
	public static function fromString(string $s, ?int &$type = null): static
175
	{
190
	{
176
		if (!extension_loaded('gd')) {
191
		if (!extension_loaded('gd')) {
Zeile 184... Zeile 199...
184
 
199
 
185
		return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__);
200
		return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__);
Zeile 186... Zeile 201...
186
	}
201
	}
187
 
202
 
188
 
203
 
189
	private static function invokeSafe(string $func, string $arg, string $message, string $callee): self
204
	private static function invokeSafe(string $func, string $arg, string $message, string $callee): static
190
	{
205
	{
191
		$errors = [];
206
		$errors = [];
Zeile 203... Zeile 218...
203
	}
218
	}
Zeile 204... Zeile 219...
204
 
219
 
205
 
220
 
206
	/**
-
 
207
	 * Creates a new true color image of the given dimensions. The default color is black.
221
	/**
208
	 * @return static
222
	 * Creates a new true color image of the given dimensions. The default color is black.
209
	 * @throws Nette\NotSupportedException if gd extension is not loaded
223
	 * @throws Nette\NotSupportedException if gd extension is not loaded
210
	 */
224
	 */
211
	public static function fromBlank(int $width, int $height, ?array $color = null)
225
	public static function fromBlank(int $width, int $height, ?array $color = null): static
212
	{
226
	{
213
		if (!extension_loaded('gd')) {
227
		if (!extension_loaded('gd')) {
Zeile 288... Zeile 302...
288
	}
302
	}
Zeile 289... Zeile 303...
289
 
303
 
290
 
304
 
291
	/**
-
 
292
	 * Wraps GD image.
305
	/**
293
	 * @param  resource|\GdImage  $image
306
	 * Wraps GD image.
294
	 */
307
	 */
295
	public function __construct($image)
308
	public function __construct(\GdImage $image)
296
	{
309
	{
297
		$this->setImageResource($image);
310
		$this->setImageResource($image);
Zeile 317... Zeile 330...
317
	}
330
	}
Zeile 318... Zeile 331...
318
 
331
 
319
 
332
 
320
	/**
-
 
321
	 * Sets image resource.
-
 
322
	 * @param  resource|\GdImage  $image
333
	/**
323
	 * @return static
334
	 * Sets image resource.
324
	 */
335
	 */
325
	protected function setImageResource($image)
-
 
326
	{
-
 
327
		if (!$image instanceof \GdImage && !(is_resource($image) && get_resource_type($image) === 'gd')) {
-
 
328
			throw new Nette\InvalidArgumentException('Image is not valid.');
-
 
329
		}
336
	protected function setImageResource(\GdImage $image): static
330
 
337
	{
331
		$this->image = $image;
338
		$this->image = $image;
Zeile 332... Zeile 339...
332
		return $this;
339
		return $this;
333
	}
340
	}
334
 
-
 
335
 
341
 
336
	/**
342
 
337
	 * Returns image GD resource.
343
	/**
338
	 * @return resource|\GdImage
344
	 * Returns image GD resource.
339
	 */
345
	 */
Zeile 340... Zeile 346...
340
	public function getImageResource()
346
	public function getImageResource(): \GdImage
341
	{
-
 
342
		return $this->image;
347
	{
343
	}
348
		return $this->image;
344
 
-
 
345
 
349
	}
346
	/**
350
 
347
	 * Scales an image.
351
 
348
	 * @param  int|string|null  $width in pixels or percent
352
	/**
349
	 * @param  int|string|null  $height in pixels or percent
353
	 * Scales an image. Width and height accept pixels or percent.
350
	 * @return static
354
	 * @param  self::OrSmaller|self::OrBigger|self::Stretch|self::Cover|self::ShrinkOnly  $mode
Zeile 351... Zeile 355...
351
	 */
355
	 */
Zeile 352... Zeile 356...
352
	public function resize($width, $height, int $flags = self::FIT)
356
	public function resize(int|string|null $width, int|string|null $height, int $mode = self::OrSmaller): static
353
	{
357
	{
354
		if ($flags & self::EXACT) {
358
		if ($mode & self::Cover) {
355
			return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height);
359
			return $this->resize($width, $height, self::OrBigger)->crop('50%', '50%', $width, $height);
Zeile 367... Zeile 371...
367
				0,
371
				0,
368
				0,
372
				0,
369
				$newWidth,
373
				$newWidth,
370
				$newHeight,
374
				$newHeight,
371
				$this->getWidth(),
375
				$this->getWidth(),
372
				$this->getHeight()
376
				$this->getHeight(),
373
			);
377
			);
374
			$this->image = $newImage;
378
			$this->image = $newImage;
375
		}
379
		}
Zeile 376... Zeile 380...
376
 
380
 
Zeile 381... Zeile 385...
381
		return $this;
385
		return $this;
382
	}
386
	}
Zeile 383... Zeile 387...
383
 
387
 
384
 
388
 
385
	/**
-
 
386
	 * Calculates dimensions of resized image.
389
	/**
387
	 * @param  int|string|null  $newWidth in pixels or percent
390
	 * Calculates dimensions of resized image. Width and height accept pixels or percent.
388
	 * @param  int|string|null  $newHeight in pixels or percent
391
	 * @param  self::OrSmaller|self::OrBigger|self::Stretch|self::Cover|self::ShrinkOnly  $mode
389
	 */
392
	 */
390
	public static function calculateSize(
393
	public static function calculateSize(
391
		int $srcWidth,
394
		int $srcWidth,
392
		int $srcHeight,
395
		int $srcHeight,
393
		$newWidth,
396
		$newWidth,
394
		$newHeight,
397
		$newHeight,
395
		int $flags = self::FIT
398
		int $mode = self::OrSmaller,
396
	): array
399
	): array
397
	{
400
	{
398
		if ($newWidth === null) {
401
		if ($newWidth === null) {
Zeile 404... Zeile 407...
404
		}
407
		}
Zeile 405... Zeile 408...
405
 
408
 
406
		if ($newHeight === null) {
409
		if ($newHeight === null) {
407
		} elseif (self::isPercent($newHeight)) {
410
		} elseif (self::isPercent($newHeight)) {
408
			$newHeight = (int) round($srcHeight / 100 * abs($newHeight));
411
			$newHeight = (int) round($srcHeight / 100 * abs($newHeight));
409
			$flags |= empty($percents) ? 0 : self::STRETCH;
412
			$mode |= empty($percents) ? 0 : self::Stretch;
410
		} else {
413
		} else {
411
			$newHeight = abs($newHeight);
414
			$newHeight = abs($newHeight);
Zeile 412... Zeile 415...
412
		}
415
		}
413
 
416
 
414
		if ($flags & self::STRETCH) { // non-proportional
417
		if ($mode & self::Stretch) { // non-proportional
415
			if (!$newWidth || !$newHeight) {
418
			if (!$newWidth || !$newHeight) {
Zeile 416... Zeile 419...
416
				throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.');
419
				throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.');
417
			}
420
			}
418
 
421
 
419
			if ($flags & self::SHRINK_ONLY) {
422
			if ($mode & self::ShrinkOnly) {
420
				$newWidth = (int) round($srcWidth * min(1, $newWidth / $srcWidth));
423
				$newWidth = min($srcWidth, $newWidth);
421
				$newHeight = (int) round($srcHeight * min(1, $newHeight / $srcHeight));
424
				$newHeight = min($srcHeight, $newHeight);
422
			}
425
			}
423
		} else {  // proportional
426
		} else {  // proportional
Zeile 432... Zeile 435...
432
 
435
 
433
			if ($newHeight > 0) { // fit height
436
			if ($newHeight > 0) { // fit height
434
				$scale[] = $newHeight / $srcHeight;
437
				$scale[] = $newHeight / $srcHeight;
Zeile 435... Zeile 438...
435
			}
438
			}
436
 
439
 
437
			if ($flags & self::FILL) {
440
			if ($mode & self::OrBigger) {
Zeile 438... Zeile 441...
438
				$scale = [max($scale)];
441
				$scale = [max($scale)];
439
			}
442
			}
440
 
443
 
Zeile 441... Zeile 444...
441
			if ($flags & self::SHRINK_ONLY) {
444
			if ($mode & self::ShrinkOnly) {
442
				$scale[] = 1;
445
				$scale[] = 1;
Zeile 450... Zeile 453...
450
		return [max($newWidth, 1), max($newHeight, 1)];
453
		return [max($newWidth, 1), max($newHeight, 1)];
451
	}
454
	}
Zeile 452... Zeile 455...
452
 
455
 
453
 
-
 
454
	/**
456
 
455
	 * Crops image.
-
 
456
	 * @param  int|string  $left in pixels or percent
-
 
457
	 * @param  int|string  $top in pixels or percent
-
 
458
	 * @param  int|string  $width in pixels or percent
-
 
459
	 * @param  int|string  $height in pixels or percent
457
	/**
460
	 * @return static
458
	 * Crops image. Arguments accepts pixels or percent.
461
	 */
459
	 */
462
	public function crop($left, $top, $width, $height)
460
	public function crop(int|string $left, int|string $top, int|string $width, int|string $height): static
463
	{
461
	{
464
		[$r['x'], $r['y'], $r['width'], $r['height']]
462
		[$r['x'], $r['y'], $r['width'], $r['height']]
465
			= static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height);
463
			= static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height);
Zeile 475... Zeile 473...
475
		return $this;
473
		return $this;
476
	}
474
	}
Zeile 477... Zeile 475...
477
 
475
 
478
 
476
 
479
	/**
-
 
480
	 * Calculates dimensions of cutout in image.
-
 
481
	 * @param  int|string  $left in pixels or percent
-
 
482
	 * @param  int|string  $top in pixels or percent
-
 
483
	 * @param  int|string  $newWidth in pixels or percent
477
	/**
484
	 * @param  int|string  $newHeight in pixels or percent
478
	 * Calculates dimensions of cutout in image. Arguments accepts pixels or percent.
-
 
479
	 */
-
 
480
	public static function calculateCutout(
-
 
481
		int $srcWidth,
-
 
482
		int $srcHeight,
-
 
483
		int|string $left,
-
 
484
		int|string $top,
-
 
485
		int|string $newWidth,
485
	 */
486
		int|string $newHeight,
486
	public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $top, $newWidth, $newHeight): array
487
	): array
487
	{
488
	{
488
		if (self::isPercent($newWidth)) {
489
		if (self::isPercent($newWidth)) {
Zeile 517... Zeile 518...
517
	}
518
	}
Zeile 518... Zeile 519...
518
 
519
 
519
 
520
 
520
	/**
-
 
521
	 * Sharpens image a little bit.
521
	/**
522
	 * @return static
522
	 * Sharpens image a little bit.
523
	 */
523
	 */
524
	public function sharpen()
524
	public function sharpen(): static
525
	{
525
	{
526
		imageconvolution($this->image, [ // my magic numbers ;)
526
		imageconvolution($this->image, [ // my magic numbers ;)
527
			[-1, -1, -1],
527
			[-1, -1, -1],
Zeile 531... Zeile 531...
531
		return $this;
531
		return $this;
532
	}
532
	}
Zeile 533... Zeile 533...
533
 
533
 
534
 
-
 
535
	/**
-
 
536
	 * Puts another image into this image.
534
 
537
	 * @param  int|string  $left in pixels or percent
535
	/**
538
	 * @param  int|string  $top in pixels or percent
-
 
539
	 * @param  int  $opacity 0..100
536
	 * Puts another image into this image. Left and top accepts pixels or percent.
540
	 * @return static
537
	 * @param  int  $opacity 0..100
541
	 */
538
	 */
542
	public function place(self $image, $left = 0, $top = 0, int $opacity = 100)
539
	public function place(self $image, int|string $left = 0, int|string $top = 0, int $opacity = 100): static
543
	{
540
	{
544
		$opacity = max(0, min(100, $opacity));
541
		$opacity = max(0, min(100, $opacity));
545
		if ($opacity === 0) {
542
		if ($opacity === 0) {
Zeile 589... Zeile 586...
589
			$left,
586
			$left,
590
			$top,
587
			$top,
591
			0,
588
			0,
592
			0,
589
			0,
593
			$width,
590
			$width,
594
			$height
591
			$height,
595
		);
592
		);
596
		return $this;
593
		return $this;
597
	}
594
	}
Zeile 601... Zeile 598...
601
	 * 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).
598
	 * 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).
602
	 * @throws ImageException
599
	 * @throws ImageException
603
	 */
600
	 */
604
	public function save(string $file, ?int $quality = null, ?int $type = null): void
601
	public function save(string $file, ?int $quality = null, ?int $type = null): void
605
	{
602
	{
606
		$type = $type ?? self::extensionToType(pathinfo($file, PATHINFO_EXTENSION));
603
		$type ??= self::extensionToType(pathinfo($file, PATHINFO_EXTENSION));
607
		$this->output($type, $quality, $file);
604
		$this->output($type, $quality, $file);
608
	}
605
	}
Zeile 609... Zeile 606...
609
 
606
 
610
 
607
 
611
	/**
608
	/**
612
	 * 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).
609
	 * 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).
613
	 */
610
	 */
614
	public function toString(int $type = self::JPEG, ?int $quality = null): string
611
	public function toString(int $type = self::JPEG, ?int $quality = null): string
615
	{
612
	{
616
		return Helpers::capture(function () use ($type, $quality) {
613
		return Helpers::capture(function () use ($type, $quality): void {
617
			$this->output($type, $quality);
614
			$this->output($type, $quality);
Zeile 618... Zeile 615...
618
		});
615
		});
619
	}
616
	}
620
 
617
 
621
 
618
 
622
	/**
619
	/**
623
	 * Outputs image to string.
-
 
624
	 */
620
	 * Outputs image to string.
625
	public function __toString(): string
-
 
626
	{
-
 
627
		try {
-
 
628
			return $this->toString();
-
 
629
		} catch (\Throwable $e) {
-
 
630
			if (func_num_args() || PHP_VERSION_ID >= 70400) {
-
 
631
				throw $e;
-
 
632
			}
-
 
633
 
621
	 */
Zeile 634... Zeile 622...
634
			trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
622
	public function __toString(): string
635
			return '';
623
	{
Zeile 693... Zeile 681...
693
	}
681
	}
Zeile 694... Zeile 682...
694
 
682
 
695
 
683
 
696
	/**
-
 
697
	 * Call to undefined method.
684
	/**
698
	 * @return mixed
685
	 * Call to undefined method.
699
	 * @throws Nette\MemberAccessException
686
	 * @throws Nette\MemberAccessException
700
	 */
687
	 */
701
	public function __call(string $name, array $args)
688
	public function __call(string $name, array $args): mixed
702
	{
689
	{
703
		$function = 'image' . $name;
690
		$function = 'image' . $name;
704
		if (!function_exists($function)) {
691
		if (!function_exists($function)) {
Zeile 713... Zeile 700...
713
				$args[$key] = imagecolorallocatealpha(
700
				$args[$key] = imagecolorallocatealpha(
714
					$this->image,
701
					$this->image,
715
					$value['red'],
702
					$value['red'],
716
					$value['green'],
703
					$value['green'],
717
					$value['blue'],
704
					$value['blue'],
718
					$value['alpha']
705
					$value['alpha'],
719
				) ?: imagecolorresolvealpha(
706
				) ?: imagecolorresolvealpha(
720
					$this->image,
707
					$this->image,
721
					$value['red'],
708
					$value['red'],
722
					$value['green'],
709
					$value['green'],
723
					$value['blue'],
710
					$value['blue'],
724
					$value['alpha']
711
					$value['alpha'],
725
				);
712
				);
726
			}
713
			}
727
		}
714
		}
Zeile 728... Zeile 715...
728
 
715
 
729
		$res = $function($this->image, ...$args);
716
		$res = $function($this->image, ...$args);
730
		return $res instanceof \GdImage || (is_resource($res) && get_resource_type($res) === 'gd')
717
		return $res instanceof \GdImage
731
			? $this->setImageResource($res)
718
			? $this->setImageResource($res)
732
			: $res;
719
			: $res;
Zeile 739... Zeile 726...
739
		imagepng($this->image, null, 0);
726
		imagepng($this->image, null, 0);
740
		$this->setImageResource(imagecreatefromstring(ob_get_clean()));
727
		$this->setImageResource(imagecreatefromstring(ob_get_clean()));
741
	}
728
	}
Zeile 742... Zeile -...
742
 
-
 
743
 
-
 
744
	/**
-
 
745
	 * @param  int|string  $num in pixels or percent
729
 
746
	 */
730
 
747
	private static function isPercent(&$num): bool
731
	private static function isPercent(int|string &$num): bool
748
	{
732
	{
749
		if (is_string($num) && substr($num, -1) === '%') {
733
		if (is_string($num) && str_ends_with($num, '%')) {
750
			$num = (float) substr($num, 0, -1);
734
			$num = (float) substr($num, 0, -1);
751
			return true;
735
			return true;
752
		} elseif (is_int($num) || $num === (string) (int) $num) {
736
		} elseif (is_int($num) || $num === (string) (int) $num) {