LICENSE000064400000002067150731646620005570 0ustar00The MIT License (MIT) Copyright (c) 2023 Oliver Vogel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. composer.json000064400000001530150731646620007277 0ustar00{ "name": "intervention/image", "description": "PHP image manipulation", "homepage": "https://image.intervention.io/", "keywords": ["image", "gd", "imagick", "watermark", "thumbnail", "resize"], "license": "MIT", "authors": [ { "name": "Oliver Vogel", "email": "oliver@intervention.io", "homepage": "https://intervention.io/" } ], "require": { "php": "^8.1", "ext-mbstring": "*", "intervention/gif": "^3" }, "require-dev": { "phpunit/phpunit": "^9", "mockery/mockery": "^1.6", "phpstan/phpstan": "^1" }, "autoload": { "psr-4": { "Intervention\\Image\\": "src" } }, "autoload-dev": { "psr-4": { "Intervention\\Image\\Tests\\": "tests" } } } src/Analyzers/AbstractAnalyzer.php000064400000000633150731646620013301 0ustar00analyze($this); } } src/Analyzers/ColorspaceAnalyzer.php000064400000000146150731646620013627 0ustar00items); } /** * Returns Iterator * * @return Traversable */ public function getIterator(): Traversable { return new ArrayIterator($this->items); } public function toArray(): array { return $this->items; } /** * Count items in collection * * @return integer */ public function count(): int { return count($this->items); } /** * Append new item to collection * * @param mixed $item * @return CollectionInterface */ public function push($item): CollectionInterface { $this->items[] = $item; return $this; } /** * Return first item in collection * * @return mixed */ public function first(): mixed { if ($item = reset($this->items)) { return $item; } return null; } /** * Returns last item in collection * * @return mixed */ public function last(): mixed { if ($item = end($this->items)) { return $item; } return null; } /** * Return item at given position starting at 0 * * @param integer $key * @return mixed */ public function getAtPosition(int $key = 0, $default = null): mixed { if ($this->count() == 0) { return $default; } $positions = array_values($this->items); if (! array_key_exists($key, $positions)) { return $default; } return $positions[$key]; } public function get(int|string $query, $default = null): mixed { if ($this->count() == 0) { return $default; } if (is_int($query) && array_key_exists($query, $this->items)) { return $this->items[$query]; } if (is_string($query) && strpos($query, '.') === false) { return array_key_exists($query, $this->items) ? $this->items[$query] : $default; } $query = explode('.', $query); $result = $default; $items = $this->items; foreach ($query as $key) { if (!is_array($items) || !array_key_exists($key, $items)) { $result = $default; break; } $result = $items[$key]; $items = $result; } return $result; } public function map(callable $callback): self { $items = array_map(function ($item) use ($callback) { return $callback($item); }, $this->items); return new self($items); } /** * Run callback on each item of the collection an remove it if it does not return true * * @param callable $callback * @return Collection */ public function filter(callable $callback): self { $items = array_filter($this->items, function ($item) use ($callback) { return $callback($item); }); return new self($items); } public function pushEach(array $data, ?callable $callback = null): CollectionInterface { if (! is_iterable($data)) { throw new RuntimeException('Unable to iterate given data.'); } foreach ($data as $item) { $this->push(is_callable($callback) ? $callback($item) : $item); } return $this; } public function empty(): CollectionInterface { $this->items = []; return $this; } } src/Colors/AbstractColor.php000064400000003567150731646620012074 0ustar00channels; } public function channel(string $classname): ColorChannelInterface { $channels = array_filter($this->channels(), function (ColorChannelInterface $channel) use ($classname) { return get_class($channel) == $classname; }); if (count($channels) == 0) { throw new ColorException('Channel ' . $classname . ' could not be found.'); } return reset($channels); } public function normalize(): array { return array_map(function (ColorChannelInterface $channel) { return $channel->normalize(); }, $this->channels()); } /** * {@inheritdoc} * * @see ColorInterface::toArray() */ public function toArray(): array { return array_map(function (ColorChannelInterface $channel) { return $channel->value(); }, $this->channels()); } /** * {@inheritdoc} * * @see ColorInterface::convertTo() */ public function convertTo(string|ColorspaceInterface $colorspace): ColorInterface { $colorspace = match (true) { is_object($colorspace) => $colorspace, default => new $colorspace(), }; return $colorspace->importColor($this); } /** * {@inheritdoc} * * @see ColorInterface::__toString() */ public function __toString(): string { return $this->toString(); } } src/Colors/AbstractColorChannel.php000064400000004074150731646620013357 0ustar00value = $this->validate( match (true) { is_null($value) && is_numeric($normalized) => intval(round($normalized * $this->max())), is_numeric($value) && is_null($normalized) => $value, default => throw new ColorException('Color channels must either have a value or a normalized value') } ); } /** * Alias of value() * * @return int */ public function toInt(): int { return $this->value; } /** * {@inheritdoc} * * @see ColorChannelInterface::value() */ public function value(): int { return $this->value; } /** * {@inheritdoc} * * @see ColorChannelInterface::normalize() */ public function normalize($precision = 32): float { return round(($this->value() - $this->min()) / ($this->max() - $this->min()), $precision); } /** * {@inheritdoc} * * @see ColorChannelInterface::validate() */ public function validate(mixed $value): mixed { if ($value < $this->min() || $value > $this->max()) { throw new ColorException('Color channel value must be in range ' . $this->min() . ' to ' . $this->max()); } return $value; } /** * {@inheritdoc} * * @see ColorChannelInterface::toString() */ public function toString(): string { return (string) $this->value(); } /** * {@inheritdoc} * * @see ColorChannelInterface::__toString() */ public function __toString(): string { return $this->toString(); } } src/Colors/Cmyk/Channels/Cyan.php000064400000000431150731646620012645 0ustar00channels = [ new Cyan($c), new Magenta($m), new Yellow($y), new Key($k), ]; } public static function create(mixed $input): ColorInterface { return (new class ([ Decoders\StringColorDecoder::class, ]) extends AbstractInputHandler { })->handle($input); } public function colorspace(): ColorspaceInterface { return new Colorspace(); } public function toHex(string $prefix = ''): string { return $this->convertTo(RgbColorspace::class)->toHex($prefix); } public function cyan(): ColorChannelInterface { return $this->channel(Cyan::class); } public function magenta(): ColorChannelInterface { return $this->channel(Magenta::class); } public function yellow(): ColorChannelInterface { return $this->channel(Yellow::class); } public function key(): ColorChannelInterface { return $this->channel(Key::class); } public function toString(): string { return sprintf( 'cmyk(%d%%, %d%%, %d%%, %d%%)', $this->cyan()->value(), $this->magenta()->value(), $this->yellow()->value(), $this->key()->value() ); } public function isGreyscale(): bool { return 0 === array_sum([ $this->cyan()->value(), $this->magenta()->value(), $this->yellow()->value(), ]); } } src/Colors/Cmyk/Colorspace.php000064400000003760150731646620012322 0ustar00value(); }, self::$channels, $normalized); return new Color(...$values); } /** * {@inheritdoc} * * @see ColorspaceInterface::importColor() */ public function importColor(ColorInterface $color): ColorInterface { return match (get_class($color)) { RgbColor::class => $this->importRgbColor($color), HsvColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), HslColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), default => $color, }; } protected function importRgbColor(RgbColor $color): CmykColor { $c = (255 - $color->red()->value()) / 255.0 * 100; $m = (255 - $color->green()->value()) / 255.0 * 100; $y = (255 - $color->blue()->value()) / 255.0 * 100; $k = intval(round(min([$c, $m, $y]))); $c = intval(round($c - $k)); $m = intval(round($m - $k)); $y = intval(round($y - $k)); return new CmykColor($c, $m, $y, $k); } } src/Colors/Cmyk/Decoders/StringColorDecoder.php000064400000002334150731646620015507 0ustar00[0-9\.]+%?), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?)\)$/i'; if (preg_match($pattern, $input, $matches) != 1) { throw new DecoderException('Unable to decode input'); } $values = array_map(function ($value) { return intval(round(floatval(trim(str_replace('%', '', $value))))); }, [$matches['c'], $matches['m'], $matches['y'], $matches['k']]); return new Color(...$values); } } src/Colors/Hsl/Channels/Hue.php000064400000000701150731646620012317 0ustar00channels = [ new Hue($h), new Saturation($s), new Luminance($l), ]; } public function colorspace(): ColorspaceInterface { return new Colorspace(); } /** * {@inheritdoc} * * @see ColorInterface::create() */ public static function create(mixed $input): ColorInterface { return (new class ([ Decoders\StringColorDecoder::class, ]) extends AbstractInputHandler { })->handle($input); } /** * Return the Hue channel * * @return ColorChannelInterface */ public function hue(): ColorChannelInterface { return $this->channel(Hue::class); } /** * Return the Saturation channel * * @return ColorChannelInterface */ public function saturation(): ColorChannelInterface { return $this->channel(Saturation::class); } /** * Return the Luminance channel * * @return ColorChannelInterface */ public function luminance(): ColorChannelInterface { return $this->channel(Luminance::class); } public function toHex(string $prefix = ''): string { return $this->convertTo(RgbColorspace::class)->toHex($prefix); } /** * {@inheritdoc} * * @see ColorInterface::toString() */ public function toString(): string { return sprintf( 'hsl(%d, %d%%, %d%%)', $this->hue()->value(), $this->saturation()->value(), $this->luminance()->value() ); } /** * {@inheritdoc} * * @see ColorInterface::isGreyscale() */ public function isGreyscale(): bool { return $this->saturation()->value() == 0; } } src/Colors/Hsl/Colorspace.php000064400000006445150731646620012150 0ustar00value(); }, self::$channels, $normalized); return new Color(...$values); } public function importColor(ColorInterface $color): ColorInterface { return match (get_class($color)) { CmykColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), RgbColor::class => $this->importRgbColor($color), HsvColor::class => $this->importHsvColor($color), default => $color, }; } protected function importRgbColor(RgbColor $color): ColorInterface { // normalized values of rgb channels $values = array_map(function ($channel) { return $channel->normalize(); }, $color->channels()); // take only RGB $values = array_slice($values, 0, 3); // calculate Luminance $min = min(...$values); $max = max(...$values); $luminance = ($max + $min) / 2; $delta = $max - $min; // calculate saturation $saturation = match (true) { $delta == 0 => 0, default => $delta / (1 - abs(2 * $luminance - 1)), }; // calculate hue list($r, $g, $b) = $values; $hue = match (true) { ($delta == 0) => 0, ($max == $r) => 60 * fmod((($g - $b) / $delta), 6), ($max == $g) => 60 * ((($b - $r) / $delta) + 2), ($max == $b) => 60 * ((($r - $g) / $delta) + 4), default => 0, }; $hue = ($hue + 360) % 360; // normalize hue return new Color( intval(round($hue)), intval(round($saturation * 100)), intval(round($luminance * 100)), ); } protected function importHsvColor(HsvColor $color): ColorInterface { // normalized values of hsv channels list($h, $s, $v) = array_map(function ($channel) { return $channel->normalize(); }, $color->channels()); // calculate Luminance $luminance = (2 - $s) * $v / 2; // calculate Saturation $saturation = match (true) { $luminance == 0 => $s, $luminance == 1 => 0, $luminance < .5 => $s * $v / ($luminance * 2), default => $s * $v / (2 - $luminance * 2), }; return new Color( intval(round($h * 360)), intval(round($saturation * 100)), intval(round($luminance * 100)), ); } } src/Colors/Hsl/Decoders/StringColorDecoder.php000064400000002432150731646620015331 0ustar00[0-9\.]+), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?)?\)$/i'; if (preg_match($pattern, $input, $matches) != 1) { throw new DecoderException('Unable to decode input'); } $values = array_map(function ($value) { return match (strpos($value, '%')) { false => intval(trim($value)), default => intval(trim(str_replace('%', '', $value))), }; }, [$matches['h'], $matches['s'], $matches['l']]); return new Color(...$values); } } src/Colors/Hsv/Channels/Hue.php000064400000000701150731646620012331 0ustar00channels = [ new Hue($h), new Saturation($s), new Value($v), ]; } public function colorspace(): ColorspaceInterface { return new Colorspace(); } /** * {@inheritdoc} * * @see ColorInterface::create() */ public static function create(mixed $input): ColorInterface { return (new class ([ Decoders\StringColorDecoder::class, ]) extends AbstractInputHandler { })->handle($input); } /** * Return the Hue channel * * @return ColorChannelInterface */ public function hue(): ColorChannelInterface { return $this->channel(Hue::class); } /** * Return the Saturation channel * * @return ColorChannelInterface */ public function saturation(): ColorChannelInterface { return $this->channel(Saturation::class); } /** * Return the Value channel * * @return ColorChannelInterface */ public function value(): ColorChannelInterface { return $this->channel(Value::class); } public function toHex(string $prefix = ''): string { return $this->convertTo(RgbColorspace::class)->toHex($prefix); } /** * {@inheritdoc} * * @see ColorInterface::toString() */ public function toString(): string { return sprintf( 'hsv(%d, %d%%, %d%%)', $this->hue()->value(), $this->saturation()->value(), $this->value()->value() ); } /** * {@inheritdoc} * * @see ColorInterface::isGreyscale() */ public function isGreyscale(): bool { return $this->saturation()->value() == 0; } } src/Colors/Hsv/Colorspace.php000064400000005623150731646620012157 0ustar00value(); }, self::$channels, $normalized); return new Color(...$values); } /** * {@inheritdoc} * * @see ColorspaceInterface::importColor() */ public function importColor(ColorInterface $color): ColorInterface { return match (get_class($color)) { CmykColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), RgbColor::class => $this->importRgbColor($color), HslColor::class => $this->importHslColor($color), default => $color, }; } protected function importRgbColor(RgbColor $color): ColorInterface { // normalized values of rgb channels $values = array_map(function ($channel) { return $channel->normalize(); }, $color->channels()); // take only RGB $values = array_slice($values, 0, 3); // calculate chroma $min = min(...$values); $max = max(...$values); $chroma = $max - $min; // calculate value $v = 100 * $max; if ($chroma == 0) { // greyscale color return new Color(0, 0, intval(round($v))); } // calculate saturation $s = 100 * ($chroma / $max); // calculate hue list($r, $g, $b) = $values; $h = match (true) { ($r == $min) => 3 - (($g - $b) / $chroma), ($b == $min) => 1 - (($r - $g) / $chroma), default => 5 - (($b - $r) / $chroma), } * 60; return new Color( intval(round($h)), intval(round($s)), intval(round($v)) ); } protected function importHslColor(HslColor $color): ColorInterface { // normalized values of hsl channels list($h, $s, $l) = array_map(function ($channel) { return $channel->normalize(); }, $color->channels()); $v = $l + $s * min($l, 1 - $l); $s = ($v == 0) ? 0 : 2 * (1 - $l / $v); return $this->colorFromNormalized([$h, $s, $v]); } } src/Colors/Hsv/Decoders/StringColorDecoder.php000064400000002442150731646620015344 0ustar00[0-9\.]+), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?)?\)$/i'; if (preg_match($pattern, $input, $matches) != 1) { throw new DecoderException('Unable to decode input'); } $values = array_map(function ($value) { return match (strpos($value, '%')) { false => intval(trim($value)), default => intval(trim(str_replace('%', '', $value))), }; }, [$matches['h'], $matches['s'], $matches['v']]); return new Color(...$values); } } src/Colors/Profile.php000064400000000272150731646620010720 0ustar00normalize(), 6)); } } src/Colors/Rgb/Channels/Blue.php000064400000000134150731646620012451 0ustar00channels = [ new Red($r), new Green($g), new Blue($b), new Alpha($a), ]; } public function colorspace(): ColorspaceInterface { return new Colorspace(); } /** * {@inheritdoc} * * @see ColorInterface::create() */ public static function create(mixed $input): ColorInterface { return (new class ([ Decoders\HexColorDecoder::class, Decoders\StringColorDecoder::class, Decoders\TransparentColorDecoder::class, Decoders\HtmlColornameDecoder::class, ]) extends AbstractInputHandler { })->handle($input); } /** * Return the RGB red color channel * * @return ColorChannelInterface */ public function red(): ColorChannelInterface { return $this->channel(Red::class); } /** * Return the RGB green color channel * * @return ColorChannelInterface */ public function green(): ColorChannelInterface { return $this->channel(Green::class); } /** * Return the RGB blue color channel * * @return ColorChannelInterface */ public function blue(): ColorChannelInterface { return $this->channel(Blue::class); } /** * Return the colors alpha channel * * @return ColorChannelInterface */ public function alpha(): ColorChannelInterface { return $this->channel(Alpha::class); } /** * {@inheritdoc} * * @see ColorInterface::toHex() */ public function toHex(string $prefix = ''): string { if ($this->isFullyOpaque()) { return sprintf( '%s%02x%02x%02x', $prefix, $this->red()->value(), $this->green()->value(), $this->blue()->value() ); } return sprintf( '%s%02x%02x%02x%02x', $prefix, $this->red()->value(), $this->green()->value(), $this->blue()->value(), $this->alpha()->value() ); } public function isFullyOpaque(): bool { return $this->alpha()->value() === 255; } /** * {@inheritdoc} * * @see ColorInterface::toString() */ public function toString(): string { if ($this->isFullyOpaque()) { return sprintf( 'rgb(%d, %d, %d)', $this->red()->value(), $this->green()->value(), $this->blue()->value() ); } return sprintf( 'rgba(%d, %d, %d, %.1F)', $this->red()->value(), $this->green()->value(), $this->blue()->value(), $this->alpha()->normalize(), ); } /** * {@inheritdoc} * * @see ColorInterface::isGreyscale() */ public function isGreyscale(): bool { $values = [$this->red()->value(), $this->green()->value(), $this->blue()->value()]; return count(array_unique($values, SORT_REGULAR)) === 1; } } src/Colors/Rgb/Colorspace.php000064400000007013150731646620012124 0ustar00value(); }, self::$channels, $normalized); return new Color(...$values); } /** * {@inheritdoc} * * @see ColorspaceInterface::importColor() */ public function importColor(ColorInterface $color): ColorInterface { return match (get_class($color)) { CmykColor::class => $this->importCmykColor($color), HsvColor::class => $this->importHsvColor($color), HslColor::class => $this->importHslColor($color), default => $color, }; } protected function importCmykColor(CmykColor $color): ColorInterface { return new Color( (int) (255 * (1 - $color->cyan()->normalize()) * (1 - $color->key()->normalize())), (int) (255 * (1 - $color->magenta()->normalize()) * (1 - $color->key()->normalize())), (int) (255 * (1 - $color->yellow()->normalize()) * (1 - $color->key()->normalize())), ); } protected function importHsvColor(HsvColor $color): ColorInterface { $chroma = $color->value()->normalize() * $color->saturation()->normalize(); $hue = $color->hue()->normalize() * 6; $x = $chroma * (1 - abs(fmod($hue, 2) - 1)); // connect channel values $values = match (true) { $hue < 1 => [$chroma, $x, 0], $hue < 2 => [$x, $chroma, 0], $hue < 3 => [0, $chroma, $x], $hue < 4 => [0, $x, $chroma], $hue < 5 => [$x, 0, $chroma], default => [$chroma, 0, $x], }; // add to each value $values = array_map(function ($value) use ($color, $chroma) { return $value + ($color->value()->normalize() - $chroma); }, $values); array_push($values, 1); // append alpha channel value return $this->colorFromNormalized($values); } protected function importHslColor(HslColor $color): ColorInterface { // normalized values of hsl channels list($h, $s, $l) = array_map(function ($channel) { return $channel->normalize(); }, $color->channels()); $c = (1 - abs(2 * $l - 1)) * $s; $x = $c * (1 - abs(fmod($h * 6, 2) - 1)); $m = $l - $c / 2; $values = match (true) { $h < 1 / 6 => [$c, $x, 0], $h < 2 / 6 => [$x, $c, 0], $h < 3 / 6 => [0, $c, $x], $h < 4 / 6 => [0, $x, $c], $h < 5 / 6 => [$x, 0, $c], default => [$c, 0, $x], }; $values = array_map(function ($value) use ($m) { return $value + $m; }, $values); array_push($values, 1); // append alpha channel value return $this->colorFromNormalized($values); } } src/Colors/Rgb/Decoders/HexColorDecoder.php000064400000003136150731646620014575 0ustar00[a-f\d]{3}(?:[a-f\d]?|(?:[a-f\d]{3}(?:[a-f\d]{2})?)?)\b)$/i'; if (preg_match($pattern, $input, $matches) != 1) { throw new DecoderException('Unable to decode input'); } $values = str_split($matches['hex']); $values = match (strlen($matches['hex'])) { 3, 4 => str_split($matches['hex']), 6, 8 => str_split($matches['hex'], 2), default => throw new DecoderException('Unable to decode input'), }; $values = array_map(function ($value) { return match (strlen($value)) { 1 => hexdec($value . $value), 2 => hexdec($value), default => throw new DecoderException('Unable to decode input'), }; }, $values); return new Color(...$values); } } src/Colors/Rgb/Decoders/HtmlColornameDecoder.php000064400000014050150731646620015613 0ustar00 '#ffa07a', 'salmon' => '#fa8072', 'darksalmon' => '#e9967a', 'lightcoral' => '#f08080', 'indianred' => '#cd5c5c', 'crimson' => '#dc143c', 'firebrick' => '#b22222', 'red' => '#ff0000', 'darkred' => '#8b0000', 'coral' => '#ff7f50', 'tomato' => '#ff6347', 'orangered' => '#ff4500', 'gold' => '#ffd700', 'orange' => '#ffa500', 'darkorange' => '#ff8c00', 'lightyellow' => '#ffffe0', 'lemonchiffon' => '#fffacd', 'lightgoldenrodyellow' => '#fafad2', 'papayawhip' => '#ffefd5', 'moccasin' => '#ffe4b5', 'peachpuff' => '#ffdab9', 'palegoldenrod' => '#eee8aa', 'khaki' => '#f0e68c', 'darkkhaki' => '#bdb76b', 'yellow' => '#ffff00', 'lawngreen' => '#7cfc00', 'chartreuse' => '#7fff00', 'limegreen' => '#32cd32', 'lime' => '#00ff00', 'forestgreen' => '#228b22', 'green' => '#008000', 'darkgreen' => '#006400', 'greenyellow' => '#adff2f', 'yellowgreen' => '#9acd32', 'springgreen' => '#00ff7f', 'mediumspringgreen' => '#00fa9a', 'lightgreen' => '#90ee90', 'palegreen' => '#98fb98', 'darkseagreen' => '#8fbc8f', 'mediumseagre' => 'en #3cb371', 'seagreen' => '#2e8b57', 'olive' => '#808000', 'darkolivegreen' => '#556b2f', 'olivedrab' => '#6b8e23', 'lightcyan' => '#e0ffff', 'cyan' => '#00ffff', 'aqua' => '#00ffff', 'aquamarine' => '#7fffd4', 'mediumaquamarine' => '#66cdaa', 'paleturquoise' => '#afeeee', 'turquoise' => '#40e0d0', 'mediumturquoise' => '#48d1cc', 'darkturquoise' => '#00ced1', 'lightseagreen' => '#20b2aa', 'cadetblue' => '#5f9ea0', 'darkcyan' => '#008b8b', 'teal' => '#008080', 'powderblue' => '#b0e0e6', 'lightblue' => '#add8e6', 'lightskyblue' => '#87cefa', 'skyblue' => '#87ceeb', 'deepskyblue' => '#00bfff', 'lightsteelblue' => '#b0c4de', 'dodgerblue' => '#1e90ff', 'cornflowerblue' => '#6495ed', 'steelblue' => '#4682b4', 'royalblue' => '#4169e1', 'blue' => '#0000ff', 'mediumblue' => '#0000cd', 'darkblue' => '#00008b', 'navy' => '#000080', 'midnightblue' => '#191970', 'mediumslateblue' => '#7b68ee', 'slateblue' => '#6a5acd', 'darkslateblue' => '#483d8b', 'lavender' => '#e6e6fa', 'thistle' => '#d8bfd8', 'plum' => '#dda0dd', 'violet' => '#ee82ee', 'orchid' => '#da70d6', 'fuchsia' => '#ff00ff', 'magenta' => '#ff00ff', 'mediumorchid' => '#ba55d3', 'mediumpurple' => '#9370db', 'blueviolet' => '#8a2be2', 'darkviolet' => '#9400d3', 'darkorchid' => '#9932cc', 'darkmagenta' => '#8b008b', 'purple' => '#800080', 'indigo' => '#4b0082', 'pink' => '#ffc0cb', 'lightpink' => '#ffb6c1', 'hotpink' => '#ff69b4', 'deeppink' => '#ff1493', 'palevioletred' => '#db7093', 'mediumvioletred' => '#c71585', 'white' => '#ffffff', 'snow' => '#fffafa', 'honeydew' => '#f0fff0', 'mintcream' => '#f5fffa', 'azure' => '#f0ffff', 'aliceblue' => '#f0f8ff', 'ghostwhite' => '#f8f8ff', 'whitesmoke' => '#f5f5f5', 'seashell' => '#fff5ee', 'beige' => '#f5f5dc', 'oldlace' => '#fdf5e6', 'floralwhite' => '#fffaf0', 'ivory' => '#fffff0', 'antiquewhite' => '#faebd7', 'linen' => '#faf0e6', 'lavenderblush' => '#fff0f5', 'mistyrose' => '#ffe4e1', 'gainsboro' => '#dcdcdc', 'lightgray' => '#d3d3d3', 'silver' => '#c0c0c0', 'darkgray' => '#a9a9a9', 'gray' => '#808080', 'dimgray' => '#696969', 'lightslategray' => '#778899', 'slategray' => '#708090', 'darkslategray' => '#2f4f4f', 'black' => '#000000', 'cornsilk' => '#fff8dc', 'blanchedalmond' => '#ffebcd', 'bisque' => '#ffe4c4', 'navajowhite' => '#ffdead', 'wheat' => '#f5deb3', 'burlywood' => '#deb887', 'tan' => '#d2b48c', 'rosybrown' => '#bc8f8f', 'sandybrown' => '#f4a460', 'goldenrod' => '#daa520', 'peru' => '#cd853f', 'chocolate' => '#d2691e', 'saddlebrown' => '#8b4513', 'sienna' => '#a0522d', 'brown' => '#a52a2a', 'maroon' => '#800000', ]; /** * Decode html color names * * @param mixed $input * @return ImageInterface|ColorInterface */ public function decode($input): ImageInterface|ColorInterface { if (! is_string($input)) { throw new DecoderException('Unable to decode input'); } if (!array_key_exists(strtolower($input), static::$names)) { throw new DecoderException('Unable to decode input'); } return parent::decode(static::$names[strtolower($input)]); } } src/Colors/Rgb/Decoders/StringColorDecoder.php000064400000003326150731646620015320 0ustar00[0-9\.]+%?), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?)(?:, ?(?P(?:1)|(?:1\.0*)|(?:0)|(?:0?\.\d+%?)|(?:\d{1,3}%)))?\)$/i'; if (preg_match($pattern, $input, $matches) != 1) { throw new DecoderException('Unable to decode input'); } // rgb values $values = array_map(function ($value) { return match (strpos($value, '%')) { false => intval(trim($value)), default => intval(round(floatval(trim(str_replace('%', '', $value))) / 100 * 255)), }; }, [$matches['r'], $matches['g'], $matches['b']]); // alpha value if (array_key_exists('a', $matches)) { $values[] = match (true) { strpos($matches['a'], '%') => round(intval(trim(str_replace('%', '', $matches['a']))) / 2.55), default => intval(round(floatval(trim($matches['a'])) * 255)), }; } return new Color(...$values); } } src/Colors/Rgb/Decoders/TransparentColorDecoder.php000064400000001167150731646620016354 0ustar00decode($input); } catch (DecoderException $e) { if (!$this->hasSuccessor()) { throw new DecoderException($e->getMessage()); } return $this->successor->handle($input); } return $decoded; } /** * Determine if current decoder has a successor * * @return bool */ protected function hasSuccessor(): bool { return $this->successor !== null; } /** * Determine if the given input is GIF data format * * @param string $input * @return bool */ protected function isGifFormat(string $input): bool { return preg_match( "/^47494638(37|39)61/", strtoupper(substr(bin2hex($input), 0, 32)) ); } /** * Extract and return EXIF data from given image data string * * @param string $image_data * @return CollectionInterface */ protected function extractExifData(string $image_data): CollectionInterface { if (!function_exists('exif_read_data')) { return new Collection(); } try { $pointer = $this->buildFilePointer($image_data); $data = @exif_read_data($pointer, null, true); fclose($pointer); } catch (Exception $e) { $data = []; } return new Collection(is_array($data) ? $data : []); } /** * Determine if given input is base64 encoded data * * @param mixed $input * @return bool */ protected function isValidBase64(mixed $input): bool { if (!is_string($input)) { return false; } return base64_encode(base64_decode($input)) === str_replace(["\n", "\r"], '', $input); } /** * Parse data uri * * @param mixed $value * @return object */ protected function parseDataUri($value): object { $pattern = "/^data:(?P\w+\/[-+.\w]+)?" . "(?P(;[-\w]+=[-\w]+)*)(?P;base64)?,(?P.*)/"; $result = preg_match($pattern, $value, $matches); return new class($matches, $result) { private $matches; private $result; public function __construct($matches, $result) { $this->matches = $matches; $this->result = $result; } public function isValid(): bool { return (bool) $this->result; } public function mediaType(): ?string { if (isset($this->matches['mediatype']) && !empty($this->matches['mediatype'])) { return $this->matches['mediatype']; } return null; } public function hasMediaType(): bool { return !empty($this->mediaType()); } public function parameters(): array { if (isset($this->matches['parameters']) && !empty($this->matches['parameters'])) { return explode(';', trim($this->matches['parameters'], ';')); } return []; } public function isBase64Encoded(): bool { if (isset($this->matches['base64']) && $this->matches['base64'] === ';base64') { return true; } return false; } public function data(): ?string { if (isset($this->matches['data']) && !empty($this->matches['data'])) { return $this->matches['data']; } return null; } }; } } src/Drivers/AbstractDrawModifier.php000064400000002072150731646620013535 0ustar00drawable->position(); } public function backgroundColor(): ColorInterface { try { $color = $this->driver()->handleInput($this->drawable->backgroundColor()); } catch (DecoderException $e) { return $this->driver()->handleInput('transparent'); } return $color; } public function borderColor(): ColorInterface { try { $color = $this->driver()->handleInput($this->drawable->borderColor()); } catch (DecoderException $e) { return $this->driver()->handleInput('transparent'); } return $color; } } src/Drivers/AbstractDriver.php000064400000003244150731646620012416 0ustar00isExternal($input)) { return $input; } $driver_namespace = (new ReflectionClass($this))->getNamespaceName(); $class_path = substr(get_class($input), strlen("Intervention\\Image\\")); $specialized = $driver_namespace . "\\" . $class_path; if (! class_exists($specialized)) { throw new NotSupportedException( "Class '" . $class_path . "' is not supported by " . $this->id() . " driver." ); } return new $specialized($input, $this); } /** * Determine if given object is external custom modifier, analyzer or encoder * * @param object $input * @return bool */ private function isExternal(object $input): bool { if ($input instanceof AbstractModifier) { return false; } if ($input instanceof AbstractAnalyzer) { return false; } if ($input instanceof AbstractEncoder) { return false; } return true; } } src/Drivers/AbstractInputHandler.php000064400000002633150731646620013561 0ustar00decoders = count($decoders) ? $decoders : $this->decoders; } /** * {@inheritdoc} * * @see InputHandlerInterface::handle() */ public function handle($input): ImageInterface|ColorInterface { return $this->chain()->handle($input); } /** * Stack the decoder array into a nested decoder object * * @return AbstractDecoder */ protected function chain(): AbstractDecoder { if (count($this->decoders) == 0) { throw new DecoderException('No decoders found in ' . get_class($this)); } // get instance of last decoder in stack list($classname) = array_slice(array_reverse($this->decoders), 0, 1); $chain = new $classname(); // build decoder chain foreach (array_slice(array_reverse($this->decoders), 1) as $classname) { $chain = new $classname($chain); } return $chain; } } src/Drivers/AbstractTextModifier.php000064400000005402150731646620013564 0ustar00fontSizeInPixels() * $this->font->lineHeight())); } public function capHeight(): int { return $this->boxSize('T')->height(); } public function fontSizeInPixels(): int { return $this->boxSize('Hy')->height(); } /** * Build TextBlock object from text string and align every line * according to text writers font object and position. * * @return TextBlock */ public function alignedTextBlock(Point $position, string $text): TextBlock { $lines = new TextBlock($text); $boundingBox = $this->boundingBox($lines, $position); $pivot = $boundingBox->last(); $leading = $this->leadingInPixels(); $blockWidth = $this->lineWidth($lines->longestLine()); $x = $pivot->x(); $y = $this->font->hasFilename() ? $pivot->y() + $this->capHeight() : $pivot->y(); $x_adjustment = 0; foreach ($lines as $line) { $line_width = $this->lineWidth($line); $x_adjustment = $this->font->alignment() == 'left' ? 0 : $blockWidth - $line_width; $x_adjustment = $this->font->alignment() == 'right' ? intval(round($x_adjustment)) : $x_adjustment; $x_adjustment = $this->font->alignment() == 'center' ? intval(round($x_adjustment / 2)) : $x_adjustment; $position = new Point($x + $x_adjustment, $y); $position->rotate($this->font->angle(), $pivot); $line->setPosition($position); $y += $leading; } return $lines; } public function boundingBox(TextBlock $block, Point $pivot = null): Polygon { $pivot = $pivot ? $pivot : new Point(); // bounding box $box = (new Rectangle( $this->lineWidth($block->longestLine()), $this->leadingInPixels() * ($block->count() - 1) + $this->capHeight() )); // set pivot $box->setPivot($pivot); // align $box->align($this->font->alignment()); $box->valign($this->font->valignment()); $box->rotate($this->font->angle()); return $box; } private function lineWidth(Line $line): int { return $this->boxSize((string) $line)->width(); } } src/Drivers/DriverSpecializedAnalyzer.php000064400000001252150731646620014612 0ustar00driver; } /** * Magic method to read attributes of underlying analyzer * * @param string $name * @return mixed */ public function __get(string $name): mixed { return $this->analyzer->$name; } } src/Drivers/DriverSpecializedEncoder.php000064400000001767150731646620014417 0ustar00driver; } /** * Magic method to read attributes of underlying encoder * * @param string $name * @return mixed */ public function __get(string $name): mixed { return $this->encoder->$name; } /** * Get return value of callback through output buffer * * @param callable $callback * @return string */ protected function getBuffered(callable $callback): string { ob_start(); $callback(); $buffer = ob_get_contents(); ob_end_clean(); return $buffer; } } src/Drivers/DriverSpecializedModifier.php000064400000001720150731646620014563 0ustar00driver; } /** * Magic method to read attributes of underlying modifier * * @param string $name * @return mixed */ public function __get(string $name): mixed { return $this->modifier->$name; } /** * Magic method to call methods of underlying modifier * * @param string $name * @param array $arguments * @return mixed */ public function __call(string $name, array $arguments): mixed { return $this->modifier->$name(...$arguments); } } src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php000064400000000574150731646620015604 0ustar00core()->native()); } } src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php000064400000002122150731646620015561 0ustar00colorAt( $image->colorspace(), $image->core()->frame($this->frame_key)->native() ); } protected function colorAt(ColorspaceInterface $colorspace, GdImage $gd): ColorInterface { $index = @imagecolorat($gd, $this->x, $this->y); if ($index === false) { throw new GeometryException( 'The specified position is not in the valid image area.' ); } return $this->driver()->colorProcessor($colorspace)->nativeToColor($index); } } src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php000064400000001102150731646620015741 0ustar00colorspace(); foreach ($image as $frame) { $colors->push( parent::colorAt($colorspace, $frame->native()) ); } return $colors; } } src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php000064400000000635150731646620015653 0ustar00core()->native())); } } src/Drivers/Gd/Analyzers/WidthAnalyzer.php000064400000000532150731646620014563 0ustar00core()->native()); } } src/Drivers/Gd/ColorProcessor.php000064400000005067150731646620013014 0ustar00convertTo($this->colorspace); // gd only supports rgb so the channels can be accessed directly $r = $color->channel(Red::class)->value(); $g = $color->channel(Green::class)->value(); $b = $color->channel(Blue::class)->value(); $a = $color->channel(Alpha::class)->value(); // convert alpha value to gd alpha // ([opaque]255-0[transparent]) to ([opaque]0-127[transparent]) $a = (int) $this->convertRange($a, 0, 255, 127, 0); return ($a << 24) + ($r << 16) + ($g << 8) + $b; } public function nativeToColor(mixed $value): ColorInterface { if (! is_int($value)) { throw new ColorException("GD driver can only decode colors in integer format."); } $a = ($value >> 24) & 0xFF; $r = ($value >> 16) & 0xFF; $g = ($value >> 8) & 0xFF; $b = $value & 0xFF; // convert gd apha integer to intervention alpha integer // ([opaque]0-127[transparent]) to ([opaque]255-0[transparent]) $a = (int) static::convertRange($a, 127, 0, 0, 255); return new Color($r, $g, $b, $a); } /** * Convert input in range (min) to (max) to the corresponding value * in target range (targetMin) to (targetMax). * * @param float|int $input * @param float|int $min * @param float|int $max * @param float|int $targetMin * @param float|int $targetMax * @return float|int */ protected function convertRange( float|int $input, float|int $min, float|int $max, float|int $targetMin, float|int $targetMax ): float|int { return ceil(((($input - $min) * ($targetMax - $targetMin)) / ($max - $min)) + $targetMin); } } src/Drivers/Gd/Core.php000064400000002363150731646620010722 0ustar00push($frame); return $this; } public function native(): mixed { return $this->first()->native(); } public function setNative(mixed $native): self { $this->empty()->push(new Frame($native)); return $this; } public function frame(int $position): FrameInterface { return $this->getAtPosition($position); } public function loops(): int { return $this->loops; } public function setLoops(int $loops): self { $this->loops = $loops; return $this; } public function first(): FrameInterface { return parent::first(); } public function last(): FrameInterface { return parent::last(); } public function __clone(): void { foreach ($this->items as $key => $frame) { $this->items[$key] = clone $frame; } } } src/Drivers/Gd/Decoders/Base64ImageDecoder.php000064400000001132150731646620015030 0ustar00isValidBase64($input)) { throw new DecoderException('Unable to decode input'); } return parent::decode(base64_decode($input)); } } src/Drivers/Gd/Decoders/BinaryImageDecoder.php000064400000005543150731646620015242 0ustar00isGifFormat($input)) { return $this->decodeGif($input); // decode (animated) gif } return $this->decodeString($input); } private function decodeString(string $input): ImageInterface { $gd = @imagecreatefromstring($input); if ($gd === false) { throw new DecoderException('Unable to decode input'); } if (!imageistruecolor($gd)) { imagepalettetotruecolor($gd); } imagesavealpha($gd, true); // build image instance $image = new Image( new Driver(), new Core([ new Frame($gd) ]), $this->extractExifData($input) ); if ($info = getimagesizefromstring($input)) { $image->setOrigin( new Origin($info['mime']) ); } // fix image orientation return match ($image->exif('IFD0.Orientation')) { 2 => $image->flip(), 3 => $image->rotate(180), 4 => $image->rotate(180)->flip(), 5 => $image->rotate(270)->flip(), 6 => $image->rotate(270), 7 => $image->rotate(90)->flip(), 8 => $image->rotate(90), default => $image }; } private function decodeGif(string $input): ImageInterface { $gif = GifDecoder::decode($input); if (!$gif->isAnimated()) { return $this->decodeString($input); } $splitter = GifSplitter::create($gif)->split(); $delays = $splitter->getDelays(); // build core $core = new Core(); $core->setLoops($gif->getMainApplicationExtension()?->getLoops()); foreach ($splitter->coalesceToResources() as $key => $data) { $core->push( (new Frame($data))->setDelay($delays[$key] / 100) ); } $image = new Image(new Driver(), $core); return $image->setOrigin( new Origin('image/gif') ); } } src/Drivers/Gd/Decoders/ColorObjectDecoder.php000064400000001157150731646620015255 0ustar00parseDataUri($input); if (! $uri->isValid()) { throw new DecoderException('Unable to decode input'); } if ($uri->isBase64Encoded()) { return parent::decode(base64_decode($uri->data())); } return parent::decode(urldecode($uri->data())); } } src/Drivers/Gd/Decoders/FilePathImageDecoder.php000064400000001613150731646620015504 0ustar00getRealPath()); } } src/Drivers/Gd/Driver.php000064400000005120150731646620011257 0ustar00core->add( $this->driver->handleInput($source)->core()->first()->setDelay($delay) ); return $this; } public function __invoke(): ImageInterface { return new Image( $this->driver, $this->core ); } }; $init($animation); return call_user_func($animation); } /** * {@inheritdoc} * * @see DriverInterface::handleInput() */ public function handleInput(mixed $input): ImageInterface|ColorInterface { return (new InputHandler())->handle($input); } /** * {@inheritdoc} * * @see DriverInterface::colorProcessor() */ public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface { return new ColorProcessor($colorspace); } } src/Drivers/Gd/Encoders/AvifEncoder.php000064400000001110150731646620013746 0ustar00core()->native(); $data = $this->getBuffered(function () use ($gd) { imageavif($gd, null, $this->quality); }); return new EncodedImage($data, 'image/avif'); } } src/Drivers/Gd/Encoders/BmpEncoder.php000064400000001012150731646620013600 0ustar00getBuffered(function () use ($image) { imagebmp($image->core()->native(), null, false); }); return new EncodedImage($data, 'image/bmp'); } } src/Drivers/Gd/Encoders/GifEncoder.php000064400000002177150731646620013604 0ustar00isAnimated()) { return $this->encodeAnimated($image); } $gd = $image->core()->native(); $data = $this->getBuffered(function () use ($gd) { imagegif($gd); }); return new EncodedImage($data, 'image/gif'); } protected function encodeAnimated(ImageInterface $image): EncodedImage { $builder = GifBuilder::canvas( $image->width(), $image->height(), $image->loops() ); foreach ($image as $frame) { $builder->addFrame( $this->encode($frame->toImage($image->driver())), $frame->delay() ); } return new EncodedImage($builder->encode(), 'image/gif'); } } src/Drivers/Gd/Encoders/JpegEncoder.php000064400000001110150731646620013746 0ustar00core()->native(); $data = $this->getBuffered(function () use ($gd) { imagejpeg($gd, null, $this->quality); }); return new EncodedImage($data, 'image/jpeg'); } } src/Drivers/Gd/Encoders/PngEncoder.php000064400000001007150731646620013612 0ustar00getBuffered(function () use ($image) { imagepng($image->core()->native(), null, -1); }); return new EncodedImage($data, 'image/png'); } } src/Drivers/Gd/Encoders/WebpEncoder.php000064400000001110150731646620013756 0ustar00core()->native(); $data = $this->getBuffered(function () use ($gd) { imagewebp($gd, null, $this->quality); }); return new EncodedImage($data, 'image/webp'); } } src/Drivers/Gd/Frame.php000064400000006741150731646620011070 0ustar00native = $native; return $this; } public function native(): GdImage { return $this->native; } public function size(): SizeInterface { return new Rectangle(imagesx($this->native), imagesy($this->native)); } public function delay(): float { return $this->delay; } public function setDelay(float $delay): FrameInterface { $this->delay = $delay; return $this; } public function dispose(): int { return $this->dispose; } public function setDispose(int $dispose): FrameInterface { $this->dispose = $dispose; return $this; } public function setOffset(int $left, int $top): FrameInterface { $this->offset_left = $left; $this->offset_top = $top; return $this; } public function offsetLeft(): int { return $this->offset_left; } public function setOffsetLeft(int $offset): FrameInterface { $this->offset_left = $offset; return $this; } public function offsetTop(): int { return $this->offset_top; } public function setOffsetTop(int $offset): FrameInterface { $this->offset_top = $offset; return $this; } /** * This workaround helps cloning GdImages which is currently not possible. * * @return void */ public function __clone(): void { // create new clone image $width = imagesx($this->native); $height = imagesy($this->native); $clone = match (imageistruecolor($this->native)) { true => imagecreatetruecolor($width, $height), default => imagecreate($width, $height), }; // transfer resolution to clone $resolution = imageresolution($this->native); if (is_array($resolution) && array_key_exists(0, $resolution) && array_key_exists(1, $resolution)) { imageresolution($clone, $resolution[0], $resolution[1]); } // transfer transparency to clone $transIndex = imagecolortransparent($this->native); if ($transIndex != -1) { $rgba = imagecolorsforindex($clone, $transIndex); $transColor = imagecolorallocatealpha($clone, $rgba['red'], $rgba['green'], $rgba['blue'], 127); imagefill($clone, 0, 0, $transColor); imagecolortransparent($clone, $transColor); } else { imagealphablending($clone, false); imagesavealpha($clone, true); } // transfer actual image to clone imagecopy($clone, $this->native, 0, 0, 0, 0, $width, $height); $this->native = $clone; } } src/Drivers/Gd/InputHandler.php000064400000003460150731646620012426 0ustar00amount; $i++) { imagefilter($frame->native(), IMG_FILTER_GAUSSIAN_BLUR); } } return $image; } } src/Drivers/Gd/Modifiers/BrightnessModifier.php000064400000000772150731646620015544 0ustar00native(), IMG_FILTER_BRIGHTNESS, intval($this->level * 2.55)); } return $image; } } src/Drivers/Gd/Modifiers/ColorizeModifier.php000064400000001302150731646620015210 0ustar00red * 2.55); $green = round($this->green * 2.55); $blue = round($this->blue * 2.55); foreach ($image as $frame) { imagefilter($frame->native(), IMG_FILTER_COLORIZE, $red, $green, $blue); } return $image; } } src/Drivers/Gd/Modifiers/ColorspaceModifier.php000064400000001315150731646620015520 0ustar00targetColorspace(), RgbColorspace::class)) { throw new NotSupportedException( 'Only RGB colorspace is supported with GD driver.' ); } return $image; } } src/Drivers/Gd/Modifiers/ContainModifier.php000064400000005602150731646620015024 0ustar00getCropSize($image); $resize = $this->getResizeSize($image); $background = $this->driver()->handleInput($this->background); foreach ($image as $frame) { $this->modify($frame, $crop, $resize, $background); } return $image; } protected function modify( FrameInterface $frame, SizeInterface $crop, SizeInterface $resize, ColorInterface $background ): void { // create new gd image $modified = $this->driver()->createImage( $resize->width(), $resize->height() )->modify( new FillModifier($background) )->core()->native(); // retain resolution $this->copyResolution($frame->native(), $modified); // make image area transparent to keep transparency // even if background-color is set $transparent = imagecolorallocatealpha( $modified, $background->channel(Red::class)->value(), $background->channel(Green::class)->value(), $background->channel(Blue::class)->value(), 127, ); imagealphablending($modified, false); // do not blend / just overwrite imagecolortransparent($modified, $transparent); imagefilledrectangle( $modified, $crop->pivot()->x(), $crop->pivot()->y(), $crop->pivot()->x() + $crop->width() - 1, $crop->pivot()->y() + $crop->height() - 1, $transparent ); // copy image from original with blending alpha imagealphablending($modified, true); imagecopyresampled( $modified, $frame->native(), $crop->pivot()->x(), $crop->pivot()->y(), 0, 0, $crop->width(), $crop->height(), $frame->size()->width(), $frame->size()->height() ); // set new content as recource $frame->setNative($modified); } } src/Drivers/Gd/Modifiers/ContrastModifier.php000064400000000756150731646620015233 0ustar00native(), IMG_FILTER_CONTRAST, ($this->level * -1)); } return $image; } } src/Drivers/Gd/Modifiers/CoverDownModifier.php000064400000000550150731646620015334 0ustar00scaleDown($this->width, $this->height); } } src/Drivers/Gd/Modifiers/CoverModifier.php000064400000003744150731646620014514 0ustar00getCropSize($image); $resize = $this->getResizeSize($crop); foreach ($image as $frame) { $this->modifyFrame($frame, $crop, $resize); } return $image; } protected function modifyFrame(FrameInterface $frame, SizeInterface $crop, SizeInterface $resize): void { // create new image $modified = $this->driver()->createImage( $resize->width(), $resize->height() )->core()->native(); // get original image $original = $frame->native(); // retain resolution $this->copyResolution($original, $modified); // preserve transparency $transIndex = imagecolortransparent($original); if ($transIndex != -1) { $rgba = imagecolorsforindex($modified, $transIndex); $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); imagefill($modified, 0, 0, $transColor); imagecolortransparent($modified, $transColor); } // copy content from resource imagecopyresampled( $modified, $original, 0, 0, $crop->pivot()->x(), $crop->pivot()->y(), $resize->width(), $resize->height(), $crop->width(), $crop->height() ); // set new content as resource $frame->setNative($modified); } } src/Drivers/Gd/Modifiers/CropModifier.php000064400000003700150731646620014331 0ustar00crop($image); foreach ($image as $frame) { $this->cropFrame($frame, $crop); } return $image; } protected function cropFrame(FrameInterface $frame, SizeInterface $resizeTo): void { // create new image $modified = $this->driver() ->createImage($resizeTo->width(), $resizeTo->height()) ->core() ->native(); // get original image $original = $frame->native(); // retain resolution $this->copyResolution($original, $modified); // preserve transparency $transIndex = imagecolortransparent($original); if ($transIndex != -1) { $rgba = imagecolorsforindex($modified, $transIndex); $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); imagefill($modified, 0, 0, $transColor); imagecolortransparent($modified, $transColor); } // copy content from resource imagecopyresampled( $modified, $original, 0, 0, $resizeTo->pivot()->x() + $this->offset_x, $resizeTo->pivot()->y() + $this->offset_y, $resizeTo->width(), $resizeTo->height(), $resizeTo->width(), $resizeTo->height(), ); // set new content as recource $frame->setNative($modified); } } src/Drivers/Gd/Modifiers/DrawEllipseModifier.php000064400000004453150731646620015647 0ustar00drawable->hasBorder()) { // slightly smaller ellipse to keep 1px bordered edges clean if ($this->drawable->hasBackgroundColor()) { imagefilledellipse( $frame->native(), $this->position()->x(), $this->position()->y(), $this->drawable->width() - 1, $this->drawable->height() - 1, $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); } // gd's imageellipse ignores imagesetthickness // so i use imagearc with 360 degrees instead. imagesetthickness( $frame->native(), $this->drawable->borderSize(), ); imagearc( $frame->native(), $this->position()->x(), $this->position()->y(), $this->drawable->width(), $this->drawable->height(), 0, 360, $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ) ); } else { imagefilledellipse( $frame->native(), $this->position()->x(), $this->position()->y(), $this->drawable->width(), $this->drawable->height(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); } } return $image; } } src/Drivers/Gd/Modifiers/DrawLineModifier.php000064400000001740150731646620015135 0ustar00native(), true); imageline( $frame->native(), $this->drawable->start()->x(), $this->drawable->start()->y(), $this->drawable->end()->x(), $this->drawable->end()->y(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); } return $image; } } src/Drivers/Gd/Modifiers/DrawPixelModifier.php000064400000001475150731646620015334 0ustar00driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->color) ); foreach ($image as $frame) { imagesetpixel( $frame->native(), $this->position->x(), $this->position->y(), $color ); } return $image; } } src/Drivers/Gd/Modifiers/DrawPolygonModifier.php000064400000002620150731646620015673 0ustar00drawable->hasBackgroundColor()) { imagefilledpolygon( $frame->native(), $this->drawable->toArray(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); } if ($this->drawable->hasBorder()) { imagesetthickness($frame->native(), $this->drawable->borderSize()); imagepolygon( $frame->native(), $this->drawable->toArray(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ) ); } } return $image; } } src/Drivers/Gd/Modifiers/DrawRectangleModifier.php000064400000003551150731646620016154 0ustar00drawable->hasBackgroundColor()) { imagefilledrectangle( $frame->native(), $this->position()->x(), $this->position()->y(), $this->position()->x() + $this->drawable->width(), $this->position()->y() + $this->drawable->height(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); } // draw border if ($this->drawable->hasBorder()) { imagesetthickness($frame->native(), $this->drawable->borderSize()); imagerectangle( $frame->native(), $this->position()->x(), $this->position()->y(), $this->position()->x() + $this->drawable->width(), $this->position()->y() + $this->drawable->height(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ) ); } } return $image; } } src/Drivers/Gd/Modifiers/FillModifier.php000064400000003043150731646620014314 0ustar00color($image); foreach ($image as $frame) { if ($this->hasPosition()) { $this->floodFillWithColor($frame, $color); } else { $this->fillAllWithColor($frame, $color); } } return $image; } private function color(ImageInterface $image): int { return $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->color) ); } private function floodFillWithColor(Frame $frame, int $color): void { imagefill( $frame->native(), $this->position->x(), $this->position->y(), $color ); } private function fillAllWithColor(Frame $frame, int $color): void { imagealphablending($frame->native(), true); imagefilledrectangle( $frame->native(), 0, 0, $frame->size()->width() - 1, $frame->size()->height() - 1, $color ); } } src/Drivers/Gd/Modifiers/FlipModifier.php000064400000000661150731646620014323 0ustar00native(), IMG_FLIP_VERTICAL); } return $image; } } src/Drivers/Gd/Modifiers/FlopModifier.php000064400000000663150731646620014333 0ustar00native(), IMG_FLIP_HORIZONTAL); } return $image; } } src/Drivers/Gd/Modifiers/GammaModifier.php000064400000000732150731646620014452 0ustar00native(), 1, $this->gamma); } return $image; } } src/Drivers/Gd/Modifiers/GreyscaleModifier.php000064400000000673150731646620015352 0ustar00native(), IMG_FILTER_GRAYSCALE); } return $image; } } src/Drivers/Gd/Modifiers/InvertModifier.php000064400000000665150731646620014704 0ustar00native(), IMG_FILTER_NEGATE); } return $image; } } src/Drivers/Gd/Modifiers/PadModifier.php000064400000000151150731646620014127 0ustar00native(), IMG_FILTER_PIXELATE, $this->size, true); } return $image; } } src/Drivers/Gd/Modifiers/PlaceModifier.php000064400000002033150731646620014450 0ustar00driver()->handleInput($this->element); $position = $this->getPosition($image, $watermark); foreach ($image as $frame) { imagealphablending($frame->native(), true); imagecopy( $frame->native(), $watermark->core()->native(), $position->x(), $position->y(), 0, 0, $watermark->width(), $watermark->height() ); } return $image; } } src/Drivers/Gd/Modifiers/ProfileModifier.php000064400000001076150731646620015032 0ustar00limit <= 0) { throw new InputException('Quantization limit must be greater than 0.'); } // no color reduction if the limit is higher than the colors in the img $colorCount = imagecolorstotal($image->core()->native()); if ($colorCount > 0 && $this->limit > $colorCount) { return $image; } $width = $image->width(); $height = $image->height(); $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->background) ); foreach ($image as $frame) { // create new image for color quantization $reduced = imagecreatetruecolor($width, $height); // retain resolution $this->copyResolution($frame->native(), $reduced); // fill with background imagefill($reduced, 0, 0, $background); // set transparency imagecolortransparent($reduced, $background); // copy original image (colors are limited automatically in the copy process) imagecopy($reduced, $frame->native(), 0, 0, 0, 0, $width, $height); // gd library does not support color quantization directly therefore the // colors are decrease by transforming the image to a palette version imagetruecolortopalette($reduced, true, $this->limit); $frame->setNative($reduced); } return $image; } } src/Drivers/Gd/Modifiers/RemoveAnimationModifier.php000064400000001065150731646620016525 0ustar00core()->setNative( $this->chosenFrame($image, $this->position)->native() ); return $image; } } src/Drivers/Gd/Modifiers/ResizeCanvasModifier.php000064400000005362150731646620016031 0ustar00cropSize($image); $background = $this->driver()->handleInput($this->background); foreach ($image as $frame) { $this->modify($frame, $resize, $background); } return $image; } protected function modify( FrameInterface $frame, SizeInterface $resize, ColorInterface $background, ): void { // create new gd image $modified = $this->driver()->createImage( $resize->width(), $resize->height() )->modify( new FillModifier($background) )->core()->native(); // retain resolution $this->copyResolution($frame->native(), $modified); // make image area transparent to keep transparency // even if background-color is set $transparent = imagecolorallocatealpha( $modified, $background->channel(Red::class)->value(), $background->channel(Green::class)->value(), $background->channel(Blue::class)->value(), 127, ); imagealphablending($modified, false); // do not blend / just overwrite imagecolortransparent($modified, $transparent); imagefilledrectangle( $modified, $resize->pivot()->x() * -1, $resize->pivot()->y() * -1, $resize->pivot()->x() * -1 + $frame->size()->width() - 1, $resize->pivot()->y() * -1 + $frame->size()->height() - 1, $transparent ); // copy image from original with blending alpha imagealphablending($modified, true); imagecopyresampled( $modified, $frame->native(), $resize->pivot()->x() * -1, $resize->pivot()->y() * -1, 0, 0, $frame->size()->width(), $frame->size()->height(), $frame->size()->width(), $frame->size()->height() ); // set new content as recource $frame->setNative($modified); } } src/Drivers/Gd/Modifiers/ResizeCanvasRelativeModifier.php000064400000000503150731646620017515 0ustar00size()->resizeDown($this->width, $this->height); } } src/Drivers/Gd/Modifiers/ResizeModifier.php000064400000004170150731646620014671 0ustar00getAdjustedSize($image); foreach ($image as $frame) { $this->resizeFrame($frame, $resizeTo); } return $image; } private function resizeFrame(FrameInterface $frame, SizeInterface $resizeTo): void { // create new image $modified = imagecreatetruecolor( $resizeTo->width(), $resizeTo->height() ); // get current GDImage $current = $frame->native(); // retain resolution $this->copyResolution($current, $modified); // preserve transparency $transIndex = imagecolortransparent($current); if ($transIndex != -1) { $rgba = imagecolorsforindex($modified, $transIndex); $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); imagefill($modified, 0, 0, $transColor); imagecolortransparent($modified, $transColor); } else { imagealphablending($modified, false); imagesavealpha($modified, true); } // copy content from resource imagecopyresampled( $modified, $current, $resizeTo->pivot()->x(), $resizeTo->pivot()->y(), 0, 0, $resizeTo->width(), $resizeTo->height(), $frame->size()->width(), $frame->size()->height() ); // set new content as recource $frame->setNative($modified); } protected function getAdjustedSize(ImageInterface $image): SizeInterface { return $image->size()->resize($this->width, $this->height); } } src/Drivers/Gd/Modifiers/ResolutionModifier.php000064400000001057150731646620015574 0ustar00x)); $y = intval(round($this->y)); foreach ($image as $frame) { imageresolution($frame->native(), $x, $y); } return $image; } } src/Drivers/Gd/Modifiers/RotateModifier.php000064400000006465150731646620014677 0ustar00driver()->handleInput($this->background); foreach ($image as $frame) { $this->modifyFrame($frame, $background); } return $image; } /** * Apply rotation modification on given frame, given background * color is used for newly create image areas * * @param FrameInterface $frame * @param ColorInterface $background * @return void */ protected function modifyFrame(FrameInterface $frame, ColorInterface $background): void { // get transparent color from frame core $transparent = match ($transparent = imagecolortransparent($frame->native())) { -1 => imagecolorallocatealpha( $frame->native(), $background->channel(Red::class)->value(), $background->channel(Green::class)->value(), $background->channel(Blue::class)->value(), 127 ), default => $transparent, }; // rotate original image against transparent background $rotated = imagerotate( $frame->native(), $this->rotationAngle(), $transparent ); // create size from original after rotation $container = (new Rectangle( imagesx($rotated), imagesy($rotated), ))->movePivot('center'); // create size from original and rotate points $cutout = (new Rectangle( imagesx($frame->native()), imagesy($frame->native()), $container->pivot() ))->align('center') ->valign('center') ->rotate($this->rotationAngle() * -1); // create new gd image $modified = $this->driver()->createImage( imagesx($rotated), imagesy($rotated) )->modify(new FillModifier($background)) ->core() ->native(); // retain resolution $this->copyResolution($frame->native(), $modified); // draw the cutout on new gd image to have a transparent // background where the rotated image will be placed imagealphablending($modified, false); imagefilledpolygon( $modified, $cutout->toArray(), imagecolortransparent($modified) ); // place rotated image on new gd image imagealphablending($modified, true); imagecopy( $modified, $rotated, 0, 0, 0, 0, imagesx($rotated), imagesy($rotated) ); $frame->setNative($modified); } } src/Drivers/Gd/Modifiers/ScaleDownModifier.php000064400000000653150731646620015311 0ustar00size()->scaleDown($this->width, $this->height); } } src/Drivers/Gd/Modifiers/ScaleModifier.php000064400000000643150731646620014460 0ustar00size()->scale($this->width, $this->height); } } src/Drivers/Gd/Modifiers/SharpenModifier.php000064400000001501150731646620015023 0ustar00matrix(); foreach ($image as $frame) { imageconvolution($frame->native(), $matrix, 1, 0); } return $image; } private function matrix(): array { $min = $this->amount >= 10 ? $this->amount * -0.01 : 0; $max = $this->amount * -0.025; $abs = ((4 * $min + 4 * $max) * -1) + 1; return [ [$min, $max, $min], [$max, $abs, $max], [$min, $max, $min] ]; } } src/Drivers/Gd/Modifiers/TextModifier.php000064400000007032150731646620014354 0ustar00alignedTextBlock($this->position, $this->text); $color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->font->color()) ); foreach ($image as $frame) { if ($this->font->hasFilename()) { foreach ($lines as $line) { imagettftext( $frame->native(), $this->adjustedFontSize(), $this->font->angle() * -1, $line->position()->x(), $line->position()->y(), $color, $this->font->filename(), $line ); } } else { foreach ($lines as $line) { imagestring( $frame->native(), $this->getGdFont(), $line->position()->x(), $line->position()->y(), $line, $color ); } } } return $image; } /** * Calculate size of bounding box of given text * * @return Polygon */ protected function boxSize(string $text): Polygon { if (!$this->font->hasFilename()) { // calculate box size from gd font $box = new Rectangle(0, 0); $chars = mb_strlen($text); if ($chars > 0) { $box->setWidth($chars * $this->getGdFontWidth()); $box->setHeight($this->getGdFontHeight()); } return $box; } // calculate box size from font file with angle 0 $box = imageftbbox( $this->adjustedFontSize(), 0, $this->font->filename(), $text ); // build polygon from points $polygon = new Polygon(); $polygon->addPoint(new Point($box[6], $box[7])); $polygon->addPoint(new Point($box[4], $box[5])); $polygon->addPoint(new Point($box[2], $box[3])); $polygon->addPoint(new Point($box[0], $box[1])); return $polygon; } private function adjustedFontSize(): float { return floatval(ceil($this->font->size() * .75)); } private function getGdFont(): int { if (is_numeric($this->font->filename())) { return intval($this->font->filename()); } return 1; } private function getGdFontWidth(): int { return $this->getGdFont() + 4; } private function getGdFontHeight(): int { switch ($this->getGdFont()) { case 2: return 14; case 3: return 14; case 4: return 16; case 5: return 16; default: case 1: return 8; } } } src/Drivers/Gd/SpecializedModifier.php000064400000001011150731646620013732 0ustar00core()->native()->getImageColorspace()) { Imagick::COLORSPACE_CMYK => new CmykColorspace(), default => new RgbColorspace(), }; } } src/Drivers/Imagick/Analyzers/HeightAnalyzer.php000064400000000551150731646620015727 0ustar00core()->native()->getImageHeight(); } } src/Drivers/Imagick/Analyzers/PixelColorAnalyzer.php000064400000001643150731646620016602 0ustar00colorAt( $image->colorspace(), $image->core()->frame($this->frame_key)->native() ); } protected function colorAt(ColorspaceInterface $colorspace, Imagick $imagick): ColorInterface { return $this->driver() ->colorProcessor($colorspace) ->nativeToColor( $imagick->getImagePixelColor($this->x, $this->y) ); } } src/Drivers/Imagick/Analyzers/PixelColorsAnalyzer.php000064400000001107150731646620016760 0ustar00colorspace(); foreach ($image as $frame) { $colors->push( parent::colorAt($colorspace, $frame->native()) ); } return $colors; } } src/Drivers/Imagick/Analyzers/ProfileAnalyzer.php000064400000001204150731646620016113 0ustar00core()->native()->getImageProfiles('icc'); if (!array_key_exists('icc', $profiles)) { throw new ColorException('No ICC profile found in image.'); } return new Profile($profiles['icc']); } } src/Drivers/Imagick/Analyzers/ResolutionAnalyzer.php000064400000000647150731646620016670 0ustar00core()->native()->getImageResolution()); } } src/Drivers/Imagick/Analyzers/WidthAnalyzer.php000064400000000547150731646620015603 0ustar00core()->native()->getImageWidth(); } } src/Drivers/Imagick/ColorProcessor.php000064400000002664150731646620014026 0ustar00convertTo($this->colorspace) ); } public function nativeToColor(mixed $native): ColorInterface { return match (get_class($this->colorspace)) { CmykColorspace::class => $this->colorspace->colorFromNormalized([ $native->getColorValue(Imagick::COLOR_CYAN), $native->getColorValue(Imagick::COLOR_MAGENTA), $native->getColorValue(Imagick::COLOR_YELLOW), $native->getColorValue(Imagick::COLOR_BLACK), ]), default => $this->colorspace->colorFromNormalized([ $native->getColorValue(Imagick::COLOR_RED), $native->getColorValue(Imagick::COLOR_GREEN), $native->getColorValue(Imagick::COLOR_BLUE), $native->getColorValue(Imagick::COLOR_ALPHA), ]), }; } } src/Drivers/Imagick/Core.php000064400000005366150731646620011742 0ustar00native(); $imagick->setImageDelay($frame->delay()); $imagick->setImageDispose($frame->dispose()); $size = $frame->size(); $imagick->setImagePage( $size->width(), $size->height(), $frame->offsetLeft(), $frame->offsetTop() ); $this->imagick->addImage($imagick); return $this; } public function count(): int { return $this->imagick->getNumberImages(); } public function current(): mixed { $this->imagick->setIteratorIndex($this->iteratorIndex); return new Frame($this->imagick->current()); } public function next(): void { $this->iteratorIndex = $this->iteratorIndex + 1; } public function key(): mixed { return $this->iteratorIndex; } public function valid(): bool { try { $result = $this->imagick->setIteratorIndex($this->iteratorIndex); } catch (ImagickException $e) { return false; } return $result; } public function rewind(): void { $this->iteratorIndex = 0; } public function native(): mixed { return $this->imagick; } public function setNative(mixed $native): CoreInterface { $this->imagick = $native; return $this; } public function frame(int $position): FrameInterface { foreach ($this->imagick as $core) { if ($core->getIteratorIndex() == $position) { return new Frame($core); } } throw new AnimationException('Frame #' . $position . ' is not be found in the image.'); } public function loops(): int { return $this->imagick->getImageIterations(); } public function setLoops(int $loops): CoreInterface { $this->imagick = $this->imagick->coalesceImages(); $this->imagick->setImageIterations($loops); return $this; } public function first(): FrameInterface { return $this->frame(0); } public function last(): FrameInterface { return $this->frame($this->count()); } public function __clone(): void { $this->imagick = clone $this->imagick; } } src/Drivers/Imagick/Decoders/Base64ImageDecoder.php000064400000001137150731646620016047 0ustar00isValidBase64($input)) { throw new DecoderException('Unable to decode input'); } return parent::decode(base64_decode($input)); } } src/Drivers/Imagick/Decoders/BinaryImageDecoder.php000064400000003520150731646620016245 0ustar00readImageBlob($input); } catch (ImagickException $e) { throw new DecoderException('Unable to decode input'); } $imagick = $imagick->coalesceImages(); // fix image orientation switch ($imagick->getImageOrientation()) { case Imagick::ORIENTATION_BOTTOMRIGHT: $imagick->rotateimage("#000", 180); break; case Imagick::ORIENTATION_RIGHTTOP: $imagick->rotateimage("#000", 90); break; case Imagick::ORIENTATION_LEFTBOTTOM: $imagick->rotateimage("#000", -90); break; } // set new orientation in image $imagick->setImageOrientation(Imagick::ORIENTATION_TOPLEFT); $image = new Image( new Driver(), new Core($imagick), $this->extractExifData($input) ); $image->setOrigin(new Origin( $imagick->getImageMimeType() )); return $image; } } src/Drivers/Imagick/Decoders/ColorObjectDecoder.php000064400000001164150731646620016265 0ustar00parseDataUri($input); if (! $uri->isValid()) { throw new DecoderException('Unable to decode input'); } if ($uri->isBase64Encoded()) { return parent::decode(base64_decode($uri->data())); } return parent::decode(urldecode($uri->data())); } } src/Drivers/Imagick/Decoders/FilePathImageDecoder.php000064400000001537150731646620016523 0ustar00getRealPath()); } } src/Drivers/Imagick/Driver.php000064400000005326150731646620012301 0ustar00newImage($width, $height, $background, 'png'); $imagick->setType(Imagick::IMGTYPE_UNDEFINED); $imagick->setImageType(Imagick::IMGTYPE_UNDEFINED); $imagick->setColorspace(Imagick::COLORSPACE_SRGB); $imagick->setImageResolution(96, 96); return new Image($this, new Core($imagick)); } /** * {@inheritdoc} * * @see DriverInterface::createAnimation() */ public function createAnimation(callable $init): ImageInterface { $imagick = new Imagick(); $imagick->setFormat('gif'); $animation = new class ($this, $imagick) { public function __construct( protected DriverInterface $driver, public Imagick $imagick ) { } public function add($source, float $delay = 1): self { $native = $this->driver->handleInput($source)->core()->native(); $native->setImageDelay($delay * 100); $this->imagick->addImage($native); return $this; } public function __invoke(): ImageInterface { return new Image( $this->driver, new Core($this->imagick) ); } }; $init($animation); return call_user_func($animation); } /** * {@inheritdoc} * * @see DriverInterface::handleInput() */ public function handleInput(mixed $input): ImageInterface|ColorInterface { return (new InputHandler())->handle($input); } /** * {@inheritdoc} * * @see DriverInterface::colorProcessor() */ public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface { return new ColorProcessor($colorspace); } } src/Drivers/Imagick/Encoders/AvifEncoder.php000064400000001563150731646620014774 0ustar00core()->native(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); $imagick->setCompressionQuality($this->quality); $imagick->setImageCompressionQuality($this->quality); return new EncodedImage($imagick->getImagesBlob(), 'image/avif'); } } src/Drivers/Imagick/Encoders/BmpEncoder.php000064400000001326150731646620014622 0ustar00core()->native(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); return new EncodedImage($imagick->getImagesBlob(), 'image/bmp'); } } src/Drivers/Imagick/Encoders/GifEncoder.php000064400000001330150731646620014604 0ustar00core()->native(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); return new EncodedImage($imagick->getImagesBlob(), 'image/gif'); } } src/Drivers/Imagick/Encoders/JpegEncoder.php000064400000001727150731646620014776 0ustar00core()->native(); $imagick->setImageBackgroundColor('white'); $imagick->setBackgroundColor('white'); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); $imagick->setCompressionQuality($this->quality); $imagick->setImageCompressionQuality($this->quality); return new EncodedImage($imagick->getImagesBlob(), 'image/jpeg'); } } src/Drivers/Imagick/Encoders/PngEncoder.php000064400000001327150731646620014631 0ustar00core()->native(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); return new EncodedImage($imagick->getImagesBlob(), 'image/png'); } } src/Drivers/Imagick/Encoders/WebpEncoder.php000064400000001744150731646620015005 0ustar00core()->native(); $imagick->setImageBackgroundColor(new ImagickPixel('transparent')); $imagick = $imagick->mergeImageLayers(Imagick::LAYERMETHOD_MERGE); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); $imagick->setImageCompressionQuality($this->quality); return new EncodedImage($imagick->getImagesBlob(), 'image/webp'); } } src/Drivers/Imagick/Frame.php000064400000006422150731646620012076 0ustar00native())); } /** * {@inheritdoc} * * @see DriverInterface::setNative() */ public function setNative($native): FrameInterface { $this->native = $native; return $this; } /** * {@inheritdoc} * * @see DriverInterface::native() */ public function native(): Imagick { return $this->native; } /** * {@inheritdoc} * * @see DriverInterface::size() */ public function size(): SizeInterface { return new Rectangle( $this->native->getImageWidth(), $this->native->getImageHeight() ); } /** * {@inheritdoc} * * @see DriverInterface::delay() */ public function delay(): float { return $this->native->getImageDelay() / 100; } /** * {@inheritdoc} * * @see DriverInterface::setDelay() */ public function setDelay(float $delay): FrameInterface { $this->native->setImageDelay(intval(round($delay * 100))); return $this; } /** * {@inheritdoc} * * @see DriverInterface::dispose() */ public function dispose(): int { return $this->native->getImageDispose(); } /** * {@inheritdoc} * * @see DriverInterface::setDispose() */ public function setDispose(int $dispose): FrameInterface { $this->native->setImageDispose($dispose); return $this; } /** * {@inheritdoc} * * @see DriverInterface::setOffset() */ public function setOffset(int $left, int $top): FrameInterface { $this->native->setImagePage( $this->native->getImageWidth(), $this->native->getImageHeight(), $left, $top ); return $this; } /** * {@inheritdoc} * * @see DriverInterface::offsetLeft() */ public function offsetLeft(): int { return $this->native->getImagePage()['x']; } /** * {@inheritdoc} * * @see DriverInterface::setOffsetLeft() */ public function setOffsetLeft(int $offset): FrameInterface { return $this->setOffset($offset, $this->offsetTop()); } /** * {@inheritdoc} * * @see DriverInterface::offsetTop() */ public function offsetTop(): int { return $this->native->getImagePage()['y']; } /** * {@inheritdoc} * * @see DriverInterface::setOffsetTop() */ public function setOffsetTop(int $offset): FrameInterface { return $this->setOffset($this->offsetLeft(), $offset); } } src/Drivers/Imagick/InputHandler.php000064400000003535150731646620013443 0ustar00native()->blurImage(1 * $this->amount, 0.5 * $this->amount); } return $image; } } src/Drivers/Imagick/Modifiers/BrightnessModifier.php000064400000000753150731646620016555 0ustar00native()->modulateImage(100 + $this->level, 100, 100); } return $image; } } src/Drivers/Imagick/Modifiers/ColorizeModifier.php000064400000002207150731646620016227 0ustar00normalizeLevel($this->red); $green = $this->normalizeLevel($this->green); $blue = $this->normalizeLevel($this->blue); foreach ($image as $frame) { $qrange = $frame->native()->getQuantumRange(); $frame->native()->levelImage(0, $red, $qrange['quantumRangeLong'], Imagick::CHANNEL_RED); $frame->native()->levelImage(0, $green, $qrange['quantumRangeLong'], Imagick::CHANNEL_GREEN); $frame->native()->levelImage(0, $blue, $qrange['quantumRangeLong'], Imagick::CHANNEL_BLUE); } return $image; } private function normalizeLevel(int $level): int { return $level > 0 ? intval(round($level / 5)) : intval(round(($level + 100) / 100)); } } src/Drivers/Imagick/Modifiers/ColorspaceModifier.php000064400000002455150731646620016540 0ustar00 Imagick::COLORSPACE_SRGB, CmykColorspace::class => Imagick::COLORSPACE_CMYK, ]; public function apply(ImageInterface $image): ImageInterface { $colorspace = $this->targetColorspace(); $imagick = $image->core()->native(); $imagick->transformImageColorspace( $this->getImagickColorspace($colorspace) ); return $image; } private function getImagickColorspace(ColorspaceInterface $colorspace): int { if (!array_key_exists(get_class($colorspace), self::$mapping)) { throw new NotSupportedException('Given colorspace is not supported.'); } return self::$mapping[get_class($colorspace)]; } } src/Drivers/Imagick/Modifiers/ContainModifier.php000064400000005670150731646620016043 0ustar00getCropSize($image); $resize = $this->getResizeSize($image); $transparent = new ImagickPixel('transparent'); $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->background) ); foreach ($image as $frame) { $frame->native()->scaleImage( $crop->width(), $crop->height(), ); $frame->native()->setBackgroundColor($transparent); $frame->native()->setImageBackgroundColor($transparent); $frame->native()->extentImage( $resize->width(), $resize->height(), $crop->pivot()->x() * -1, $crop->pivot()->y() * -1 ); if ($resize->width() > $crop->width()) { // fill new emerged background $draw = new ImagickDraw(); $draw->setFillColor($background); $delta = abs($crop->pivot()->x()); if ($delta > 0) { $draw->rectangle( 0, 0, $delta - 1, $resize->height() ); } $draw->rectangle( $crop->width() + $delta, 0, $resize->width(), $resize->height() ); $frame->native()->drawImage($draw); } if ($resize->height() > $crop->height()) { // fill new emerged background $draw = new ImagickDraw(); $draw->setFillColor($background); $delta = abs($crop->pivot()->y()); if ($delta > 0) { $draw->rectangle( 0, 0, $resize->width(), $delta - 1 ); } $draw->rectangle( 0, $crop->height() + $delta, $resize->width(), $resize->height() ); $frame->native()->drawImage($draw); } } return $image; } } src/Drivers/Imagick/Modifiers/ContrastModifier.php000064400000001000150731646620016224 0ustar00native()->sigmoidalContrastImage($this->level > 0, abs($this->level / 4), 0); } return $image; } } src/Drivers/Imagick/Modifiers/CoverDownModifier.php000064400000000555150731646620016353 0ustar00scaleDown($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/CoverModifier.php000064400000001714150731646620015521 0ustar00getCropSize($image); $resize = $this->getResizeSize($crop); foreach ($image as $frame) { $frame->native()->extentImage( $crop->width(), $crop->height(), $crop->pivot()->x(), $crop->pivot()->y() ); $frame->native()->scaleImage( $resize->width(), $resize->height() ); } return $image; } } src/Drivers/Imagick/Modifiers/CropModifier.php000064400000001454150731646620015347 0ustar00crop($image); foreach ($image as $frame) { $frame->native()->extentImage( $crop->width(), $crop->height(), $crop->pivot()->x() + $this->offset_x, $crop->pivot()->y() + $this->offset_y ); } return $image; } } src/Drivers/Imagick/Modifiers/DrawEllipseModifier.php000064400000002517150731646620016660 0ustar00driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ); $border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ); foreach ($image as $frame) { $drawing = new ImagickDraw(); $drawing->setFillColor($background_color); if ($this->drawable->hasBorder()) { $drawing->setStrokeWidth($this->drawable->borderSize()); $drawing->setStrokeColor($border_color); } $drawing->ellipse( $this->position()->x(), $this->position()->y(), $this->drawable->width() / 2, $this->drawable->height() / 2, 0, 360 ); $frame->native()->drawImage($drawing); } return $image; } } src/Drivers/Imagick/Modifiers/DrawLineModifier.php000064400000002105150731646620016143 0ustar00setStrokeWidth($this->drawable->width()); $drawing->setStrokeColor( $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); $drawing->line( $this->drawable->start()->x(), $this->drawable->start()->y(), $this->drawable->end()->x(), $this->drawable->end()->y(), ); foreach ($image as $frame) { $frame->native()->drawImage($drawing); } return $image; } } src/Drivers/Imagick/Modifiers/DrawPixelModifier.php000064400000001543150731646620016342 0ustar00driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->color) ); $pixel = new ImagickDraw(); $pixel->setFillColor($color); $pixel->point($this->position->x(), $this->position->y()); foreach ($image as $frame) { $frame->native()->drawImage($pixel); } return $image; } } src/Drivers/Imagick/Modifiers/DrawPolygonModifier.php000064400000003072150731646620016707 0ustar00drawable->hasBackgroundColor()) { $background_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ); $drawing->setFillColor($background_color); } if ($this->drawable->hasBorder()) { $border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ); $drawing->setStrokeColor($border_color); $drawing->setStrokeWidth($this->drawable->borderSize()); } $drawing->polygon($this->points()); foreach ($image as $frame) { $frame->native()->drawImage($drawing); } return $image; } private function points(): array { $points = []; foreach ($this->drawable as $point) { $points[] = ['x' => $point->x(), 'y' => $point->y()]; } return $points; } } src/Drivers/Imagick/Modifiers/DrawRectangleModifier.php000064400000003015150731646620017161 0ustar00driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ); $border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->borderColor() ); $drawing->setFillColor($background_color); if ($this->drawable->hasBorder()) { $drawing->setStrokeColor($border_color); $drawing->setStrokeWidth($this->drawable->borderSize()); } // build rectangle $drawing->rectangle( $this->position()->x(), $this->position()->y(), $this->position()->x() + $this->drawable->width(), $this->position()->y() + $this->drawable->height() ); foreach ($image as $frame) { $frame->native()->drawImage($drawing); } return $image; } } src/Drivers/Imagick/Modifiers/FillModifier.php000064400000003431150731646620015327 0ustar00driver()->handleInput($this->color); $pixel = $this->driver() ->colorProcessor($image->colorspace()) ->colorToNative($color); foreach ($image as $frame) { if ($this->hasPosition()) { $this->floodFillWithColor($frame, $pixel); } else { $this->fillAllWithColor($frame, $pixel); } } return $image; } private function floodFillWithColor(Frame $frame, ImagickPixel $pixel): void { $target = $frame->native()->getImagePixelColor( $this->position->x(), $this->position->y() ); $frame->native()->floodfillPaintImage( $pixel, 100, $target, $this->position->x(), $this->position->y(), false, Imagick::CHANNEL_ALL ); } private function fillAllWithColor(Frame $frame, ImagickPixel $pixel): void { $draw = new ImagickDraw(); $draw->setFillColor($pixel); $draw->rectangle( 0, 0, $frame->native()->getImageWidth(), $frame->native()->getImageHeight() ); $frame->native()->drawImage($draw); } } src/Drivers/Imagick/Modifiers/FlipModifier.php000064400000000645150731646620015337 0ustar00native()->flipImage(); } return $image; } } src/Drivers/Imagick/Modifiers/FlopModifier.php000064400000000645150731646620015345 0ustar00native()->flopImage(); } return $image; } } src/Drivers/Imagick/Modifiers/GammaModifier.php000064400000000725150731646620015466 0ustar00native()->gammaImage($this->gamma); } return $image; } } src/Drivers/Imagick/Modifiers/GreyscaleModifier.php000064400000000671150731646620016362 0ustar00native()->modulateImage(100, 0, 100); } return $image; } } src/Drivers/Imagick/Modifiers/InvertModifier.php000064400000000656150731646620015716 0ustar00native()->negateImage(false); } return $image; } } src/Drivers/Imagick/Modifiers/PadModifier.php000064400000000156150731646620015146 0ustar00pixelateFrame($frame); } return $image; } protected function pixelateFrame(Frame $frame): void { $size = $frame->size(); $frame->native()->scaleImage( round(max(1, ($size->width() / $this->size))), round(max(1, ($size->height() / $this->size))) ); $frame->native()->scaleImage($size->width(), $size->height()); } } src/Drivers/Imagick/Modifiers/PlaceModifier.php000064400000001645150731646620015472 0ustar00driver()->handleInput($this->element); $position = $this->getPosition($image, $watermark); foreach ($image as $frame) { $frame->native()->compositeImage( $watermark->core()->native(), Imagick::COMPOSITE_DEFAULT, $position->x(), $position->y() ); } return $image; } } src/Drivers/Imagick/Modifiers/ProfileModifier.php000064400000001323150731646620016037 0ustar00core()->native(); $result = $imagick->profileImage('icc', (string) $this->profile); if ($result === false) { throw new ColorException('ICC color profile could not be set.'); } return $image; } } src/Drivers/Imagick/Modifiers/ProfileRemovalModifier.php000064400000001150150731646620017363 0ustar00core()->native(); $result = $imagick->profileImage('icc', null); if ($result === false) { throw new ColorException('ICC color profile could not be removed.'); } return $image; } } src/Drivers/Imagick/Modifiers/QuantizeColorsModifier.php000064400000002006150731646620017420 0ustar00limit <= 0) { throw new InputException('Quantization limit must be greater than 0.'); } // no color reduction if the limit is higher than the colors in the img if ($this->limit > $image->core()->native()->getImageColors()) { return $image; } foreach ($image as $frame) { $frame->native()->quantizeImage( $this->limit, $frame->native()->getImageColorspace(), 0, false, false ); } return $image; } } src/Drivers/Imagick/Modifiers/RemoveAnimationModifier.php000064400000001365150731646620017542 0ustar00chosenFrame($image, $this->position); $imagick->addImage($frame->native()->getImage()); // set new imagick to image $image->core()->setNative($imagick); return $image; } } src/Drivers/Imagick/Modifiers/ResizeCanvasModifier.php000064400000004717150731646620017046 0ustar00size(); $resize = $this->cropSize($image); $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->background) ); foreach ($image as $frame) { $frame->native()->extentImage( $resize->width(), $resize->height(), $resize->pivot()->x(), $resize->pivot()->y() ); if ($resize->width() > $size->width()) { // fill new emerged background $draw = new ImagickDraw(); $draw->setFillColor($background); $delta = abs($resize->pivot()->x()); if ($delta > 0) { $draw->rectangle( 0, 0, $delta - 1, $resize->height() ); } $draw->rectangle( $size->width() + $delta, 0, $resize->width(), $resize->height() ); $frame->native()->drawImage($draw); } if ($resize->height() > $size->height()) { // fill new emerged background $draw = new ImagickDraw(); $draw->setFillColor($background); $delta = abs($resize->pivot()->y()); if ($delta > 0) { $draw->rectangle( 0, 0, $resize->width(), $delta - 1 ); } $draw->rectangle( 0, $size->height() + $delta, $resize->width(), $resize->height() ); $frame->native()->drawImage($draw); } } return $image; } } src/Drivers/Imagick/Modifiers/ResizeCanvasRelativeModifier.php000064400000000510150731646620020525 0ustar00size()->resizeDown($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/ResizeModifier.php000064400000001504150731646620015701 0ustar00getAdjustedSize($image); foreach ($image as $frame) { $frame->native()->scaleImage( $resizeTo->width(), $resizeTo->height() ); } return $image; } protected function getAdjustedSize(ImageInterface $image): SizeInterface { return $image->size()->resize($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/ResolutionModifier.php000064400000000750150731646620016605 0ustar00core()->native(); $imagick->setImageResolution($this->x, $this->y); return $image; } } src/Drivers/Imagick/Modifiers/RotateModifier.php000064400000001353150731646620015700 0ustar00driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->background) ); foreach ($image as $frame) { $frame->native()->rotateImage( $background, $this->rotationAngle() * -1 ); } return $image; } } src/Drivers/Imagick/Modifiers/ScaleDownModifier.php000064400000000660150731646620016321 0ustar00size()->scaleDown($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/ScaleModifier.php000064400000000650150731646620015470 0ustar00size()->scale($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/SharpenModifier.php000064400000000755150731646620016047 0ustar00native()->unsharpMaskImage(1, 1, $this->amount / 6.25, 0); } return $image; } } src/Drivers/Imagick/Modifiers/TextModifier.php000064400000004752150731646620015374 0ustar00alignedTextBlock($this->position, $this->text); $color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->font->color()) ); $draw = $this->toImagickDraw($color); foreach ($image as $frame) { foreach ($lines as $line) { $frame->native()->annotateImage( $draw, $line->position()->x(), $line->position()->y(), $this->font->angle(), $line ); } } return $image; } /** * Calculate box size of current font * * @return Polygon */ protected function boxSize(string $text): Polygon { // no text - no box size if (mb_strlen($text) === 0) { return (new Rectangle(0, 0)); } $draw = $this->toImagickDraw(); $draw->setStrokeAntialias(true); $draw->setTextAntialias(true); $dimensions = (new Imagick())->queryFontMetrics($draw, $text); return (new Rectangle( intval(round($dimensions['textWidth'])), intval(round($dimensions['ascender'] + $dimensions['descender'])), )); } private function toImagickDraw(?ImagickPixel $color = null): ImagickDraw { if (!$this->font->hasFilename()) { throw new FontException('No font file specified.'); } $draw = new ImagickDraw(); $draw->setStrokeAntialias(true); $draw->setTextAntialias(true); $draw->setFont($this->font->filename()); $draw->setFontSize($this->font->size()); $draw->setTextAlignment(Imagick::ALIGN_LEFT); if ($color) { $draw->setFillColor($color); } return $draw; } } src/EncodedImage.php000064400000001510150731646620010357 0ustar00mimetype; } /** * Transform encoded image data into an data uri string * * @return string */ public function toDataUri(): string { return sprintf('data:%s;base64,%s', $this->mimetype, base64_encode($this->data)); } } src/Encoders/AbstractEncoder.php000064400000000677150731646620012675 0ustar00encode($this); } } src/Encoders/AutoEncoder.php000064400000001640150731646620012031 0ustar00origin()->mimetype(); return $image->encode( match ($type) { 'image/webp' => new WebpEncoder(), 'image/avif' => new AvifEncoder(), 'image/jpeg' => new JpegEncoder(), 'image/bmp' => new BmpEncoder(), 'image/gif' => new GifEncoder(), 'image/png' => new PngEncoder(), default => throw new EncoderException('No encoder found for media type (' . $type . ').'), } ); } } src/Encoders/AvifEncoder.php000064400000000243150731646620012004 0ustar00data; } /** * Create file pointer from encoded image * * @return resource */ public function toFilePointer() { return $this->buildFilePointer($this->toString()); } /** * Return byte size of encoded image * * @return int */ public function size(): int { return mb_strlen($this->data); } /** * Cast encoded image object to string * * @return string */ public function __toString(): string { return $this->toString(); } } src/Geometry/Circle.php000064400000001426150731646620011055 0ustar00setWidth($diameter); $this->setHeight($diameter); } public function setDiameter(int $diameter): self { $this->setWidth($diameter); $this->setHeight($diameter); return $this; } public function diameter(): int { return $this->diameter; } public function setRadius(int $radius): self { return $this->setDiameter(intval($radius * 2)); } public function radius(): int { return intval($this->diameter / 2); } } src/Geometry/Ellipse.php000064400000002270150731646620011247 0ustar00pivot; } public function pivot(): PointInterface { return $this->pivot; } public function setSize(int $width, int $height): self { return $this->setWidth($width)->setHeight($height); } public function setWidth(int $width): self { $this->width = $width; return $this; } public function setHeight(int $height): self { $this->height = $height; return $this; } public function width(): int { return $this->width; } public function height(): int { return $this->height; } } src/Geometry/Factories/CircleFactory.php000064400000000601150731646620014316 0ustar00ellipse->setSize($radius * 2, $radius * 2); return $this; } public function diameter(int $diameter): self { $this->ellipse->setSize($diameter, $diameter); return $this; } } src/Geometry/Factories/EllipseFactory.php000064400000001702150731646620014515 0ustar00ellipse = is_a($init, Ellipse::class) ? $init : new Ellipse(0, 0, $pivot); if (is_callable($init)) { $init($this); } } public function size(int $width, int $height): self { $this->ellipse->setSize($width, $height); return $this; } public function background(mixed $color): self { $this->ellipse->setBackgroundColor($color); return $this; } public function border(mixed $color, int $size = 1): self { $this->ellipse->setBorder($color, $size); return $this; } public function __invoke(): Ellipse { return $this->ellipse; } } src/Geometry/Factories/LineFactory.php000064400000002661150731646620014014 0ustar00line = is_a($init, Line::class) ? $init : new Line(new Point(), new Point()); if (is_callable($init)) { $init($this); } } public function color(mixed $color): self { $this->line->setBackgroundColor($color); $this->line->setBorderColor($color); return $this; } public function background(mixed $color): self { $this->line->setBackgroundColor($color); $this->line->setBorderColor($color); return $this; } public function border(mixed $color, int $size = 1): self { $this->line->setBackgroundColor($color); $this->line->setBorderColor($color); $this->line->setWidth($size); return $this; } public function width(int $size): self { $this->line->setWidth($size); return $this; } public function from(int $x, int $y): self { $this->line->setStart(new Point($x, $y)); return $this; } public function to(int $x, int $y): self { $this->line->setEnd(new Point($x, $y)); return $this; } public function __invoke(): Line { return $this->line; } } src/Geometry/Factories/PolygonFactory.php000064400000001633150731646620014552 0ustar00polygon = is_a($init, Polygon::class) ? $init : new Polygon([]); if (is_callable($init)) { $init($this); } } public function point(int $x, int $y): self { $this->polygon->addPoint(new Point($x, $y)); return $this; } public function background(mixed $color): self { $this->polygon->setBackgroundColor($color); return $this; } public function border(mixed $color, int $size = 1): self { $this->polygon->setBorder($color, $size); return $this; } public function __invoke(): Polygon { return $this->polygon; } } src/Geometry/Factories/RectangleFactory.php000064400000002330150731646620015022 0ustar00rectangle = is_a($init, Rectangle::class) ? $init : new Rectangle(0, 0, $pivot); if (is_callable($init)) { $init($this); } } public function size(int $width, int $height): self { $this->rectangle->setSize($width, $height); return $this; } public function width(int $width): self { $this->rectangle->setWidth($width); return $this; } public function height(int $height): self { $this->rectangle->setHeight($height); return $this; } public function background(mixed $color): self { $this->rectangle->setBackgroundColor($color); return $this; } public function border(mixed $color, int $size = 1): self { $this->rectangle->setBorder($color, $size); return $this; } public function __invoke(): Rectangle { return $this->rectangle; } } src/Geometry/Line.php000064400000002642150731646620010544 0ustar00start; } public function width(): int { return $this->width; } public function setWidth(int $width): self { $this->width = $width; return $this; } public function start(): Point { return $this->start; } public function end(): Point { return $this->end; } public function setStart(Point $start): self { $this->start = $start; return $this; } public function from(int $x, int $y): self { $this->start()->setX($x); $this->start()->setY($y); return $this; } public function to(int $x, int $y): self { $this->end()->setX($x); $this->end()->setY($y); return $this; } public function setEnd(Point $end): self { $this->end = $end; return $this; } } src/Geometry/Pixel.php000064400000001013150731646620010725 0ustar00background = $background; return $this; } public function backgroundColor(): ColorInterface { return $this->background; } } src/Geometry/Point.php000064400000004145150731646620010746 0ustar00x = $x; return $this; } /** * Get X coordinate * * @return integer */ public function x(): int { return $this->x; } /** * Sets Y coordinate * * @param integer $y */ public function setY(int $y): self { $this->y = $y; return $this; } /** * Get Y coordinate * * @return integer */ public function y(): int { return $this->y; } /** * Move X coordinate * * @param integer $value */ public function moveX(int $value): self { $this->x += $value; return $this; } /** * Move Y coordinate * * @param integer $value */ public function moveY(int $value): self { $this->y += $value; return $this; } public function move(int $x, int $y): self { return $this->moveX($x)->moveY($y); } /** * Sets both X and Y coordinate * * @param integer $x * @param integer $y * @return Point */ public function setPosition(int $x, int $y): self { $this->setX($x); $this->setY($y); return $this; } /** * Rotate point ccw around pivot * * @param float $angle * @param Point $pivot * @return Point */ public function rotate(float $angle, Point $pivot): self { $sin = round(sin(deg2rad($angle)), 6); $cos = round(cos(deg2rad($angle)), 6); return $this->setPosition( intval($cos * ($this->x - $pivot->x) - $sin * ($this->y - $pivot->y) + $pivot->x), intval($sin * ($this->x - $pivot->x) + $cos * ($this->y - $pivot->y) + $pivot->y) ); } } src/Geometry/Polygon.php000064400000022121150731646620011276 0ustar00pivot; } public function getIterator(): Traversable { return new ArrayIterator($this->points); } /** * Return current pivot point * * @return PointInterface */ public function pivot(): PointInterface { return $this->pivot; } /** * Change pivot point to given point * * @param Point $pivot * @return Polygon */ public function setPivot(Point $pivot): self { $this->pivot = $pivot; return $this; } /** * Return first point of polygon * * @return ?Point */ public function first(): ?Point { if ($point = reset($this->points)) { return $point; } return null; } /** * Return last point of polygon * * @return ?Point */ public function last(): ?Point { if ($point = end($this->points)) { return $point; } return null; } /** * Return polygon's point count * * @return int */ public function count(): int { return count($this->points); } /** * Determine if point exists at given offset * * @param mixed $offset * @return bool */ public function offsetExists($offset): bool { return array_key_exists($offset, $this->points); } /** * Return point at given offset * * @param mixed $offset * @return Point */ public function offsetGet($offset): mixed { return $this->points[$offset]; } /** * Set point at given offset * * @param mixed $offset * @param Point $value * @return void */ public function offsetSet($offset, $value): void { $this->points[$offset] = $value; } /** * Unset offset at given offset * * @param mixed $offset * @return void */ public function offsetUnset($offset): void { unset($this->points[$offset]); } /** * Add given point to polygon * * @param Point $point * @return Polygon */ public function addPoint(Point $point): self { $this->points[] = $point; return $this; } /** * Calculate total horizontal span of polygon * * @return int */ public function width(): int { return abs($this->mostLeftPoint()->x() - $this->mostRightPoint()->x()); } /** * Calculate total vertical span of polygon * * @return int */ public function height(): int { return abs($this->mostBottomPoint()->y() - $this->mostTopPoint()->y()); } /** * Return most left point of all points in polygon * * @return Point */ public function mostLeftPoint(): Point { $points = []; foreach ($this->points as $point) { $points[] = $point; } usort($points, function ($a, $b) { if ($a->x() === $b->x()) { return 0; } return ($a->x() < $b->x()) ? -1 : 1; }); return $points[0]; } /** * Return most right point in polygon * * @return Point */ public function mostRightPoint(): Point { $points = []; foreach ($this->points as $point) { $points[] = $point; } usort($points, function ($a, $b) { if ($a->x() === $b->x()) { return 0; } return ($a->x() > $b->x()) ? -1 : 1; }); return $points[0]; } /** * Return most top point in polygon * * @return Point */ public function mostTopPoint(): Point { $points = []; foreach ($this->points as $point) { $points[] = $point; } usort($points, function ($a, $b) { if ($a->y() === $b->y()) { return 0; } return ($a->y() > $b->y()) ? -1 : 1; }); return $points[0]; } /** * Return most bottom point in polygon * * @return Point */ public function mostBottomPoint(): Point { $points = []; foreach ($this->points as $point) { $points[] = $point; } usort($points, function ($a, $b) { if ($a->y() === $b->y()) { return 0; } return ($a->y() < $b->y()) ? -1 : 1; }); return $points[0]; } /** * Return point in absolute center of the polygon * * @return Point */ public function centerPoint(): Point { return new Point( $this->mostRightPoint()->x() - (intval(round($this->width() / 2))), $this->mostTopPoint()->y() - (intval(round($this->height() / 2))) ); } /** * Align all points of polygon horizontally to given position around pivot point * * @param string $position * @return Polygon */ public function align(string $position): self { switch (strtolower($position)) { case 'center': case 'middle': $diff = ($this->centerPoint()->x() - $this->pivot()->x()); break; case 'right': $diff = ($this->mostRightPoint()->x() - $this->pivot()->x()); break; default: case 'left': $diff = ($this->mostLeftPoint()->x() - $this->pivot()->x()); break; } foreach ($this->points as $point) { $point->setX( intval($point->x() - $diff) ); } return $this; } /** * Align all points of polygon vertically to given position around pivot point * * @param string $position * @return Polygon */ public function valign(string $position): self { switch (strtolower($position)) { case 'center': case 'middle': $diff = ($this->centerPoint()->y() - $this->pivot()->y()); break; case 'top': $diff = ($this->mostTopPoint()->y() - $this->pivot()->y()) - $this->height(); break; default: case 'bottom': $diff = ($this->mostBottomPoint()->y() - $this->pivot()->y()) + $this->height(); break; } foreach ($this->points as $point) { $point->setY( intval($point->y() - $diff), ); } return $this; } /** * Rotate points of polygon around pivot point with given angle * * @param float $angle * @return Polygon */ public function rotate(float $angle): self { $sin = sin(deg2rad($angle)); $cos = cos(deg2rad($angle)); foreach ($this->points as $point) { // translate point to pivot $point->setX( intval($point->x() - $this->pivot()->x()), ); $point->setY( intval($point->y() - $this->pivot()->y()), ); // rotate point $x = $point->x() * $cos - $point->y() * $sin; $y = $point->x() * $sin + $point->y() * $cos; // translate point back $point->setX( intval($x + $this->pivot()->x()), ); $point->setY( intval($y + $this->pivot()->y()), ); } return $this; } /** * Move all points by given amount on the x-axis * * @param int $amount * @return Polygon */ public function movePointsX(int $amount): self { foreach ($this->points as $point) { $point->moveX($amount); } return $this; } /** * Move all points by given amount on the y-axis * * @param int $amount * @return Polygon */ public function movePointsY(int $amount): self { foreach ($this->points as $point) { $point->moveY($amount); } return $this; } /** * Return array of all x/y values of all points of polygon * * @return array */ public function toArray(): array { $coordinates = []; foreach ($this->points as $point) { $coordinates[] = $point->x(); $coordinates[] = $point->y(); } return $coordinates; } } src/Geometry/Rectangle.php000064400000014516150731646620011564 0ustar00addPoint(new Point($this->pivot->x(), $this->pivot->y())); $this->addPoint(new Point($this->pivot->x() + $width, $this->pivot->y())); $this->addPoint(new Point($this->pivot->x() + $width, $this->pivot->y() - $height)); $this->addPoint(new Point($this->pivot->x(), $this->pivot->y() - $height)); } public function setSize(int $width, int $height): self { return $this->setWidth($width)->setHeight($height); } public function setWidth(int $width): self { $this[1]->setX($this[0]->x() + $width); $this[2]->setX($this[3]->x() + $width); return $this; } public function setHeight(int $height): self { $this[2]->setY($this[1]->y() + $height); $this[3]->setY($this[0]->y() + $height); return $this; } public function pivot(): PointInterface { return $this->pivot; } public function setPivot(PointInterface $pivot): self { $this->pivot = $pivot; return $this; } public function movePivot(string $position, int $offset_x = 0, int $offset_y = 0): self { switch (strtolower($position)) { case 'top': case 'top-center': case 'top-middle': case 'center-top': case 'middle-top': $x = intval(round($this->width() / 2)) + $offset_x; $y = 0 + $offset_y; break; case 'top-right': case 'right-top': $x = $this->width() - $offset_x; $y = 0 + $offset_y; break; case 'left': case 'left-center': case 'left-middle': case 'center-left': case 'middle-left': $x = 0 + $offset_x; $y = intval(round($this->height() / 2)) + $offset_y; break; case 'right': case 'right-center': case 'right-middle': case 'center-right': case 'middle-right': $x = $this->width() - $offset_x; $y = intval(round($this->height() / 2)) + $offset_y; break; case 'bottom-left': case 'left-bottom': $x = 0 + $offset_x; $y = $this->height() - $offset_y; break; case 'bottom': case 'bottom-center': case 'bottom-middle': case 'center-bottom': case 'middle-bottom': $x = intval(round($this->width() / 2)) + $offset_x; $y = $this->height() - $offset_y; break; case 'bottom-right': case 'right-bottom': $x = $this->width() - $offset_x; $y = $this->height() - $offset_y; break; case 'center': case 'middle': case 'center-center': case 'middle-middle': $x = intval(round($this->width() / 2)) + $offset_x; $y = intval(round($this->height() / 2)) + $offset_y; break; default: case 'top-left': case 'left-top': $x = 0 + $offset_x; $y = 0 + $offset_y; break; } $this->pivot->setPosition($x, $y); return $this; } public function alignPivotTo(SizeInterface $size, string $position): self { $reference = new self($size->width(), $size->height()); $reference->movePivot($position); $this->movePivot($position)->setPivot( $reference->relativePositionTo($this) ); return $this; } public function relativePositionTo(SizeInterface $rectangle): PointInterface { return new Point( $this->pivot()->x() - $rectangle->pivot()->x(), $this->pivot()->y() - $rectangle->pivot()->y() ); } public function aspectRatio(): float { return $this->width() / $this->height(); } public function fitsInto(SizeInterface $size): bool { if ($this->width() > $size->width()) { return false; } if ($this->height() > $size->height()) { return false; } return true; } public function isLandscape(): bool { return $this->width() > $this->height(); } public function isPortrait(): bool { return $this->width() < $this->height(); } public function topLeftPoint(): PointInterface { return $this->points[0]; } public function bottomRightPoint(): PointInterface { return $this->points[2]; } protected function resizer(?int $width = null, ?int $height = null): RectangleResizer { return new RectangleResizer($width, $height); } public function resize(?int $width = null, ?int $height = null): SizeInterface { return $this->resizer($width, $height)->resize($this); } public function resizeDown(?int $width = null, ?int $height = null): SizeInterface { return $this->resizer($width, $height)->resizeDown($this); } public function scale(?int $width = null, ?int $height = null): SizeInterface { return $this->resizer($width, $height)->scale($this); } public function scaleDown(?int $width = null, ?int $height = null): SizeInterface { return $this->resizer($width, $height)->scaleDown($this); } public function cover(int $width, int $height): SizeInterface { return $this->resizer($width, $height)->cover($this); } public function contain(int $width, int $height): SizeInterface { return $this->resizer($width, $height)->contain($this); } public function containMax(int $width, int $height): SizeInterface { return $this->resizer($width, $height)->containDown($this); } } src/Geometry/Tools/RectangleResizer.php000064400000017100150731646620014220 0ustar00width); } protected function getTargetWidth(): ?int { return $this->hasTargetWidth() ? $this->width : null; } protected function hasTargetHeight(): bool { return is_integer($this->height); } protected function getTargetHeight(): ?int { return $this->hasTargetHeight() ? $this->height : null; } protected function getTargetSize(): SizeInterface { if (!$this->hasTargetWidth() || !$this->hasTargetHeight()) { throw new GeometryException('Target size needs width and height.'); } return new Rectangle($this->width, $this->height); } public function toWidth(int $width): self { $this->width = $width; return $this; } public function toHeight(int $height): self { $this->height = $height; return $this; } public function toSize(SizeInterface $size): self { $this->width = $size->width(); $this->height = $size->height(); return $this; } protected function getProportionalWidth(SizeInterface $size): int { if (!$this->hasTargetHeight()) { return $size->width(); } return (int) round($this->height * $size->aspectRatio()); } protected function getProportionalHeight(SizeInterface $size): int { if (!$this->hasTargetWidth()) { return $size->height(); } return (int) round($this->width / $size->aspectRatio()); } public function resize(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($width = $this->getTargetWidth()) { $resized->setWidth($width); } if ($height = $this->getTargetHeight()) { $resized->setHeight($height); } return $resized; } public function resizeDown(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($width = $this->getTargetWidth()) { $resized->setWidth( min($width, $size->width()) ); } if ($height = $this->getTargetHeight()) { $resized->setHeight( min($height, $size->height()) ); } return $resized; } public function scale(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($this->hasTargetWidth() && $this->hasTargetHeight()) { $resized->setWidth(min( $this->getProportionalWidth($size), $this->getTargetWidth() )); $resized->setHeight(min( $this->getProportionalHeight($size), $this->getTargetHeight() )); } elseif ($this->hasTargetWidth()) { $resized->setWidth($this->getTargetWidth()); $resized->setHeight($this->getProportionalHeight($size)); } elseif ($this->hasTargetHeight()) { $resized->setWidth($this->getProportionalWidth($size)); $resized->setHeight($this->getTargetHeight()); } return $resized; } public function scaleDown(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($this->hasTargetWidth() && $this->hasTargetHeight()) { $resized->setWidth(min( $this->getProportionalWidth($size), $this->getTargetWidth(), $size->width() )); $resized->setHeight(min( $this->getProportionalHeight($size), $this->getTargetHeight(), $size->height() )); } elseif ($this->hasTargetWidth()) { $resized->setWidth(min( $this->getTargetWidth(), $size->width() )); $resized->setHeight(min( $this->getProportionalHeight($size), $size->height() )); } elseif ($this->hasTargetHeight()) { $resized->setWidth(min( $this->getProportionalWidth($size), $size->width() )); $resized->setHeight(min( $this->getTargetHeight(), $size->height() )); } return $resized; } /** * Scale given size to cover target size * * @param SizeInterface $size Size to be resized * @return SizeInterface */ public function cover(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); // auto height $resized->setWidth($this->getTargetWidth()); $resized->setHeight($this->getProportionalHeight($size)); if ($resized->fitsInto($this->getTargetSize())) { // auto width $resized->setWidth($this->getProportionalWidth($size)); $resized->setHeight($this->getTargetHeight()); } return $resized; } /** * Scale given size to contain target size * * @param SizeInterface $size Size to be resized * @return SizeInterface */ public function contain(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); // auto height $resized->setWidth($this->getTargetWidth()); $resized->setHeight($this->getProportionalHeight($size)); if (!$resized->fitsInto($this->getTargetSize())) { // auto width $resized->setWidth($this->getProportionalWidth($size)); $resized->setHeight($this->getTargetHeight()); } return $resized; } /** * Scale given size to contain target size but prevent upsizing * * @param SizeInterface $size Size to be resized * @return SizeInterface */ public function containDown(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); // auto height $resized->setWidth( min($size->width(), $this->getTargetWidth()) ); $resized->setHeight( min($size->height(), $this->getProportionalHeight($size)) ); if (!$resized->fitsInto($this->getTargetSize())) { // auto width $resized->setWidth( min($size->width(), $this->getProportionalWidth($size)) ); $resized->setHeight( min($size->height(), $this->getTargetHeight()) ); } return $resized; } /** * Crop target size out of given size at given position (i.e. move the pivot point) * * @param SizeInterface $size * @param string $position * @return SizeInterface */ public function crop(SizeInterface $size, string $position = 'top-left'): SizeInterface { return $this->resize($size)->alignPivotTo( $size->movePivot($position), $position ); } } src/Geometry/Traits/HasBackgroundColor.php000064400000000724150731646620014634 0ustar00backgroundColor = $color; return $this; } public function backgroundColor(): mixed { return $this->backgroundColor; } public function hasBackgroundColor(): bool { return !empty($this->backgroundColor); } } src/Geometry/Traits/HasBorder.php000064400000001511150731646620012766 0ustar00setBorderSize($size)->setBorderColor($color); } public function setBorderSize(int $size): self { $this->borderSize = $size; return $this; } public function borderSize(): int { return $this->borderSize; } public function setBorderColor(mixed $color): self { $this->borderColor = $color; return $this; } public function borderColor(): mixed { return $this->borderColor; } public function hasBorder(): bool { return $this->borderSize > 0 && !is_null($this->borderColor); } } src/Image.php000064400000050362150731646620007106 0ustar00origin = new Origin(); } /** * {@inheritdoc} * * @see ImageInterface::driver() */ public function driver(): DriverInterface { return $this->driver; } /** * {@inheritdoc} * * @see ImageInterface::core() */ public function core(): CoreInterface { return $this->core; } /** * {@inheritdoc} * * @see ImageInterface::origin() */ public function origin(): Origin { return $this->origin; } /** * {@inheritdoc} * * @see ImageInterface::setOrigin() */ public function setOrigin(Origin $origin): ImageInterface { $this->origin = $origin; return $this; } /** * {@inheritdoc} * * @see ImageInterface::count() */ public function count(): int { return $this->core->count(); } /** * Implementation of IteratorAggregate * * @return Traversable */ public function getIterator(): Traversable { return $this->core; } /** * {@inheritdoc} * * @see ImageInterface::isAnimated() */ public function isAnimated(): bool { return $this->count() > 1; } /** * {@inheritdoc} * * @see ImageInterface::removeAnimation( */ public function removeAnimation(int|string $position = 0): ImageInterface { return $this->modify(new RemoveAnimationModifier($position)); } /** * {@inheritdoc} * * @see ImageInterface::loops() */ public function loops(): int { return $this->core->loops(); } /** * {@inheritdoc} * * @see ImageInterface::setLoops() */ public function setLoops(int $loops): ImageInterface { $this->core->setLoops($loops); return $this; } /** * {@inheritdoc} * * @see ImageInterface::exif() */ public function exif(?string $query = null): mixed { return is_null($query) ? $this->exif : $this->exif->get($query); } /** * {@inheritdoc} * * @see ImageInterface::modify() */ public function modify(ModifierInterface $modifier): ImageInterface { return $this->driver->resolve($modifier)->apply($this); } /** * {@inheritdoc} * * @see ImageInterface::analyze() */ public function analyze(AnalyzerInterface $analyzer): mixed { return $this->driver->resolve($analyzer)->analyze($this); } /** * {@inheritdoc} * * @see ImageInterface::encode() */ public function encode(EncoderInterface $encoder = new AutoEncoder()): EncodedImage { return $this->driver->resolve($encoder)->encode($this); } /** * {@inheritdoc} * * @see ImageInterface::width() */ public function width(): int { return $this->analyze(new WidthAnalyzer()); } /** * {@inheritdoc} * * @see ImageInterface::height() */ public function height(): int { return $this->analyze(new HeightAnalyzer()); } /** * {@inheritdoc} * * @see ImageInterface::size() */ public function size(): SizeInterface { return new Rectangle($this->width(), $this->height()); } /** * {@inheritdoc} * * @see ImageInterface::colorspace() */ public function colorspace(): ColorspaceInterface { return $this->analyze(new ColorspaceAnalyzer()); } /** * {@inheritdoc} * * @see ImageInterface::setColorspace() */ public function setColorspace(string|ColorspaceInterface $colorspace): ImageInterface { return $this->modify(new ColorspaceModifier($colorspace)); } /** * {@inheritdoc} * * @see ImageInterface::resolution() */ public function resolution(): ResolutionInterface { return $this->analyze(new ResolutionAnalyzer()); } /** * {@inheritdoc} * * @see ImageInterface::setResolution() */ public function setResolution(float $x, float $y): ImageInterface { return $this->modify(new ResolutionModifier($x, $y)); } /** * {@inheritdoc} * * @see ImageInterface::pickColor() */ public function pickColor(int $x, int $y, int $frame_key = 0): ColorInterface { return $this->analyze(new PixelColorAnalyzer($x, $y, $frame_key)); } /** * {@inheritdoc} * * @see ImageInterface::pickColors() */ public function pickColors(int $x, int $y): CollectionInterface { return $this->analyze(new PixelColorsAnalyzer($x, $y)); } /** * {@inheritdoc} * * @see ImageInterface::profile() */ public function profile(): ProfileInterface { return $this->analyze(new ProfileAnalyzer()); } /** * {@inheritdoc} * * @see ImageInterface::setProfile() */ public function setProfile(ProfileInterface $profile): ImageInterface { return $this->modify(new ProfileModifier($profile)); } /** * {@inheritdoc} * * @see ImageInterface::removeProfile() */ public function removeProfile(): ImageInterface { return $this->modify(new ProfileRemovalModifier()); } /** * {@inheritdoc} * * @see ImageInterface::reduceColors() */ public function reduceColors(int $limit, mixed $background = 'transparent'): ImageInterface { return $this->modify(new QuantizeColorsModifier($limit, $background)); } /** * {@inheritdoc} * * @see ImageInterface::sharpen() */ public function sharpen(int $amount = 10): ImageInterface { return $this->modify(new SharpenModifier($amount)); } /** * {@inheritdoc} * * @see ImageInterface::invert() */ public function invert(): ImageInterface { return $this->modify(new InvertModifier()); } /** * {@inheritdoc} * * @see ImageInterface::pixelate() */ public function pixelate(int $size): ImageInterface { return $this->modify(new PixelateModifier($size)); } /** * {@inheritdoc} * * @see ImageInterface::greyscale() */ public function greyscale(): ImageInterface { return $this->modify(new GreyscaleModifier()); } /** * {@inheritdoc} * * @see ImageInterface::brightness() */ public function brightness(int $level): ImageInterface { return $this->modify(new BrightnessModifier($level)); } /** * {@inheritdoc} * * @see ImageInterface::contrast() */ public function contrast(int $level): ImageInterface { return $this->modify(new ContrastModifier($level)); } /** * {@inheritdoc} * * @see ImageInterface::gamma() */ public function gamma(float $gamma): ImageInterface { return $this->modify(new GammaModifier($gamma)); } /** * {@inheritdoc} * * @see ImageInterface::colorize() */ public function colorize(int $red = 0, int $green = 0, int $blue = 0): ImageInterface { return $this->modify(new ColorizeModifier($red, $green, $blue)); } /** * {@inheritdoc} * * @see ImageInterface::flip() */ public function flip(): ImageInterface { return $this->modify(new FlipModifier()); } /** * {@inheritdoc} * * @see ImageInterface::flop() */ public function flop(): ImageInterface { return $this->modify(new FlopModifier()); } /** * {@inheritdoc} * * @see ImageInterface::blur() */ public function blur(int $amount = 5): ImageInterface { return $this->modify(new BlurModifier($amount)); } /** * {@inheritdoc} * * @see ImageInterface::rotate() */ public function rotate(float $angle, mixed $background = 'ffffff'): ImageInterface { return $this->modify(new RotateModifier($angle, $background)); } /** * {@inheritdoc} * * @see ImageInterface::text() */ public function text(string $text, int $x, int $y, callable|FontInterface $font): ImageInterface { return $this->modify( new TextModifier( $text, new Point($x, $y), call_user_func(new FontFactory($font)), ), ); } /** * {@inheritdoc} * * @see ImageInterface::resize() */ public function resize(?int $width = null, ?int $height = null): ImageInterface { return $this->modify(new ResizeModifier($width, $height)); } /** * {@inheritdoc} * * @see ImageInterface::resizeDown() */ public function resizeDown(?int $width = null, ?int $height = null): ImageInterface { return $this->modify(new ResizeDownModifier($width, $height)); } /** * {@inheritdoc} * * @see ImageInterface::scale() */ public function scale(?int $width = null, ?int $height = null): ImageInterface { return $this->modify(new ScaleModifier($width, $height)); } /** * {@inheritdoc} * * @see ImageInterface::scaleDown() */ public function scaleDown(?int $width = null, ?int $height = null): ImageInterface { return $this->modify(new ScaleDownModifier($width, $height)); } /** * {@inheritdoc} * * @see ImageInterface::cover() */ public function cover(int $width, int $height, string $position = 'center'): ImageInterface { return $this->modify(new CoverModifier($width, $height, $position)); } /** * {@inheritdoc} * * @see ImageInterface::coverDown() */ public function coverDown(int $width, int $height, string $position = 'center'): ImageInterface { return $this->modify(new CoverDownModifier($width, $height, $position)); } /** * {@inheritdoc} * * @see ImageInterface::resizeCanvas() */ public function resizeCanvas( ?int $width = null, ?int $height = null, mixed $background = 'ffffff', string $position = 'center' ): ImageInterface { return $this->modify(new ResizeCanvasModifier($width, $height, $background, $position)); } /** * {@inheritdoc} * * @see ImageInterface::resizeCanvasRelative() */ public function resizeCanvasRelative( ?int $width = null, ?int $height = null, mixed $background = 'ffffff', string $position = 'center' ): ImageInterface { return $this->modify(new ResizeCanvasRelativeModifier($width, $height, $background, $position)); } /** * {@inheritdoc} * * @see ImageInterface::padDown() */ public function pad( int $width, int $height, mixed $background = 'ffffff', string $position = 'center' ): ImageInterface { return $this->modify(new PadModifier($width, $height, $background, $position)); } /** * {@inheritdoc} * * @see ImageInterface::pad() */ public function contain( int $width, int $height, mixed $background = 'ffffff', string $position = 'center' ): ImageInterface { return $this->modify(new ContainModifier($width, $height, $background, $position)); } /** * {@inheritdoc} * * @see ImageInterface::crop() */ public function crop( int $width, int $height, int $offset_x = 0, int $offset_y = 0, string $position = 'top-left' ): ImageInterface { return $this->modify(new CropModifier($width, $height, $offset_x, $offset_y, $position)); } /** * {@inheritdoc} * * @see ImageInterface::place() */ public function place( mixed $element, string $position = 'top-left', int $offset_x = 0, int $offset_y = 0 ): ImageInterface { return $this->modify(new PlaceModifier($element, $position, $offset_x, $offset_y)); } /** * {@inheritdoc} * * @see ImageInterface::fill() */ public function fill(mixed $color, ?int $x = null, ?int $y = null): ImageInterface { return $this->modify( new FillModifier( $color, (is_null($x) || is_null($y)) ? null : new Point($x, $y), ), ); } /** * {@inheritdoc} * * @see ImageInterface::drawPixel() */ public function drawPixel(int $x, int $y, mixed $color): ImageInterface { return $this->modify(new DrawPixelModifier(new Point($x, $y), $color)); } /** * {@inheritdoc} * * @see ImageInterface::drawRectangle() */ public function drawRectangle(int $x, int $y, callable|Rectangle $init): ImageInterface { return $this->modify( new DrawRectangleModifier( call_user_func(new RectangleFactory(new Point($x, $y), $init)), ), ); } /** * {@inheritdoc} * * @see ImageInterface::drawEllipse() */ public function drawEllipse(int $x, int $y, callable $init): ImageInterface { return $this->modify( new DrawEllipseModifier( call_user_func(new EllipseFactory(new Point($x, $y), $init)), ), ); } /** * {@inheritdoc} * * @see ImageInterface::drawCircle() */ public function drawCircle(int $x, int $y, callable $init): ImageInterface { return $this->modify( new DrawEllipseModifier( call_user_func(new CircleFactory(new Point($x, $y), $init)), ), ); } public function drawPolygon(callable $init): ImageInterface { return $this->modify( new DrawPolygonModifier( call_user_func(new PolygonFactory($init)), ), ); } public function drawLine(callable $init): ImageInterface { return $this->modify( new DrawLineModifier( call_user_func(new LineFactory($init)), ), ); } /** * {@inheritdoc} * * @see ImageInterface::toJpeg() */ public function toJpeg(int $quality = 75): EncodedImageInterface { return $this->encode(new JpegEncoder($quality)); } /** * Alias of self::toJpeg() * * @param int $quality * @return EncodedImageInterface */ public function toJpg(int $quality = 75): EncodedImageInterface { return $this->toJpeg($quality); } /** * {@inheritdoc} * * @see ImageInterface::toPng() */ public function toPng(): EncodedImageInterface { return $this->encode(new PngEncoder()); } /** * {@inheritdoc} * * @see ImageInterface::toGif() */ public function toGif(): EncodedImageInterface { return $this->encode(new GifEncoder()); } /** * {@inheritdoc} * * @see ImageInterface::toWebp() */ public function toWebp(int $quality = 75): EncodedImageInterface { return $this->encode(new WebpEncoder($quality)); } /** * {@inheritdoc} * * @see ImageInterface::toBitmap() */ public function toBitmap(): EncodedImageInterface { return $this->encode(new BmpEncoder()); } /** * Alias if self::toBitmap() * * @return EncodedImageInterface */ public function toBmp(): EncodedImageInterface { return $this->toBitmap(); } /** * {@inheritdoc} * * @see ImageInterface::toAvif() */ public function toAvif(int $quality = 75): EncodedImageInterface { return $this->encode(new AvifEncoder($quality)); } /** * Clone image * * @return void */ public function __clone(): void { $this->driver = clone $this->driver; $this->core = clone $this->core; $this->exif = clone $this->exif; } } src/ImageManager.php000064400000004771150731646620010404 0ustar00driver = $this->resolveDriver($driver); } /** * Create image mangager with given driver * * @param string|DriverInterface $driver * @return ImageManager */ public static function withDriver(string|DriverInterface $driver): self { return new self(self::resolveDriver($driver)); } /** * Create image manager with GD driver * * @return ImageManager */ public static function gd(): self { return self::withDriver(GdDriver::class); } /** * Create image manager with Imagick driver * * @return ImageManager */ public static function imagick(): self { return self::withDriver(ImagickDriver::class); } /** * Create new image instance with given width & height * * @param int $width * @param int $height * @return ImageInterface */ public function create(int $width, int $height): ImageInterface { return $this->driver->createImage($width, $height); } /** * Create new image instance from given source which can be one of the following * * - Path in filesystem * - File Pointer resource * - SplFileInfo object * - Raw binary image data * - Base64 encoded image data * - Data Uri * - Intervention\Image\Image Instance * * @param mixed $input * @return ImageInterface */ public function read(mixed $input): ImageInterface { return $this->driver->handleInput($input); } /** * Create new animated image by given callback * * @param callable $init * @return ImageInterface */ public function animate(callable $init): ImageInterface { return $this->driver->createAnimation($init); } /** * Return driver object * * @param string|DriverInterface $driver * @return DriverInterface */ private static function resolveDriver(string|DriverInterface $driver): DriverInterface { if (is_object($driver)) { return $driver; } return new $driver(); } } src/Interfaces/AnalyzerInterface.php000064400000000425150731646620013550 0ustar00modifiers as $modifier) { $modifier->apply($image); } return $image; } public function push(ModifierInterface $modifier): self { $this->modifiers[] = $modifier; return $this; } } src/Modifiers/AbstractModifier.php000064400000000637150731646620013227 0ustar00modify($this); } } src/Modifiers/BlurModifier.php000064400000000240150731646620012356 0ustar00target)) { return $this->target; } if (in_array($this->target, ['rgb', 'RGB', RgbColorspace::class])) { return new RgbColorspace(); } if (in_array($this->target, ['cmyk', 'CMYK', CmykColorspace::class])) { return new CmykColorspace(); } throw new NotSupportedException('Given colorspace is not supported.'); } } src/Modifiers/ContainModifier.php000064400000001613150731646620013052 0ustar00size() ->contain( $this->width, $this->height ) ->alignPivotTo( $this->getResizeSize($image), $this->position ); } public function getResizeSize(ImageInterface $image): SizeInterface { return new Rectangle($this->width, $this->height); } } src/Modifiers/ContrastModifier.php000064400000000243150731646620013252 0ustar00size(); $crop = new Rectangle($this->width, $this->height); $crop = $crop->contain( $imagesize->width(), $imagesize->height() )->alignPivotTo($imagesize, $this->position); return $crop; } public function getResizeSize(SizeInterface $size): SizeInterface { return $size->scale($this->width, $this->height); } } src/Modifiers/CropModifier.php000064400000001335150731646620012363 0ustar00width, $this->height); $crop->align($this->position); return $crop->alignPivotTo( $image->size(), $this->position ); } } src/Modifiers/DrawEllipseModifier.php000064400000000327150731646620013673 0ustar00position); } } src/Modifiers/FlipModifier.php000064400000000140150731646620012343 0ustar00size() ->containMax( $this->width, $this->height ) ->alignPivotTo( $this->getResizeSize($image), $this->position ); } } src/Modifiers/PixelateModifier.php000064400000000242150731646620013227 0ustar00size()->movePivot( $this->position, $this->offset_x, $this->offset_y ); $watermark_size = $watermark->size()->movePivot( $this->position ); return $image_size->relativePositionTo($watermark_size); } } src/Modifiers/ProfileModifier.php000064400000000346150731646620013061 0ustar00core()->frame($position); } if (preg_match("/^(?P[0-9]{1,3})%$/", $position, $matches) != 1) { throw new InputException( 'Position must be either integer or a percent value as string.' ); } $total = count($image); $position = intval(round($total / 100 * intval($matches['percent']))); $position = $position == $total ? $position - 1 : $position; return $image->core()->frame($position); } } src/Modifiers/ResizeCanvasModifier.php000064400000001507150731646620014056 0ustar00width) ? $image->width() : $this->width; $height = is_null($this->height) ? $image->height() : $this->height; return (new Rectangle($width, $height)) ->alignPivotTo( $image->size(), $this->position ); } } src/Modifiers/ResizeCanvasRelativeModifier.php000064400000001251150731646620015546 0ustar00width) ? $image->width() : $image->width() + $this->width; $height = is_null($this->height) ? $image->height() : $image->height() + $this->height; return (new Rectangle($width, $height)) ->alignPivotTo( $image->size(), $this->position ); } } src/Modifiers/ResizeDownModifier.php000064400000000144150731646620013546 0ustar00angle, 360); } } src/Modifiers/ScaleDownModifier.php000064400000000142150731646620013332 0ustar00mimetype; } } src/Resolution.php000064400000005371150731646620010227 0ustar00x; } /** * {@inheritdoc} * * @see ResolutionInterface::setX() */ public function setX(float $x): self { $this->x = $x; return $this; } /** * {@inheritdoc} * * @see ResolutionInterface::y() */ public function y(): float { return $this->y; } /** * {@inheritdoc} * * @see ResolutionInterface::setY() */ public function setY(float $y): self { $this->y = $y; return $this; } /** * {@inheritdoc} * * @see ResolutionInterface::setPerUnit() */ protected function setPerUnit(int $per_unit): self { $this->per_unit = $per_unit; return $this; } /** * {@inheritdoc} * * @see ResolutionInterface::unit() */ public function unit(): string { return match ($this->per_unit) { self::PER_CM => 'dpcm', default => 'dpi', }; } /** * {@inheritdoc} * * @see ResolutionInterface::perInch() */ public function perInch(): self { return match ($this->per_unit) { self::PER_CM => $this ->setPerUnit(self::PER_INCH) ->setX($this->x * (1 / 2.54)) ->setY($this->y * (1 / 2.54)), default => $this }; } /** * {@inheritdoc} * * @see ResolutionInterface::perCm() */ public function perCm(): self { return match ($this->per_unit) { self::PER_INCH => $this ->setPerUnit(self::PER_CM) ->setX($this->x / (1 / 2.54)) ->setY($this->y / (1 / 2.54)), default => $this, }; } /** * {@inheritdoc} * * @see ResolutionInterface::toString() */ public function toString(): string { return sprintf("%1\$.2f x %2\$.2f %3\$s", $this->x, $this->y, $this->unit()); } /** * {@inheritdoc} * * @see ResolutionInterface::__toString() */ public function __toString(): string { return $this->toString(); } } src/Traits/CanBuildFilePointer.php000064400000000414150731646620013145 0ustar00filename = $filename; } /** * {@inheritdoc} * * @see FontInterface::setSize() */ public function setSize(float $size): FontInterface { $this->size = $size; return $this; } /** * {@inheritdoc} * * @see FontInterface::size() */ public function size(): float { return $this->size; } /** * {@inheritdoc} * * @see FontInterface::setAngle() */ public function setAngle(float $angle): FontInterface { $this->angle = $angle; return $this; } /** * {@inheritdoc} * * @see FontInterface::angle() */ public function angle(): float { return $this->angle; } /** * {@inheritdoc} * * @see FontInterface::setFilename() */ public function setFilename(string $filename): FontInterface { $this->filename = $filename; return $this; } /** * {@inheritdoc} * * @see FontInterface::filename() */ public function filename(): ?string { return $this->filename; } /** * {@inheritdoc} * * @see FontInterface::hasFilename() */ public function hasFilename(): bool { return !is_null($this->filename) && is_file($this->filename); } /** * {@inheritdoc} * * @see FontInterface::setColor() */ public function setColor(mixed $color): FontInterface { $this->color = $color; return $this; } /** * {@inheritdoc} * * @see FontInterface::color() */ public function color(): mixed { return $this->color; } /** * {@inheritdoc} * * @see FontInterface::alignment() */ public function alignment(): string { return $this->alignment; } /** * {@inheritdoc} * * @see FontInterface::setAlignment() */ public function setAlignment(string $value): FontInterface { $this->alignment = $value; return $this; } /** * {@inheritdoc} * * @see FontInterface::valignment() */ public function valignment(): string { return $this->valignment; } /** * {@inheritdoc} * * @see FontInterface::setValignment() */ public function setValignment(string $value): FontInterface { $this->valignment = $value; return $this; } /** * {@inheritdoc} * * @see FontInterface::setLineHeight() */ public function setLineHeight(float $height): FontInterface { $this->lineHeight = $height; return $this; } /** * {@inheritdoc} * * @see FontInterface::lineHeight() */ public function lineHeight(): float { return $this->lineHeight; } } src/Typography/FontFactory.php000064400000002475150731646620012472 0ustar00font = is_a($init, FontInterface::class) ? $init : new Font(); if (is_callable($init)) { $init($this); } } public function __invoke(): FontInterface { return $this->font; } public function filename(string $value): self { $this->font->setFilename($value); return $this; } public function color(mixed $value): self { $this->font->setColor($value); return $this; } public function size(float $value): self { $this->font->setSize($value); return $this; } public function align(string $value): self { $this->font->setAlignment($value); return $this; } public function valign(string $value): self { $this->font->setValignment($value); return $this; } public function lineHeight(float $value): self { $this->font->setLineHeight($value); return $this; } public function angle(float $value): self { $this->font->setAngle($value); return $this; } } src/Typography/Line.php000064400000001112150731646620011106 0ustar00position; } public function setPosition(Point $point): self { $this->position = $point; return $this; } public function __toString(): string { return $this->text; } } src/Typography/TextBlock.php000064400000002023150731646620012120 0ustar00push(new Line($line)); } } /** * Return array of lines in text block * * @return array */ public function lines(): array { return $this->items; } public function line($key): ?Line { if (!array_key_exists($key, $this->lines())) { return null; } return $this->lines()[$key]; } /** * Return line with most characters of text block * * @return Line */ public function longestLine(): Line { $lines = $this->lines(); usort($lines, function ($a, $b) { if (mb_strlen($a) === mb_strlen($b)) { return 0; } return (mb_strlen($a) > mb_strlen($b)) ? -1 : 1; }); return $lines[0]; } }