Skip to content

Commit 2790367

Browse files
committed
added ImageColor, replaces Image::rgb()
1 parent 3b8b227 commit 2790367

File tree

3 files changed

+143
-42
lines changed

3 files changed

+143
-42
lines changed

src/Utils/Image.php

Lines changed: 36 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
* @method array affineMatrixGet(int $type, mixed $options = null)
2828
* @method void alphaBlending(bool $on)
2929
* @method void antialias(bool $on)
30-
* @method void arc($x, $y, $w, $h, $start, $end, $color)
31-
* @method void char(int $font, $x, $y, string $char, $color)
32-
* @method void charUp(int $font, $x, $y, string $char, $color)
30+
* @method void arc($x, $y, $w, $h, $start, $end, ImageColor $color)
31+
* @method void char(int $font, $x, $y, string $char, ImageColor $color)
32+
* @method void charUp(int $font, $x, $y, string $char, ImageColor $color)
3333
* @method int colorAllocate($red, $green, $blue)
3434
* @method int colorAllocateAlpha($red, $green, $blue, $alpha)
3535
* @method int colorAt($x, $y)
@@ -52,14 +52,14 @@
5252
* @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
5353
* @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
5454
* @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
55-
* @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1)
56-
* @method void ellipse($cx, $cy, $w, $h, $color)
57-
* @method void fill($x, $y, $color)
58-
* @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style)
59-
* @method void filledEllipse($cx, $cy, $w, $h, $color)
60-
* @method void filledPolygon(array $points, $numPoints, $color)
61-
* @method void filledRectangle($x1, $y1, $x2, $y2, $color)
62-
* @method void fillToBorder($x, $y, $border, $color)
55+
* @method Image cropAuto(int $mode = -1, float $threshold = .5, ?ImageColor $color = null)
56+
* @method void ellipse($cx, $cy, $w, $h, ImageColor $color)
57+
* @method void fill($x, $y, ImageColor $color)
58+
* @method void filledArc($cx, $cy, $w, $h, $s, $e, ImageColor $color, $style)
59+
* @method void filledEllipse($cx, $cy, $w, $h, ImageColor $color)
60+
* @method void filledPolygon(array $points, $numPoints, ImageColor $color)
61+
* @method void filledRectangle($x1, $y1, $x2, $y2, ImageColor $color)
62+
* @method void fillToBorder($x, $y, $border, ImageColor $color)
6363
* @method void filter($filtertype)
6464
* @method void flip(int $mode)
6565
* @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = null)
@@ -68,28 +68,28 @@
6868
* @method int interlace($interlace = null)
6969
* @method bool isTrueColor()
7070
* @method void layerEffect($effect)
71-
* @method void line($x1, $y1, $x2, $y2, $color)
72-
* @method void openPolygon(array $points, int $num_points, int $color)
71+
* @method void line($x1, $y1, $x2, $y2, ImageColor $color)
72+
* @method void openPolygon(array $points, int $num_points, ImageColor $color)
7373
* @method void paletteCopy(Image $source)
7474
* @method void paletteToTrueColor()
75-
* @method void polygon(array $points, $numPoints, $color)
76-
* @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null)
75+
* @method void polygon(array $points, $numPoints, ImageColor $color)
76+
* @method array psText(string $text, $font, $size, ImageColor $color, ImageColor $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null)
7777
* @method void rectangle($x1, $y1, $x2, $y2, $col)
7878
* @method mixed resolution(int $res_x = null, int $res_y = null)
79-
* @method Image rotate(float $angle, $backgroundColor)
79+
* @method Image rotate(float $angle, ImageColor $backgroundColor)
8080
* @method void saveAlpha(bool $saveflag)
8181
* @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED)
8282
* @method void setBrush(Image $brush)
8383
* @method void setClip(int $x1, int $y1, int $x2, int $y2)
8484
* @method void setInterpolation(int $method = IMG_BILINEAR_FIXED)
85-
* @method void setPixel($x, $y, $color)
85+
* @method void setPixel($x, $y, ImageColor $color)
8686
* @method void setStyle(array $style)
8787
* @method void setThickness($thickness)
8888
* @method void setTile(Image $tile)
8989
* @method void string($font, $x, $y, string $s, $col)
9090
* @method void stringUp($font, $x, $y, string $s, $col)
9191
* @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, ImageColor $color, string $fontfile, string $text)
9393
* @property-read positive-int $width
9494
* @property-read positive-int $height
9595
* @property-read \GdImage $imageResource
@@ -149,6 +149,7 @@ class Image
149149

