update lock clucknut
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 14s
Build, Push and Deploy / build-and-push (push) Successful in 3m14s
Build, Push and Deploy / deploy-staging (push) Successful in 25s
Build, Push and Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-04-18 20:32:18 +07:00
parent 4554035227
commit dcaf267458
3359 changed files with 153185 additions and 205489 deletions

View File

@@ -24,7 +24,7 @@
* $image->send();
* </code>
*
* @method Image affine(array $affine, ?array $clip = null)
* @method Image affine(array<int, float|int> $affine, ?array{x: int, y: int, width: int, height: int} $clip = null)
* @method void alphaBlending(bool $enable)
* @method void antialias(bool $enable)
* @method void arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color)
@@ -41,51 +41,51 @@
* @method int colorResolve(int $red, int $green, int $blue)
* @method int colorResolveAlpha(int $red, int $green, int $blue, int $alpha)
* @method void colorSet(int $index, int $red, int $green, int $blue, int $alpha = 0)
* @method array colorsForIndex(int $color)
* @method array{red: int, green: int, blue: int, alpha: int} colorsForIndex(int $color)
* @method int colorsTotal()
* @method int colorTransparent(?int $color = null)
* @method void convolution(array $matrix, float $div, float $offset)
* @method void convolution(array<int, array<int, float>> $matrix, float $div, float $offset)
* @method void copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH)
* @method void copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $pct)
* @method void copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $pct)
* @method void copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH)
* @method void copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH)
* @method Image cropAuto(int $mode = IMG_CROP_DEFAULT, float $threshold = .5, ?ImageColor $color = null)
* @method Image cropAuto(int $mode = 0, float $threshold = .5, ?ImageColor $color = null)
* @method void ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color)
* @method void fill(int $x, int $y, ImageColor $color)
* @method void filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style)
* @method void filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color)
* @method void filledPolygon(array $points, ImageColor $color)
* @method void filledPolygon(array<int, int> $points, ImageColor $color)
* @method void filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color)
* @method void fillToBorder(int $x, int $y, ImageColor $borderColor, ImageColor $color)
* @method void filter(int $filter, ...$args)
* @method void flip(int $mode)
* @method array ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options = [])
* @method array<int, int> ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array<string, mixed> $options = [])
* @method void gammaCorrect(float $inputgamma, float $outputgamma)
* @method array getClip()
* @method array{int, int, int, int} getClip()
* @method int getInterpolation()
* @method int interlace(?bool $enable = null)
* @method bool isTrueColor()
* @method void layerEffect(int $effect)
* @method void line(int $x1, int $y1, int $x2, int $y2, ImageColor $color)
* @method void openPolygon(array $points, ImageColor $color)
* @method void openPolygon(array<int, int> $points, ImageColor $color)
* @method void paletteCopy(Image $source)
* @method void paletteToTrueColor()
* @method void polygon(array $points, ImageColor $color)
* @method void polygon(array<int, int> $points, ImageColor $color)
* @method void rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color)
* @method mixed resolution(?int $resolutionX = null, ?int $resolutionY = null)
* @method Image rotate(float $angle, ImageColor $backgroundColor)
* @method void saveAlpha(bool $enable)
* @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED)
* @method Image scale(int $newWidth, int $newHeight = -1, int $mode = 3)
* @method void setBrush(Image $brush)
* @method void setClip(int $x1, int $y1, int $x2, int $y2)
* @method void setInterpolation(int $method = IMG_BILINEAR_FIXED)
* @method void setInterpolation(int $method = 3)
* @method void setPixel(int $x, int $y, ImageColor $color)
* @method void setStyle(array $style)
* @method void setStyle(array<int, int> $style)
* @method void setThickness(int $thickness)
* @method void setTile(Image $tile)
* @method void trueColorToPalette(bool $dither, int $ncolors)
* @method array ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontfile, string $text, array $options = [])
* @method array<int, int> ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontfile, string $text, array<string, mixed> $options = [])
* @property-read positive-int $width
* @property-read positive-int $height
* @property-read \GdImage $imageResource
@@ -146,6 +146,7 @@ class Image
/**
* Returns RGB color (0..255) and transparency (0..127).
* @deprecated use ImageColor::rgb()
* @return array{red: int, green: int, blue: int, alpha: int}
*/
public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array
{
@@ -192,6 +193,7 @@ public static function fromString(string $s, ?int &$type = null): static
}
/** @param callable-string $func */
private static function invokeSafe(string $func, string $arg, string $message, string $callee): static
{
$errors = [];
@@ -213,6 +215,7 @@ private static function invokeSafe(string $func, string $arg, string $message, s
* Creates a new true color image of the given dimensions. The default color is black.
* @param positive-int $width
* @param positive-int $height
* @param ImageColor|array{red: int, green: int, blue: int, alpha?: int}|null $color
* @throws Nette\NotSupportedException if gd extension is not loaded
*/
public static function fromBlank(int $width, int $height, ImageColor|array|null $color = null): static
@@ -224,9 +227,9 @@ public static function fromBlank(int $width, int $height, ImageColor|array|null
$image = new static(imagecreatetruecolor($width, $height));
if ($color) {
$image->alphablending(false);
$image->filledrectangle(0, 0, $width - 1, $height - 1, $color);
$image->alphablending(true);
$image->alphaBlending(false);
$image->filledRectangle(0, 0, $width - 1, $height - 1, self::normalizeColor($color));
$image->alphaBlending(true);
}
return $image;
@@ -235,9 +238,11 @@ public static function fromBlank(int $width, int $height, ImageColor|array|null
/**
* Returns the type of image from file.
* @return ImageType::*|null
* @param-out ?int $width
* @param-out ?int $height
* @return ?ImageType::*
*/
public static function detectTypeFromFile(string $file, &$width = null, &$height = null): ?int
public static function detectTypeFromFile(string $file, mixed &$width = null, mixed &$height = null): ?int
{
[$width, $height, $type] = Helpers::falseToNull(@getimagesize($file)); // @ - files smaller than 12 bytes causes read error
return $type && isset(self::Formats[$type]) ? $type : null;
@@ -246,9 +251,11 @@ public static function detectTypeFromFile(string $file, &$width = null, &$height
/**
* Returns the type of image from string.
* @return ImageType::*|null
* @param-out ?int $width
* @param-out ?int $height
* @return ?ImageType::*
*/
public static function detectTypeFromString(string $s, &$width = null, &$height = null): ?int
public static function detectTypeFromString(string $s, mixed &$width = null, mixed &$height = null): ?int
{
[$width, $height, $type] = Helpers::falseToNull(@getimagesizefromstring($s)); // @ - strings smaller than 12 bytes causes read error
return $type && isset(self::Formats[$type]) ? $type : null;
@@ -314,7 +321,7 @@ public static function isTypeSupported(int $type): bool
}
/** @return ImageType[] */
/** @return ImageType::*[] */
public static function getSupportedTypes(): array
{
self::ensureExtension();
@@ -386,6 +393,10 @@ public function getImageResource(): \GdImage
public function resize(int|string|null $width, int|string|null $height, int $mode = self::OrSmaller): static
{
if ($mode & self::Cover) {
if ($width === null || $height === null) {
throw new Nette\InvalidArgumentException('Both width and height must be set for Cover mode.');
}
return $this->resize($width, $height, self::OrBigger)->crop('50%', '50%', $width, $height);
}
@@ -419,12 +430,13 @@ public function resize(int|string|null $width, int|string|null $height, int $mod
/**
* Calculates dimensions of resized image. Width and height accept pixels or percent.
* @param int-mask-of<self::OrSmaller|self::OrBigger|self::Stretch|self::Cover|self::ShrinkOnly> $mode
* @return array{int<1, max>, int<1, max>}
*/
public static function calculateSize(
int $srcWidth,
int $srcHeight,
$newWidth,
$newHeight,
int|string|null $newWidth,
int|string|null $newHeight,
int $mode = self::OrSmaller,
): array
{
@@ -468,19 +480,19 @@ public static function calculateSize(
}
if ($mode & self::OrBigger) {
$scale = [max($scale)];
$scale = [max($scale ?: [1])];
}
if ($mode & self::ShrinkOnly) {
$scale[] = 1;
}
$scale = min($scale);
$scale = min($scale ?: [1]);
$newWidth = (int) round($srcWidth * $scale);
$newHeight = (int) round($srcHeight * $scale);
}
return [max($newWidth, 1), max($newHeight, 1)];
return [max((int) $newWidth, 1), max((int) $newHeight, 1)];
}
@@ -495,7 +507,7 @@ public function crop(int|string $left, int|string $top, int|string $width, int|s
$this->image = imagecrop($this->image, $r);
imagesavealpha($this->image, true);
} else {
$newImage = static::fromBlank($r['width'], $r['height'], ImageColor::rgb(0, 0, 0, 0))->getImageResource();
$newImage = static::fromBlank(max(1, $r['width']), max(1, $r['height']), ImageColor::rgb(0, 0, 0, 0))->getImageResource();
imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']);
$this->image = $newImage;
}
@@ -506,6 +518,7 @@ public function crop(int|string $left, int|string $top, int|string $width, int|s
/**
* Calculates dimensions of cutout in image. Arguments accepts pixels or percent.
* @return array{int, int, int, int}
*/
public static function calculateCutout(
int $srcWidth,
@@ -516,21 +529,10 @@ public static function calculateCutout(
int|string $newHeight,
): array
{
if (self::isPercent($newWidth)) {
$newWidth = (int) round($srcWidth / 100 * $newWidth);
}
if (self::isPercent($newHeight)) {
$newHeight = (int) round($srcHeight / 100 * $newHeight);
}
if (self::isPercent($left)) {
$left = (int) round(($srcWidth - $newWidth) / 100 * $left);
}
if (self::isPercent($top)) {
$top = (int) round(($srcHeight - $newHeight) / 100 * $top);
}
$newWidth = (int) (self::isPercent($newWidth) ? round($srcWidth / 100 * $newWidth) : $newWidth);
$newHeight = (int) (self::isPercent($newHeight) ? round($srcHeight / 100 * $newHeight) : $newHeight);
$left = (int) (self::isPercent($left) ? round(($srcWidth - $newWidth) / 100 * $left) : $left);
$top = (int) (self::isPercent($top) ? round(($srcHeight - $newHeight) / 100 * $top) : $top);
if ($left < 0) {
$newWidth += $left;
@@ -575,14 +577,8 @@ public function place(self $image, int|string $left = 0, int|string $top = 0, in
$width = $image->getWidth();
$height = $image->getHeight();
if (self::isPercent($left)) {
$left = (int) round(($this->getWidth() - $width) / 100 * $left);
}
if (self::isPercent($top)) {
$top = (int) round(($this->getHeight() - $height) / 100 * $top);
}
$left = (int) (self::isPercent($left) ? round(($this->getWidth() - $width) / 100 * $left) : $left);
$top = (int) (self::isPercent($top) ? round(($this->getHeight() - $height) / 100 * $top) : $top);
$output = $input = $image->image;
if ($opacity < 100) {
@@ -595,7 +591,7 @@ public function place(self $image, int|string $left = 0, int|string $top = 0, in
imagealphablending($output, false);
if (!$image->isTrueColor()) {
$input = $output;
imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127));
imagefilledrectangle($output, 0, 0, $width, $height, (int) imagecolorallocatealpha($output, 0, 0, 0, 127));
imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height);
}
@@ -626,6 +622,8 @@ public function place(self $image, int|string $left = 0, int|string $top = 0, in
/**
* Calculates the bounding box for a TrueType text. Returns keys left, top, width and height.
* @param array<string, mixed> $options
* @return array{left: int, top: int, width: int, height: int}
*/
public static function calculateTextBox(
string $text,
@@ -670,7 +668,7 @@ public function filledRectangleWH(int $x, int $y, int $width, int $height, Image
/**
* 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).
* @param ImageType::*|null $type
* @param ?ImageType::* $type
* @throws ImageException
*/
public function save(string $file, ?int $quality = null, ?int $type = null): void
@@ -746,6 +744,7 @@ private function output(int $type, ?int $quality, ?string $file = null): void
/**
* Call to undefined method.
* @param mixed[] $args
* @throws Nette\MemberAccessException
*/
public function __call(string $name, array $args): mixed
@@ -760,6 +759,7 @@ public function __call(string $name, array $args): mixed
$args[$key] = $value->getImageResource();
} elseif ($value instanceof ImageColor || (is_array($value) && isset($value['red']))) {
/** @var ImageColor|array{red: int, green: int, blue: int, alpha?: int} $value */
$args[$key] = $this->resolveColor($value);
}
}
@@ -775,10 +775,11 @@ public function __clone()
{
ob_start(fn() => '');
imagepng($this->image, null, 0);
$this->setImageResource(imagecreatefromstring(ob_get_clean()));
$this->setImageResource(imagecreatefromstring(ob_get_clean()) ?: throw new Nette\ShouldNotHappenException);
}
/** @param-out int|float $num */
private static function isPercent(int|string &$num): bool
{
if (is_string($num) && str_ends_with($num, '%')) {
@@ -802,13 +803,30 @@ public function __serialize(): array
}
/**
* @param ImageColor|array{red: int, green: int, blue: int, alpha?: int} $color
*/
public function resolveColor(ImageColor|array $color): int
{
$color = $color instanceof ImageColor ? $color->toRGBA() : array_values($color);
$color = self::normalizeColor($color)->toRGBA();
return imagecolorallocatealpha($this->image, ...$color) ?: imagecolorresolvealpha($this->image, ...$color);
}
/** @param ImageColor|array{red: int, green: int, blue: int, alpha?: int} $color */
private static function normalizeColor(ImageColor|array $color): ImageColor
{
return $color instanceof ImageColor
? $color
: ImageColor::rgb(
$color['red'],
$color['green'],
$color['blue'],
(127 - ($color['alpha'] ?? 0)) / 127,
);
}
private static function ensureExtension(): void
{
if (!extension_loaded('gd')) {