150150
/**
151151
* Returns RGB color (0..255) and transparency (0..127).
152+
* @deprecated use ImageColor::rgb()
152153
*/
153154
public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array
154155
{
@@ -224,7 +225,7 @@ private static function invokeSafe(string $func, string $arg, string $message, s
224225
* @param positive-int $height
225226
* @throws Nette\NotSupportedException if gd extension is not loaded
226227
*/
227-
public static function fromBlank(int $width, int $height, ?array $color = null): static
228+
public static function fromBlank(int $width, int $height, ImageColor|array|null $color = null): static
228229
{
229230
if (!extension_loaded('gd')) {
230231
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
@@ -234,16 +235,14 @@ public static function fromBlank(int $width, int $height, ?array $color = null):
234235
throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.');
235236
}
236237

237-
$image = imagecreatetruecolor($width, $height);
238+
$image = new static(imagecreatetruecolor($width, $height));
238239
if ($color) {
239-
$color += ['alpha' => 0];
240-
$color = imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']);
241-
imagealphablending($image, false);
242-
imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color);
243-
imagealphablending($image, true);
240+
$image->alphablending(false);
241+
$image->filledrectangle(0, 0, $width - 1, $height - 1, $color);
242+
$image->alphablending(true);
244243
}
245244

246-
return new static($image);
245+
return $image;
247246
}
248247

249248

@@ -389,7 +388,7 @@ public function resize(int|string|null $width, int|string|null $height, int $mod
389388
[$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $mode);
390389

391390
if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize
392-
$newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource();
391+
$newImage = static::fromBlank($newWidth, $newHeight, ImageColor::rgb(0, 0, 0, 0))->getImageResource();
393392
imagecopyresampled(
394393
$newImage,
395394
$this->image,
@@ -492,7 +491,7 @@ public function crop(int|string $left, int|string $top, int|string $width, int|s
492491
$this->image = imagecrop($this->image, $r);
493492
imagesavealpha($this->image, true);
494493
} else {
495-
$newImage = static::fromBlank($r['width'], $r['height'], self::RGB(0, 0, 0, 127))->getImageResource();
494+
$newImage = static::fromBlank($r['width'], $r['height'], ImageColor::rgb(0, 0, 0, 0))->getImageResource();
496495
imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']);
497496
$this->image = $newImage;
498497
}
@@ -727,20 +726,8 @@ public function __call(string $name, array $args): mixed
727726
if ($value instanceof self) {
728727
$args[$key] = $value->getImageResource();
729728

730-
} elseif (is_array($value) && isset($value['red'])) { // rgb
731-
$args[$key] = imagecolorallocatealpha(
732-
$this->image,
733-
$value['red'],
734-
$value['green'],
735-
$value['blue'],
736-
$value['alpha'],
737-
) ?: imagecolorresolvealpha(
738-
$this->image,
739-
$value['red'],
740-
$value['green'],
741-
$value['blue'],
742-
$value['alpha'],
743-
);
729+
} elseif ($value instanceof ImageColor || (is_array($value) && isset($value['red']))) {
730+
$args[$key] = $this->resolveColor($value);
744731
}
745732
}
746733

@@ -780,4 +767,11 @@ public function __sleep(): array
780767
{
781768
throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.');
782769
}
770+
771+
772+
public function resolveColor(ImageColor|array $color): int
773+
{
774+
$color = $color instanceof ImageColor ? $color->toRGBA() : array_values($color);
775+
return imagecolorallocatealpha($this->image, ...$color) ?: imagecolorresolvealpha($this->image, ...$color);
776+
}
783777
}

src/Utils/ImageColor.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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+
8+
declare(strict_types=1);
9+
10+
namespace Nette\Utils;
11+
12+
use Nette;
13+
14+
15+
/**
16+
* Represent RGB color (0..255) with opacity (0..1).
17+
*/
18+
class ImageColor
19+
{
20+
public static function rgb(int $red, int $green, int $blue, float $opacity = 1): self
21+
{
22+
return new self($red, $green, $blue, $opacity);
23+
}
24+
25+
26+
/**
27+
* Accepts formats #RRGGBB, #RRGGBBAA, #RGB, #RGBA
28+
*/
29+
public static function hex(string $hex): self
30+
{
31+
$hex = ltrim($hex, '#');
32+
$len = strlen($hex);
33+
if ($len === 3 || $len === 4) {
34+
return new self(
35+
(int) hexdec($hex[0]) * 17,
36+
(int) hexdec($hex[1]) * 17,
37+
(int) hexdec($hex[2]) * 17,
38+
(int) hexdec($hex[3] ?? 'F') * 17 / 255,
39+
);
40+
} elseif ($len === 6 || $len === 8) {
41+
return new self(
42+
(int) hexdec($hex[0] . $hex[1]),
43+
(int) hexdec($hex[2] . $hex[3]),
44+
(int) hexdec($hex[4] . $hex[5]),
45+
(int) hexdec(($hex[6] ?? 'F') . ($hex[7] ?? 'F')) / 255,
46+
);
47+
} else {
48+
throw new Nette\InvalidArgumentException('Invalid hex color format.');
49+
}
50+
}
51+
52+
53+
private function __construct(
54+
public int $red,
55+
public int $green,
56+
public int $blue,
57+
public float $opacity = 1,
58+
) {
59+
$this->red = max(0, min(255, $red));
60+
$this->green = max(0, min(255, $green));
61+
$this->blue = max(0, min(255, $blue));
62+
$this->opacity = max(0, min(1, $opacity));
63+
}
64+
65+
66+
public function toRGBA(): array
67+
{
68+
return [
69+
max(0, min(255, $this->red)),
70+
max(0, min(255, $this->green)),
71+
max(0, min(255, $this->blue)),
72+
max(0, min(127, (int) round(127 - $this->opacity * 127))),
73+
];
74+
}
75+
}

tests/Utils/Image.color.phpt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\Utils\ImageColor
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Utils\ImageColor;
10+
use Tester\Assert;
11+
12+
13+
require __DIR__ . '/../bootstrap.php';
14+
15+
16+
test('hex()', function () {
17+
Assert::equal(ImageColor::rgb(17, 34, 51, 1.0), ImageColor::hex('#123'));
18+
Assert::equal(ImageColor::rgb(17, 34, 51, 0.8 / 3), ImageColor::hex('#1234'));
19+
Assert::equal(ImageColor::rgb(18, 52, 86), ImageColor::hex('#123456'));
20+
Assert::equal(ImageColor::rgb(18, 52, 86, 120 / 255), ImageColor::hex('#12345678'));
21+
22+
Assert::exception(
23+
fn() => ImageColor::hex('#12'),
24+
InvalidArgumentException::class,
25+
'Invalid hex color format.',
26+
);
27+
});
28+
29+
test('toRGBA()', function () {
30+
Assert::same((ImageColor::rgb(0, 1, 2, 0.3))->toRGBA(), [0, 1, 2, 89]);
31+
Assert::same((ImageColor::rgb(1000, -1, 1000, -10))->toRGBA(), [255, 0, 255, 127]);
32+
});

0 commit comments

Comments
 (0)