LICENSE000064400000002074150731645420005563 0ustar00The MIT License (MIT) Copyright (c) 2013-2024 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.json000064400000002316150731645420007277 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": "^4.0.1" }, "require-dev": { "phpunit/phpunit": "^10.0", "mockery/mockery": "^1.6", "phpstan/phpstan": "^1", "squizlabs/php_codesniffer": "^3.8", "slevomat/coding-standard": "~8.0" }, "suggest": { "ext-exif": "Recommended to be able to read EXIF data properly." }, "autoload": { "psr-4": { "Intervention\\Image\\": "src" } }, "autoload-dev": { "psr-4": { "Intervention\\Image\\Tests\\": "tests" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } readme.md000064400000005217150731645420006337 0ustar00# Intervention Image ## PHP Image Processing [![Latest Version](https://img.shields.io/packagist/v/intervention/image.svg)](https://packagist.org/packages/intervention/image) [![Build Status](https://github.com/Intervention/image/actions/workflows/run-tests.yml/badge.svg)](https://github.com/Intervention/image/actions) [![Monthly Downloads](https://img.shields.io/packagist/dm/intervention/image.svg)](https://packagist.org/packages/intervention/image/stats) Intervention Image is a **PHP image processing library** that provides a simple and expressive way to create, edit, and compose images. It features a unified API for the two most popular image manipulation extensions. You can choose between the GD library or Imagick as the base layer for all operations. - Simple interface for common image editing tasks - Interchangable driver architecture - Support for animated images - Framework-agnostic - PSR-12 compliant ## Installation You can easily install this library using [Composer](https://getcomposer.org). Just request the package with the following command: ```bash composer require intervention/image ``` ## Getting Started Learn the [basics](https://image.intervention.io/v3/basics/instantiation/) on how to use Intervention Image and more with the [official documentation](https://image.intervention.io/v3/). ## Code Examples ```php use Intervention\Image\ImageManager; // create image manager with desired driver $manager = new ImageManager( new Intervention\Image\Drivers\Gd\Driver() ); // open an image file $image = $manager->read('images/example.gif'); // resize image instance $image->resize(height: 300); // insert a watermark $image->place('images/watermark.png'); // encode edited image $encoded = $image->toJpg(); // save encoded image $encoded->save('images/example.jpg'); ``` ## Requirements - PHP >= 8.1 ## Supported Image Libraries - GD Library - Imagick PHP extension ## Development & Testing This package contains a Docker image for building a test suite and an analysis container. You must have Docker installed on your system to run all tests using the following command. ```bash docker-compose run --rm --build tests ``` Run the static analyzer on the code base. ```bash docker-compose run --rm --build analysis ``` ## Security If you discover any security related issues, please email oliver@intervention.io directly. ## Authors This library is developed and maintained by [Oliver Vogel](https://intervention.io) Thanks to the community of [contributors](https://github.com/Intervention/image/graphs/contributors) who have helped to improve this project. ## License Intervention Image is licensed under the [MIT License](LICENSE). src/Analyzers/ColorspaceAnalyzer.php000064400000000205150731645420013620 0ustar00analyze($this); } } src/Analyzers/WidthAnalyzer.php000064400000000200150731645420012600 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 int */ 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 int $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]; } /** * {@inheritdoc} * * @see CollectionInterface::get() */ 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('.', (string) $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); } /** * {@inheritdoc} * * @see CollectionInterface::empty() */ public function empty(): CollectionInterface { $this->items = []; return $this; } /** * {@inheritdoc} * * @see CollectionInterface::slice() */ public function slice(int $offset, ?int $length = null): CollectionInterface { $this->items = array_slice($this->items, $offset, $length); return $this; } } src/Colors/AbstractColor.php000064400000003623150731645420012062 0ustar00channels; } public function channel(string $classname): ColorChannelInterface { $channels = array_filter($this->channels(), function (ColorChannelInterface $channel) use ($classname) { return $channel::class == $classname; }); if (count($channels) == 0) { throw new ColorException('Color 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.php000064400000004130150731645420013345 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.php000064400000000463150731645420012647 0ustar00channels = [ new Cyan($c), new Magenta($m), new Yellow($y), new Key($k), ]; } /** * {@inheritdoc} * * @see ColorInterface::create() */ public static function create(mixed $input): ColorInterface { return (new class ([ Decoders\StringColorDecoder::class, ]) extends AbstractInputHandler { })->handle($input); } /** * {@inheritdoc} * * @see ColorInterface::colorspace() */ public function colorspace(): ColorspaceInterface { return new Colorspace(); } /** * {@inheritdoc} * * @see ColorInterface::toHex() */ public function toHex(string $prefix = ''): string { return $this->convertTo(RgbColorspace::class)->toHex($prefix); } /** * Return the CMYK cyan channel * * @return ColorChannelInterface */ public function cyan(): ColorChannelInterface { /** @throws void */ return $this->channel(Cyan::class); } /** * Return the CMYK magenta channel * * @return ColorChannelInterface */ public function magenta(): ColorChannelInterface { /** @throws void */ return $this->channel(Magenta::class); } /** * Return the CMYK yellow channel * * @return ColorChannelInterface */ public function yellow(): ColorChannelInterface { /** @throws void */ return $this->channel(Yellow::class); } /** * Return the CMYK key channel * * @return ColorChannelInterface */ public function key(): ColorChannelInterface { /** @throws void */ return $this->channel(Key::class); } /** * {@inheritdoc} * * @see ColorInterface::toString() */ public function toString(): string { return sprintf( 'cmyk(%d%%, %d%%, %d%%, %d%%)', $this->cyan()->value(), $this->magenta()->value(), $this->yellow()->value(), $this->key()->value() ); } /** * {@inheritdoc} * * @see ColorInterface::isGreyscale() */ public function isGreyscale(): bool { return 0 === array_sum([ $this->cyan()->value(), $this->magenta()->value(), $this->yellow()->value(), ]); } /** * {@inheritdoc} * * @see ColorInterface::isTransparent() */ public function isTransparent(): bool { return false; } } src/Colors/Cmyk/Colorspace.php000064400000004006150731645420012311 0ustar00value(); }, self::$channels, $normalized); return new Color(...$values); } /** * {@inheritdoc} * * @see ColorspaceInterface::importColor() */ public function importColor(ColorInterface $color): ColorInterface { return match ($color::class) { 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.php000064400000002372150731645420015506 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.php000064400000000733150731645420012321 0ustar00channels = [ new Hue($h), new Saturation($s), new Luminance($l), ]; } /** * {@inheritdoc} * * @see ColorInterface::colorspace() */ 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 { /** @throws void */ return $this->channel(Hue::class); } /** * Return the Saturation channel * * @return ColorChannelInterface */ public function saturation(): ColorChannelInterface { /** @throws void */ return $this->channel(Saturation::class); } /** * Return the Luminance channel * * @return ColorChannelInterface */ public function luminance(): ColorChannelInterface { /** @throws void */ 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; } /** * {@inheritdoc} * * @see ColorInterface::isTransparent() */ public function isTransparent(): bool { return false; } } src/Colors/Hsl/Colorspace.php000064400000006473150731645420012146 0ustar00value(); }, self::$channels, $normalized); return new Color(...$values); } public function importColor(ColorInterface $color): ColorInterface { return match ($color::class) { 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.php000064400000002470150731645420015330 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.php000064400000000733150731645420012333 0ustar00channels = [ new Hue($h), new Saturation($s), new Value($v), ]; } /** * {@inheritdoc} * * @see ColorInterface::colorspace() */ 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 { /** @throws void */ return $this->channel(Hue::class); } /** * Return the Saturation channel * * @return ColorChannelInterface */ public function saturation(): ColorChannelInterface { /** @throws void */ return $this->channel(Saturation::class); } /** * Return the Value channel * * @return ColorChannelInterface */ public function value(): ColorChannelInterface { /** @throws void */ 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; } /** * {@inheritdoc} * * @see ColorInterface::isTransparent() */ public function isTransparent(): bool { return false; } } src/Colors/Hsv/Colorspace.php000064400000005651150731645420012155 0ustar00value(); }, self::$channels, $normalized); return new Color(...$values); } /** * {@inheritdoc} * * @see ColorspaceInterface::importColor() */ public function importColor(ColorInterface $color): ColorInterface { return match ($color::class) { 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.php000064400000002500150731645420015334 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.php000064400000000324150731645420010713 0ustar00normalize(), 6)); } } src/Colors/Rgb/Channels/Blue.php000064400000000157150731645420012453 0ustar00channels = [ new Red($r), new Green($g), new Blue($b), new Alpha($a), ]; } /** * {@inheritdoc} * * @see ColorInterface::colorspace() */ 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 { /** @throws void */ return $this->channel(Red::class); } /** * Return the RGB green color channel * * @return ColorChannelInterface */ public function green(): ColorChannelInterface { /** @throws void */ return $this->channel(Green::class); } /** * Return the RGB blue color channel * * @return ColorChannelInterface */ public function blue(): ColorChannelInterface { /** @throws void */ return $this->channel(Blue::class); } /** * Return the colors alpha channel * * @return ColorChannelInterface */ public function alpha(): ColorChannelInterface { /** @throws void */ return $this->channel(Alpha::class); } /** * {@inheritdoc} * * @see ColorInterface::toHex() */ public function toHex(string $prefix = ''): string { if ($this->isTransparent()) { return sprintf( '%s%02x%02x%02x%02x', $prefix, $this->red()->value(), $this->green()->value(), $this->blue()->value(), $this->alpha()->value() ); } return sprintf( '%s%02x%02x%02x', $prefix, $this->red()->value(), $this->green()->value(), $this->blue()->value() ); } /** * {@inheritdoc} * * @see ColorInterface::toString() */ public function toString(): string { if ($this->isTransparent()) { return sprintf( 'rgba(%d, %d, %d, %.1F)', $this->red()->value(), $this->green()->value(), $this->blue()->value(), $this->alpha()->normalize(), ); } return sprintf( 'rgb(%d, %d, %d)', $this->red()->value(), $this->green()->value(), $this->blue()->value() ); } /** * {@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; } /** * {@inheritdoc} * * @see ColorInterface::isTransparent() */ public function isTransparent(): bool { return $this->alpha()->value() < $this->alpha()->max(); } } src/Colors/Rgb/Colorspace.php000064400000007037150731645420012127 0ustar00value(); }, self::$channels, $normalized); return new Color(...$values); } /** * {@inheritdoc} * * @see ColorspaceInterface::importColor() */ public function importColor(ColorInterface $color): ColorInterface { return match ($color::class) { 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.php000064400000003174150731645420014574 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.php000064400000013026150731645420015612 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(mixed $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.php000064400000003405150731645420015313 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.php000064400000001226150731645420016345 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 1 === preg_match( "/^47494638(37|39)61/", strtoupper(substr(bin2hex($input), 0, 32)) ); } /** * Determine if given input is a path to an existing regular file * * @param mixed $input * @return bool */ protected function isFile(mixed $input): bool { if (!is_string($input)) { return false; } if (strlen($input) > PHP_MAXPATHLEN) { return false; } try { if (!@is_file($input)) { return false; } } catch (Exception) { return false; } return true; } /** * Extract and return EXIF data from given input which can be binary image * data or a file path. * * @param string $path_or_data * @return CollectionInterface */ protected function extractExifData(string $path_or_data): CollectionInterface { if (!function_exists('exif_read_data')) { return new Collection(); } try { $source = match (true) { (strlen($path_or_data) <= PHP_MAXPATHLEN && is_file($path_or_data)) => $path_or_data, // path default => $this->buildFilePointer($path_or_data), // data }; // extract exif data $data = @exif_read_data($source, null, true); if (is_resource($source)) { fclose($source); } } catch (Exception) { $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 $input * @return object */ protected function parseDataUri(mixed $input): object { $pattern = "/^data:(?P\w+\/[-+.\w]+)?" . "(?P(;[-\w]+=[-\w]+)*)(?P;base64)?,(?P.*)/"; $result = preg_match($pattern, $input, $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 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.php000064400000002454150731645420013536 0ustar00drawable->position(); } /** * @throws RuntimeException */ public function backgroundColor(): ColorInterface { try { $color = $this->driver()->handleInput($this->drawable->backgroundColor()); } catch (DecoderException) { return $this->driver()->handleInput('transparent'); } return $color; } /** * @throws RuntimeException */ public function borderColor(): ColorInterface { try { $color = $this->driver()->handleInput($this->drawable->borderColor()); } catch (DecoderException) { return $this->driver()->handleInput('transparent'); } return $color; } } src/Drivers/AbstractDriver.php000064400000004352150731645420012414 0ustar00checkHealth(); } /** * {@inheritdoc} * * @see DriverInterface::specialize() */ public function specialize(object $object): ModifierInterface|AnalyzerInterface|EncoderInterface|DecoderInterface { if (!($object instanceof SpecializableInterface)) { return $object; } $driver_namespace = (new ReflectionClass($this))->getNamespaceName(); $class_path = substr($object::class, strlen("Intervention\\Image\\")); $classname = $driver_namespace . "\\" . $class_path; if (!class_exists($classname)) { throw new NotSupportedException( "Class '" . $class_path . "' is not supported by " . $this->id() . " driver." ); } return forward_static_call([ $classname, 'buildSpecialized' ], $object, $this); } /** * {@inheritdoc} * * @see DriverInterface::specializeMultiple() */ public function specializeMultiple(array $specializables): array { return array_map(function ($specializable) { return $this->specialize( match (true) { is_string($specializable) => new $specializable(), is_object($specializable) => $specializable, default => throw new RuntimeException( 'Specializable item must be either a class name or an object.' ) } ); }, $specializables); } } src/Drivers/AbstractFontProcessor.php000064400000012457150731645420013774 0ustar00wrapTextBlock(new TextBlock($text), $font); $pivot = $this->buildPivot($lines, $font, $position); $leading = $this->leading($font); $blockWidth = $this->boxSize((string) $lines->longestLine(), $font)->width(); $x = $pivot->x(); $y = $font->hasFilename() ? $pivot->y() + $this->capHeight($font) : $pivot->y(); $x_adjustment = 0; foreach ($lines as $line) { $line_width = $this->boxSize((string) $line, $font)->width(); $x_adjustment = $font->alignment() == 'left' ? 0 : $blockWidth - $line_width; $x_adjustment = $font->alignment() == 'right' ? intval(round($x_adjustment)) : $x_adjustment; $x_adjustment = $font->alignment() == 'center' ? intval(round($x_adjustment / 2)) : $x_adjustment; $position = new Point($x + $x_adjustment, $y); $position->rotate($font->angle(), $pivot); $line->setPosition($position); $y += $leading; } return $lines; } /** * {@inheritdoc} * * @see FontProcessorInterface::nativeFontSize() */ public function nativeFontSize(FontInterface $font): float { return $font->size(); } /** * {@inheritdoc} * * @see FontProcessorInterface::typographicalSize() */ public function typographicalSize(FontInterface $font): int { return $this->boxSize('Hy', $font)->height(); } /** * {@inheritdoc} * * @see FontProcessorInterface::capHeight() */ public function capHeight(FontInterface $font): int { return $this->boxSize('T', $font)->height(); } /** * {@inheritdoc} * * @see FontProcessorInterface::leading() */ public function leading(FontInterface $font): int { return intval(round($this->typographicalSize($font) * $font->lineHeight())); } /** * Reformat a text block by wrapping each line before the given maximum width * * @param TextBlock $block * @param FontInterface $font * @throws FontException * @return TextBlock */ protected function wrapTextBlock(TextBlock $block, FontInterface $font): TextBlock { $newLines = []; foreach ($block as $line) { foreach ($this->wrapLine($line, $font) as $newLine) { $newLines[] = $newLine; } } return $block->setLines($newLines); } /** * Check if a line exceeds the given maximum width and wrap it if necessary. * The output will be an array of formatted lines that are all within the * maximum width. * * @param Line $line * @param FontInterface $font * @throws FontException * @return array */ protected function wrapLine(Line $line, FontInterface $font): array { // no wrap width - no wrapping if (is_null($font->wrapWidth())) { return [$line]; } $wrapped = []; $formatedLine = new Line(); foreach ($line as $word) { // calculate width of newly formated line $lineWidth = $this->boxSize(match ($formatedLine->count()) { 0 => $word, default => (string) $formatedLine . ' ' . $word, }, $font)->width(); // decide if word fits on current line or a new line must be created if ($line->count() === 1 || $lineWidth <= $font->wrapWidth()) { $formatedLine->add($word); } else { if ($formatedLine->count()) { $wrapped[] = $formatedLine; } $formatedLine = new Line($word); } } $wrapped[] = $formatedLine; return $wrapped; } /** * Build pivot point of textblock according to the font settings and based on given position * * @param TextBlock $block * @param FontInterface $font * @param PointInterface $position * @throws FontException * @return PointInterface */ protected function buildPivot(TextBlock $block, FontInterface $font, PointInterface $position): PointInterface { // bounding box $box = (new Rectangle( $this->boxSize((string) $block->longestLine(), $font)->width(), $this->leading($font) * ($block->count() - 1) + $this->capHeight($font) )); // set position $box->setPivot($position); // alignment $box->align($font->alignment()); $box->valign($font->valignment()); $box->rotate($font->angle()); return $box->last(); } } src/Drivers/AbstractInputHandler.php000064400000002641150731645420013555 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 * * @throws DecoderException * @return AbstractDecoder */ protected function chain(): AbstractDecoder { if (count($this->decoders) == 0) { throw new DecoderException('No decoders found in ' . $this::class); } // 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.php000064400000001505150731645420013561 0ustar00strokeWidth() <= 0) { return $offsets; } for ($x = $font->strokeWidth() * -1; $x <= $font->strokeWidth(); $x++) { for ($y = $font->strokeWidth() * -1; $y <= $font->strokeWidth(); $y++) { $offsets[] = new Point($x, $y); } } return $offsets; } } src/Drivers/DriverSpecialized.php000064400000003117150731645420013103 0ustar00generic = $generic; $specialized->driver = $driver; return $specialized; } /** * {@inheritdoc} * * @see SpecializedInterface::driver() */ public function driver(): DriverInterface { return $this->driver; } /** * {@inheritdoc} * * @see SpecializedInterface::generic() */ public function generic(): object { return $this->generic; } /** * Magic method to read attributes of underlying generic object * * @param string $name * @return mixed */ public function __get(string $name): mixed { return $this->generic->$name; } /** * Magic method to call methods of underlying generic object * * @param string $name * @param array $arguments * @return mixed */ public function __call(string $name, array $arguments): mixed { return $this->generic->$name(...$arguments); } } src/Drivers/DriverSpecializedEncoder.php000064400000001061150731645420014377 0ustar00core()->native()); } } src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php000064400000002457150731645420015571 0ustar00colorAt( $image->colorspace(), $image->core()->frame($this->frame_key)->native() ); } /** * @throws GeometryException * @throws ColorException */ 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.php000064400000001134150731645420015743 0ustar00colorspace(); foreach ($image as $frame) { $colors->push( parent::colorAt($colorspace, $frame->native()) ); } return $colors; } } src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php000064400000000771150731645420015651 0ustar00core()->native())); } } src/Drivers/Gd/Analyzers/WidthAnalyzer.php000064400000000666150731645420014570 0ustar00core()->native()); } } src/Drivers/Gd/Cloner.php000064400000005455150731645420011256 0ustar00 new Rectangle(imagesx($gd), imagesy($gd)), default => $size, }; // create new gd image with same size or new given size $clone = imagecreatetruecolor($size->width(), $size->height()); // copy resolution to clone $resolution = imageresolution($gd); if (is_array($resolution) && array_key_exists(0, $resolution) && array_key_exists(1, $resolution)) { imageresolution($clone, $resolution[0], $resolution[1]); } // fill with background $processor = new ColorProcessor(); imagefill($clone, 0, 0, $processor->colorToNative($background)); imagealphablending($clone, true); imagesavealpha($clone, true); return $clone; } /** * Create a clone of an GdImage that is positioned on the specified background color. * Possible transparent areas are mixed with this color. * * @param GdImage $gd * @param ColorInterface $background * @throws ColorException * @return GdImage */ public static function cloneBlended(GdImage $gd, ColorInterface $background): GdImage { // create empty canvas with same size $clone = static::cloneEmpty($gd, background: $background); // transfer actual image to clone imagecopy($clone, $gd, 0, 0, 0, 0, imagesx($gd), imagesy($gd)); return $clone; } } src/Drivers/Gd/ColorProcessor.php000064400000005120150731645420012777 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.php000064400000004314150731645420010715 0ustar00push($frame); return $this; } /** * {@inheritdoc} * * @see CoreInterface::native() */ public function native(): mixed { return $this->first()->native(); } /** * {@inheritdoc} * * @see CoreInterface::setNative() */ public function setNative(mixed $native): self { $this->empty()->push(new Frame($native)); return $this; } /** * {@inheritdoc} * * @see CoreInterface::frame() */ public function frame(int $position): FrameInterface { $frame = $this->getAtPosition($position); if (!($frame instanceof FrameInterface)) { throw new AnimationException('Frame #' . $position . ' could not be found in the image.'); } return $frame; } /** * {@inheritdoc} * * @see CoreInterface::loops() */ public function loops(): int { return $this->loops; } /** * {@inheritdoc} * * @see CoreInterface::setLoops() */ public function setLoops(int $loops): self { $this->loops = $loops; return $this; } /** * {@inheritdoc} * * @see CollectionInterface::first() */ public function first(): FrameInterface { return parent::first(); } /** * {@inheritdoc} * * @see CollectionInterface::last() */ public function last(): FrameInterface { return parent::last(); } /** * Clone instance * * @return void */ public function __clone(): void { foreach ($this->items as $key => $frame) { $this->items[$key] = clone $frame; } } } src/Drivers/Gd/Decoders/AbstractDecoder.php000064400000002555150731645420014613 0ustar00isValidBase64($input)) { throw new DecoderException('Unable to decode input'); } return parent::decode(base64_decode($input)); } } src/Drivers/Gd/Decoders/BinaryImageDecoder.php000064400000003530150731645420015231 0ustar00isGifFormat($input)) { true => $this->decodeGif($input), default => $this->decodeBinary($input), }; } /** * Decode image from given binary data * * @param string $input * @throws RuntimeException * @return ImageInterface */ private function decodeBinary(string $input): ImageInterface { $gd = @imagecreatefromstring($input); if ($gd === false) { throw new DecoderException('Unable to decode input'); } // create image instance $image = parent::decode($gd); // extract & set exif data $image->setExif($this->extractExifData($input)); try { // set mediaType on origin $image->origin()->setMediaType( $this->getMediaTypeByBinary($input) ); } catch (DecoderException) { } // adjust image orientation $image->modify(new AlignRotationModifier()); return $image; } } src/Drivers/Gd/Decoders/ColorObjectDecoder.php000064400000001216150731645420015246 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.php000064400000004144150731645420015503 0ustar00isFile($input)) { throw new DecoderException('Unable to decode input'); } // detect media (mime) type $mediaType = $this->getMediaTypeByFilePath($input); $image = match ($mediaType) { // gif files might be animated and therefore cannot // be handled by the standard GD decoder. 'image/gif' => $this->decodeGif($input), default => parent::decode(match ($mediaType) { 'image/jpeg', 'image/jpg', 'image/pjpeg' => imagecreatefromjpeg($input), 'image/webp', 'image/x-webp' => imagecreatefromwebp($input), 'image/png', 'image/x-png' => imagecreatefrompng($input), 'image/avif', 'image/x-avif' => imagecreatefromavif($input), 'image/bmp', 'image/ms-bmp', 'image/x-bitmap', 'image/x-bmp', 'image/x-ms-bmp', 'image/x-win-bitmap', 'image/x-windows-bmp', 'image/x-xbitmap' => imagecreatefrombmp($input), default => throw new DecoderException('Unable to decode input'), }), }; // set file path & mediaType on origin $image->origin()->setFilePath($input); $image->origin()->setMediaType($mediaType); // extract exif $image->setExif($this->extractExifData($input)); // adjust image orientation $image->modify(new AlignRotationModifier()); return $image; } } src/Drivers/Gd/Decoders/FilePointerImageDecoder.php000064400000001337150731645420016230 0ustar00getRealPath()); } } src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php000064400000002720150731645420015255 0ustar00split(); $delays = $splitter->getDelays(); // build core $core = new Core(); // set loops on core if ($loops = $gif->getMainApplicationExtension()?->getLoops()) { $core->setLoops($loops); } // add GDImage instances to core foreach ($splitter->coalesceToResources() as $key => $native) { $core->push( (new Frame($native))->setDelay($delays[$key] / 100) ); } // create image $image = new Image(new Driver(), $core); // set media type $image->origin()->setMediaType('image/gif'); return $image; } } src/Drivers/Gd/Driver.php000064400000007023150731645420011260 0ustar00core->add( $this->driver->handleInput($source)->core()->first()->setDelay($delay) ); return $this; } /** * @throws RuntimeException */ 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, array $decoders = []): ImageInterface|ColorInterface { return (new InputHandler($this->specializeMultiple($decoders)))->handle($input); } /** * {@inheritdoc} * * @see DriverInterface::colorProcessor() */ public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface { return new ColorProcessor($colorspace); } /** * {@inheritdoc} * * @see DriverInterface::fontProcessor() */ public function fontProcessor(): FontProcessorInterface { return new FontProcessor(); } } src/Drivers/Gd/Encoders/AvifEncoder.php000064400000001142150731645420013750 0ustar00core()->native(); $data = $this->getBuffered(function () use ($gd) { imageavif($gd, null, $this->quality); }); return new EncodedImage($data, 'image/avif'); } } src/Drivers/Gd/Encoders/BmpEncoder.php000064400000001044150731645420013602 0ustar00getBuffered(function () use ($image) { imagebmp($image->core()->native(), null, false); }); return new EncodedImage($data, 'image/bmp'); } } src/Drivers/Gd/Encoders/GifEncoder.php000064400000002744150731645420013601 0ustar00isAnimated()) { return $this->encodeAnimated($image); } $gd = $image->core()->native(); $data = $this->getBuffered(function () use ($gd) { imagegif($gd); }); return new EncodedImage($data, 'image/gif'); } /** * @throws RuntimeException */ protected function encodeAnimated(ImageInterface $image): EncodedImage { $builder = GifBuilder::canvas( $image->width(), $image->height() ); foreach ($image as $frame) { $builder->addFrame( (string) $this->encode($frame->toImage($image->driver())), $frame->delay() ); } try { $builder->setLoops($image->loops()); } catch (Exception $e) { throw new EncoderException($e->getMessage(), $e->getCode(), $e); } return new EncodedImage($builder->encode(), 'image/gif'); } } src/Drivers/Gd/Encoders/JpegEncoder.php000064400000001324150731645420013752 0ustar00core()->native(), background: $image->blendingColor()); $data = $this->getBuffered(function () use ($output) { imagejpeg($output, null, $this->quality); }); return new EncodedImage($data, 'image/jpeg'); } } src/Drivers/Gd/Encoders/PngEncoder.php000064400000001041150731645420013605 0ustar00getBuffered(function () use ($image) { imagepng($image->core()->native(), null, -1); }); return new EncodedImage($data, 'image/png'); } } src/Drivers/Gd/Encoders/WebpEncoder.php000064400000001246150731645420013765 0ustar00quality === 100 ? IMG_WEBP_LOSSLESS : $this->quality; $data = $this->getBuffered(function () use ($image, $quality) { imagewebp($image->core()->native(), null, $quality); }); return new EncodedImage($data, 'image/webp'); } } src/Drivers/Gd/FontProcessor.php000064400000004627150731645420012642 0ustar00hasFilename()) { // calculate box size from gd font $box = new Rectangle(0, 0); $chars = mb_strlen($text); if ($chars > 0) { $box->setWidth( $chars * $this->gdCharacterWidth((int) $font->filename()) ); $box->setHeight( $this->gdCharacterHeight((int) $font->filename()) ); } return $box; } // calculate box size from ttf font file with angle 0 $box = imageftbbox( $this->nativeFontSize($font), 0, $font->filename(), $text ); // build size from points return new Rectangle( intval(abs($box[4] - $box[0])), intval(abs($box[5] - $box[1])) ); } /** * {@inheritdoc} * * @see FontProcessorInterface::nativeFontSize() */ public function nativeFontSize(FontInterface $font): float { return floatval(round($font->size() * .76, 6)); } /** * {@inheritdoc} * * @see FontProcessorInterface::leading() */ public function leading(FontInterface $font): int { return (int) round(parent::leading($font) * .8); } /** * Return width of a single character * * @param int $gdfont * @return int */ protected function gdCharacterWidth(int $gdfont): int { return $gdfont + 4; } /** * Return height of a single character * * @param int $gdfont * @return int */ protected function gdCharacterHeight(int $gdfont): int { return match ($gdfont) { 2, 3 => 14, 4, 5 => 16, default => 8, }; } } src/Drivers/Gd/Frame.php000064400000007245150731645420011065 0ustar00native = $native; return $this; } /** * {@inheritdoc} * * @see FrameInterface::native() */ public function native(): GdImage { return $this->native; } /** * {@inheritdoc} * * @see FrameInterface::size() */ public function size(): SizeInterface { return new Rectangle(imagesx($this->native), imagesy($this->native)); } /** * {@inheritdoc} * * @see FrameInterface::delay() */ public function delay(): float { return $this->delay; } /** * {@inheritdoc} * * @see FrameInterface::setDelay() */ public function setDelay(float $delay): FrameInterface { $this->delay = $delay; return $this; } /** * {@inheritdoc} * * @see FrameInterface::dispose() */ public function dispose(): int { return $this->dispose; } /** * {@inheritdoc} * * @see FrameInterface::setDispose() */ public function setDispose(int $dispose): FrameInterface { $this->dispose = $dispose; return $this; } /** * {@inheritdoc} * * @see FrameInterface::setOffset() */ public function setOffset(int $left, int $top): FrameInterface { $this->offset_left = $left; $this->offset_top = $top; return $this; } /** * {@inheritdoc} * * @see FrameInterface::offsetLeft() */ public function offsetLeft(): int { return $this->offset_left; } /** * {@inheritdoc} * * @see FrameInterface::setOffsetLeft() */ public function setOffsetLeft(int $offset): FrameInterface { $this->offset_left = $offset; return $this; } /** * {@inheritdoc} * * @see FrameInterface::offsetTop() */ public function offsetTop(): int { return $this->offset_top; } /** * {@inheritdoc} * * @see FrameInterface::setOffsetTop() */ public function setOffsetTop(int $offset): FrameInterface { $this->offset_top = $offset; return $this; } /** * This workaround helps cloning GdImages which is currently not possible. * * @throws ColorException * @return void */ public function __clone(): void { $this->native = Cloner::clone($this->native); } } src/Drivers/Gd/InputHandler.php000064400000003512150731645420012421 0ustar00exif('IFD0.Orientation')) { 2 => $image->flop(), 3 => $image->rotate(180), 4 => $image->rotate(180)->flop(), 5 => $image->rotate(270)->flop(), 6 => $image->rotate(270), 7 => $image->rotate(90)->flop(), 8 => $image->rotate(90), default => $image }; } } src/Drivers/Gd/Modifiers/BlendTransparencyModifier.php000064400000001730150731645420017042 0ustar00driver()->handleInput( $this->color ? $this->color : $image->blendingColor() ); foreach ($image as $frame) { // create new canvas with blending color as background $modified = Cloner::cloneBlended( $frame->native(), background: $color ); // set new gd image $frame->setNative($modified); } return $image; } } src/Drivers/Gd/Modifiers/BlurModifier.php000064400000001176150731645420014334 0ustar00amount; $i++) { imagefilter($frame->native(), IMG_FILTER_GAUSSIAN_BLUR); } } return $image; } } src/Drivers/Gd/Modifiers/BrightnessModifier.php000064400000001126150731645420015533 0ustar00native(), IMG_FILTER_BRIGHTNESS, intval($this->level * 2.55)); } return $image; } } src/Drivers/Gd/Modifiers/ColorizeModifier.php000064400000001460150731645420015212 0ustar00red * 2.55); $green = (int) round($this->green * 2.55); $blue = (int) 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.php000064400000001447150731645420015523 0ustar00targetColorspace(), RgbColorspace::class)) { throw new NotSupportedException( 'Only RGB colorspace is supported by GD driver.' ); } return $image; } } src/Drivers/Gd/Modifiers/ContainModifier.php000064400000005742150731645420015026 0ustar00getCropSize($image); $resize = $this->getResizeSize($image); $background = $this->driver()->handleInput($this->background); $blendingColor = $image->blendingColor(); foreach ($image as $frame) { $this->modify($frame, $crop, $resize, $background, $blendingColor); } return $image; } /** * @throws ColorException */ protected function modify( FrameInterface $frame, SizeInterface $crop, SizeInterface $resize, ColorInterface $background, ColorInterface $blendingColor ): void { // create new gd image $modified = Cloner::cloneEmpty($frame->native(), $resize, $background); // make image area transparent to keep transparency // even if background-color is set $transparent = imagecolorallocatealpha( $modified, $blendingColor->channel(Red::class)->value(), $blendingColor->channel(Green::class)->value(), $blendingColor->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.php000064400000001112150731645420015213 0ustar00native(), IMG_FILTER_CONTRAST, ($this->level * -1)); } return $image; } } src/Drivers/Gd/Modifiers/CoverDownModifier.php000064400000000750150731645420015333 0ustar00scaleDown($this->width, $this->height); } } src/Drivers/Gd/Modifiers/CoverModifier.php000064400000003121150731645420014476 0ustar00getCropSize($image); $resize = $this->getResizeSize($crop); foreach ($image as $frame) { $this->modifyFrame($frame, $crop, $resize); } return $image; } /** * @throws ColorException */ protected function modifyFrame(FrameInterface $frame, SizeInterface $crop, SizeInterface $resize): void { // create new image $modified = Cloner::cloneEmpty($frame->native(), $resize); // copy content from resource imagecopyresampled( $modified, $frame->native(), 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.php000064400000010535150731645420014332 0ustar00size(); $crop = $this->crop($image); $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->background) ); foreach ($image as $frame) { $this->cropFrame($frame, $originalSize, $crop, $background); } return $image; } /** * @throws ColorException */ protected function cropFrame( FrameInterface $frame, SizeInterface $originalSize, SizeInterface $resizeTo, int $background ): void { // create new image with transparent background $modified = Cloner::cloneEmpty($frame->native(), $resizeTo); // define offset $offset_x = $resizeTo->pivot()->x() + $this->offset_x; $offset_y = $resizeTo->pivot()->y() + $this->offset_y; // define target width & height $targetWidth = min($resizeTo->width(), $originalSize->width()); $targetHeight = min($resizeTo->height(), $originalSize->height()); $targetWidth = $targetWidth < $originalSize->width() ? $targetWidth + $offset_x : $targetWidth; $targetHeight = $targetHeight < $originalSize->height() ? $targetHeight + $offset_y : $targetHeight; // copy content from resource imagecopyresampled( $modified, $frame->native(), $offset_x * -1, $offset_y * -1, 0, 0, $targetWidth, $targetHeight, $targetWidth, $targetHeight ); // don't alpha blend for covering areas imagealphablending($modified, false); // cover the possible newly created areas with background color if ($resizeTo->width() > $originalSize->width() || $this->offset_x > 0) { imagefilledrectangle( $modified, $originalSize->width() + ($this->offset_x * -1) - $resizeTo->pivot()->x(), 0, $resizeTo->width(), $resizeTo->height(), $background ); } // cover the possible newly created areas with background color if ($resizeTo->height() > $originalSize->height() || $this->offset_y > 0) { imagefilledrectangle( $modified, ($this->offset_x * -1) - $resizeTo->pivot()->x(), $originalSize->height() + ($this->offset_y * -1) - $resizeTo->pivot()->y(), ($this->offset_x * -1) + $originalSize->width() - 1 - $resizeTo->pivot()->x(), $resizeTo->height(), $background ); } // cover the possible newly created areas with background color if ((($this->offset_x * -1) - $resizeTo->pivot()->x() - 1) > 0) { imagefilledrectangle( $modified, 0, 0, ($this->offset_x * -1) - $resizeTo->pivot()->x() - 1, $resizeTo->height(), $background ); } // cover the possible newly created areas with background color if ((($this->offset_y * -1) - $resizeTo->pivot()->y() - 1) > 0) { imagefilledrectangle( $modified, ($this->offset_x * -1) - $resizeTo->pivot()->x(), 0, ($this->offset_x * -1) + $originalSize->width() - $resizeTo->pivot()->x() - 1, ($this->offset_y * -1) - $resizeTo->pivot()->y() - 1, $background ); } // set new content as recource $frame->setNative($modified); } } src/Drivers/Gd/Modifiers/DrawEllipseModifier.php000064400000004676150731645420015653 0ustar00drawable->hasBorder()) { imagealphablending($frame->native(), true); // 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 { imagealphablending($frame->native(), true); 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.php000064400000002175150731645420015135 0ustar00native(), true); imageantialias($frame->native(), true); imagesetthickness($frame->native(), $this->drawable->width()); 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.php000064400000001721150731645420015323 0ustar00driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->color) ); foreach ($image as $frame) { imagealphablending($frame->native(), true); imagesetpixel( $frame->native(), $this->position->x(), $this->position->y(), $color ); } return $image; } } src/Drivers/Gd/Modifiers/DrawPolygonModifier.php000064400000003042150731645420015667 0ustar00drawable->hasBackgroundColor()) { imagealphablending($frame->native(), true); imagefilledpolygon( $frame->native(), $this->drawable->toArray(), $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->backgroundColor() ) ); } if ($this->drawable->hasBorder()) { imagealphablending($frame->native(), true); 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.php000064400000003773150731645420016157 0ustar00drawable->hasBackgroundColor()) { imagealphablending($frame->native(), true); 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()) { imagealphablending($frame->native(), true); 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.php000064400000003343150731645420014314 0ustar00color($image); foreach ($image as $frame) { if ($this->hasPosition()) { $this->floodFillWithColor($frame, $color); } else { $this->fillAllWithColor($frame, $color); } } return $image; } /** * @throws RuntimeException */ 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.php000064400000001015150731645420014312 0ustar00native(), IMG_FLIP_VERTICAL); } return $image; } } src/Drivers/Gd/Modifiers/FlopModifier.php000064400000001017150731645420014322 0ustar00native(), IMG_FLIP_HORIZONTAL); } return $image; } } src/Drivers/Gd/Modifiers/GammaModifier.php000064400000001066150731645420014450 0ustar00native(), 1, $this->gamma); } return $image; } } src/Drivers/Gd/Modifiers/GreyscaleModifier.php000064400000001027150731645420015341 0ustar00native(), IMG_FILTER_GRAYSCALE); } return $image; } } src/Drivers/Gd/Modifiers/InvertModifier.php000064400000001021150731645420014664 0ustar00native(), IMG_FILTER_NEGATE); } return $image; } } src/Drivers/Gd/Modifiers/PadModifier.php000064400000000203150731645420014122 0ustar00native(), IMG_FILTER_PIXELATE, $this->size, true); } return $image; } } src/Drivers/Gd/Modifiers/PlaceModifier.php000064400000007125150731645420014454 0ustar00driver()->handleInput($this->element); $position = $this->getPosition($image, $watermark); foreach ($image as $frame) { imagealphablending($frame->native(), true); if ($this->opacity === 100) { $this->placeOpaque($frame, $watermark, $position); } else { $this->placeTransparent($frame, $watermark, $position); } } return $image; } /** * Insert watermark with 100% opacity * * @param FrameInterface $frame * @param ImageInterface $watermark * @param PointInterface $position * @throws RuntimeException * @return void */ private function placeOpaque(FrameInterface $frame, ImageInterface $watermark, PointInterface $position): void { imagecopy( $frame->native(), $watermark->core()->native(), $position->x(), $position->y(), 0, 0, $watermark->width(), $watermark->height() ); } /** * Insert watermark transparent with current opacity * * Unfortunately, the original PHP function imagecopymerge does not work reliably. * For example, any transparency of the image to be inserted is not applied correctly. * For this reason, a new GDImage is created into which the original image is inserted * in the first step and the watermark is inserted with 100% opacity in the second * step. This combination is then transferred to the original image again with the * respective opacity. * * Please note: Unfortunately, there is still an edge case, when a transparent image * is placed on a transparent background, the "double" transparent areas appear opaque! * * @param FrameInterface $frame * @param ImageInterface $watermark * @param PointInterface $position * @throws RuntimeException * @return void */ private function placeTransparent(FrameInterface $frame, ImageInterface $watermark, PointInterface $position): void { $cut = imagecreatetruecolor($watermark->width(), $watermark->height()); imagecopy( $cut, $frame->native(), 0, 0, $position->x(), $position->y(), imagesx($cut), imagesy($cut) ); imagecopy( $cut, $watermark->core()->native(), 0, 0, 0, 0, imagesx($cut), imagesy($cut) ); imagecopymerge( $frame->native(), $cut, $position->x(), $position->y(), 0, 0, $watermark->width(), $watermark->height(), $this->opacity ); } } src/Drivers/Gd/Modifiers/ProfileModifier.php000064400000001232150731645420015021 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 = Cloner::cloneEmpty($frame->native(), background: $image->blendingColor()); // 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.php000064400000001221150731645420016514 0ustar00core()->setNative( $this->chosenFrame($image, $this->position)->native() ); return $image; } } src/Drivers/Gd/Modifiers/ResizeCanvasModifier.php000064400000005414150731645420016024 0ustar00cropSize($image); $background = $this->driver()->handleInput($this->background); foreach ($image as $frame) { $this->modify($frame, $resize, $background); } return $image; } /** * @throws ColorException */ protected function modify( FrameInterface $frame, SizeInterface $resize, ColorInterface $background, ): void { // create new canvas with target size & target background color $modified = Cloner::cloneEmpty($frame->native(), $resize, $background); // 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.php000064400000000535150731645420017517 0ustar00size()->resizeDown($this->width, $this->height); } } src/Drivers/Gd/Modifiers/ResizeModifier.php000064400000003407150731645420014670 0ustar00getAdjustedSize($image); foreach ($image as $frame) { $this->resizeFrame($frame, $resizeTo); } return $image; } /** * @throws ColorException */ private function resizeFrame(FrameInterface $frame, SizeInterface $resizeTo): void { // create empty canvas in target size $modified = Cloner::cloneEmpty($frame->native(), $resizeTo); // copy content from resource imagecopyresampled( $modified, $frame->native(), $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); } /** * @throws RuntimeException */ protected function getAdjustedSize(ImageInterface $image): SizeInterface { return $image->size()->resize($this->width, $this->height); } } src/Drivers/Gd/Modifiers/ResolutionModifier.php000064400000001213150731645420015563 0ustar00x)); $y = intval(round($this->y)); foreach ($image as $frame) { imageresolution($frame->native(), $x, $y); } return $image; } } src/Drivers/Gd/Modifiers/RotateModifier.php000064400000006421150731645420014664 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 * @throws ColorException * @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 = Cloner::cloneEmpty($frame->native(), $container, $background); // 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.php000064400000000705150731645420015304 0ustar00size()->scaleDown($this->width, $this->height); } } src/Drivers/Gd/Modifiers/ScaleModifier.php000064400000000675150731645420014462 0ustar00size()->scale($this->width, $this->height); } } src/Drivers/Gd/Modifiers/SharpenModifier.php000064400000001635150731645420015030 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/SliceAnimationModifier.php000064400000001354150731645420016325 0ustar00offset >= $image->count()) { throw new AnimationException('Offset is not in the range of frames.'); } $image->core()->slice($this->offset, $this->length); return $image; } } src/Drivers/Gd/Modifiers/TextModifier.php000064400000011756150731645420014361 0ustar00driver()->fontProcessor(); $lines = $fontProcessor->textBlock($this->text, $this->font, $this->position); // decode text colors $textColor = $this->textColor($image); $strokeColor = $this->strokeColor($image); foreach ($image as $frame) { imagealphablending($frame->native(), true); if ($this->font->hasFilename()) { foreach ($lines as $line) { foreach ($this->strokeOffsets($this->font) as $offset) { imagettftext( $frame->native(), $fontProcessor->nativeFontSize($this->font), $this->font->angle() * -1, $line->position()->x() + $offset->x(), $line->position()->y() + $offset->y(), $strokeColor, $this->font->filename(), (string) $line ); } imagettftext( $frame->native(), $fontProcessor->nativeFontSize($this->font), $this->font->angle() * -1, $line->position()->x(), $line->position()->y(), $textColor, $this->font->filename(), (string) $line ); } } else { foreach ($lines as $line) { foreach ($this->strokeOffsets($this->font) as $offset) { imagestring( $frame->native(), $this->gdFont(), $line->position()->x() + $offset->x(), $line->position()->y() + $offset->y(), (string) $line, $strokeColor ); } imagestring( $frame->native(), $this->gdFont(), $line->position()->x(), $line->position()->y(), (string) $line, $textColor ); } } } return $image; } /** * Decode text color * * The text outline effect is drawn with a trick by plotting additional text * under the actual text with an offset in the color of the outline effect. * For this reason, no colors with transparency can be used for the text * color or the color of the stroke effect, as this would be superimposed. * * @param ImageInterface $image * @throws RuntimeException * @throws ColorException * @return int */ protected function textColor(ImageInterface $image): int { $color = $this->driver()->handleInput($this->font->color()); if ($this->font->hasStrokeEffect() && $color->isTransparent()) { throw new ColorException( 'The text color must be fully opaque when using the stroke effect.' ); } return $this->driver()->colorProcessor($image->colorspace())->colorToNative($color); } /** * Decode outline stroke color * * @param ImageInterface $image * @throws RuntimeException * @throws ColorException * @return int */ protected function strokeColor(ImageInterface $image): int { if (!$this->font->hasStrokeEffect()) { return 0; } $color = $this->driver()->handleInput($this->font->strokeColor()); if ($color->isTransparent()) { throw new ColorException( 'The stroke color must be fully opaque.' ); } return $this->driver()->colorProcessor($image->colorspace())->colorToNative($color); } /** * Return GD's internal font size (if no ttf file is set) * * @return int */ private function gdFont(): int { if (is_numeric($this->font->filename())) { return intval($this->font->filename()); } return 1; } } src/Drivers/Imagick/Analyzers/ColorspaceAnalyzer.php000064400000001330150731645420016602 0ustar00core()->native()->getImageColorspace()) { Imagick::COLORSPACE_CMYK => new CmykColorspace(), default => new RgbColorspace(), }; } } src/Drivers/Imagick/Analyzers/HeightAnalyzer.php000064400000000705150731645420015725 0ustar00core()->native()->getImageHeight(); } } src/Drivers/Imagick/Analyzers/PixelColorAnalyzer.php000064400000002137150731645420016576 0ustar00colorAt( $image->colorspace(), $image->core()->frame($this->frame_key)->native() ); } /** * @throws ColorException */ 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.php000064400000001141150731645420016753 0ustar00colorspace(); foreach ($image as $frame) { $colors->push( parent::colorAt($colorspace, $frame->native()) ); } return $colors; } } src/Drivers/Imagick/Analyzers/ProfileAnalyzer.php000064400000001340150731645420016111 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.php000064400000001003150731645420016650 0ustar00core()->native()->getImageResolution()); } } src/Drivers/Imagick/Analyzers/WidthAnalyzer.php000064400000000703150731645420015572 0ustar00core()->native()->getImageWidth(); } } src/Drivers/Imagick/ColorProcessor.php000064400000002712150731645420014015 0ustar00convertTo($this->colorspace) ); } public function nativeToColor(mixed $native): ColorInterface { return match ($this->colorspace::class) { 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.php000064400000014610150731645420011727 0ustar00imagick->setIteratorIndex($key); } catch (ImagickException) { return false; } return $result; } /** * {@inheritdoc} * * @see CollectionInterface::push() */ public function push($item): CollectionInterface { return $this->add($item); } /** * {@inheritdoc} * * @see CollectionInterface::get() */ public function get(int|string $key, $default = null): mixed { try { $this->imagick->setIteratorIndex($key); } catch (ImagickException) { return $default; } return new Frame($this->imagick->current()); } /** * {@inheritdoc} * * @see CollectionInterface::getAtPosition() */ public function getAtPosition(int $key = 0, $default = null): mixed { return $this->get($key, $default); } /** * {@inheritdoc} * * @see CollectionInterface::empty() */ public function empty(): CollectionInterface { $this->imagick->clear(); return $this; } /** * {@inheritdoc} * * @see CollectionInterface::slice() */ public function slice(int $offset, ?int $length = null): CollectionInterface { $allowed_indexes = []; $length = is_null($length) ? $this->count() : $length; for ($i = $offset; $i < $offset + $length; $i++) { $allowed_indexes[] = $i; } $sliced = new Imagick(); foreach ($this->imagick as $key => $native) { if (in_array($key, $allowed_indexes)) { $sliced->addImage($native->getImage()); } } $sliced = $sliced->coalesceImages(); $sliced->setImageIterations($this->imagick->getImageIterations()); $this->imagick = $sliced; return $this; } /** * {@inheritdoc} * * @see CoreInterface::add() */ public function add(FrameInterface $frame): CoreInterface { $imagick = $frame->native(); $imagick->setImageDelay( (int) round($frame->delay() * 100) ); $imagick->setImageDispose($frame->dispose()); $size = $frame->size(); $imagick->setImagePage( $size->width(), $size->height(), $frame->offsetLeft(), $frame->offsetTop() ); $this->imagick->addImage($imagick); return $this; } /** * {@inheritdoc} * * @see CoreInterface::count() */ public function count(): int { return $this->imagick->getNumberImages(); } /** * {@inheritdoc} * * @see Interator::rewind() */ public function current(): mixed { $this->imagick->setIteratorIndex($this->iteratorIndex); return new Frame($this->imagick->current()); } /** * {@inheritdoc} * * @see Interator::rewind() */ public function next(): void { $this->iteratorIndex = $this->iteratorIndex + 1; } /** * {@inheritdoc} * * @see Interator::rewind() */ public function key(): mixed { return $this->iteratorIndex; } /** * {@inheritdoc} * * @see Interator::rewind() */ public function valid(): bool { try { $result = $this->imagick->setIteratorIndex($this->iteratorIndex); } catch (ImagickException) { return false; } return $result; } /** * {@inheritdoc} * * @see Interator::rewind() */ public function rewind(): void { $this->iteratorIndex = 0; } /** * {@inheritdoc} * * @see CoreInterface::native() */ public function native(): mixed { return $this->imagick; } /** * {@inheritdoc} * * @see CoreInterface::setNative() */ public function setNative(mixed $native): CoreInterface { $this->imagick = $native; return $this; } /** * {@inheritdoc} * * @see CoreInterface::frame() */ public function frame(int $position): FrameInterface { foreach ($this->imagick as $core) { if ($core->getIteratorIndex() == $position) { return new Frame($core); } } throw new AnimationException('Frame #' . $position . ' could not be found in the image.'); } /** * {@inheritdoc} * * @see CoreInterface::loops() */ public function loops(): int { return $this->imagick->getImageIterations(); } /** * {@inheritdoc} * * @see CoreInterface::setLoops() */ public function setLoops(int $loops): CoreInterface { $this->imagick = $this->imagick->coalesceImages(); $this->imagick->setImageIterations($loops); return $this; } /** * {@inheritdoc} * * @see CollectionInterface::first() */ public function first(): FrameInterface { return $this->frame(0); } /** * {@inheritdoc} * * @see CollectableInterface::last() */ public function last(): FrameInterface { return $this->frame($this->count() - 1); } /** * {@inheritdoc} * * @see CollectionInterface::toArray() */ public function toArray(): array { $frames = []; foreach ($this as $frame) { $frames[] = $frame; } return $frames; } /** * Clone instance * * @return void */ public function __clone(): void { $this->imagick = clone $this->imagick; } } src/Drivers/Imagick/Decoders/Base64ImageDecoder.php000064400000001176150731645420016047 0ustar00isValidBase64($input)) { throw new DecoderException('Unable to decode input'); } return parent::decode(base64_decode($input)); } } src/Drivers/Imagick/Decoders/BinaryImageDecoder.php000064400000001746150731645420016252 0ustar00readImageBlob($input); } catch (ImagickException) { throw new DecoderException('Unable to decode input'); } // decode image $image = parent::decode($imagick); // extract exif data $image->setExif($this->extractExifData($input)); return $image; } } src/Drivers/Imagick/Decoders/ColorObjectDecoder.php000064400000001223150731645420016256 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.php000064400000002073150731645420016514 0ustar00isFile($input)) { throw new DecoderException('Unable to decode input'); } try { $imagick = new Imagick(); $imagick->readImage($input); } catch (ImagickException) { throw new DecoderException('Unable to decode input'); } // decode image $image = parent::decode($imagick); // set file path on origin $image->origin()->setFilePath($input); // extract exif data $image->setExif($this->extractExifData($input)); return $image; } } src/Drivers/Imagick/Decoders/FilePointerImageDecoder.php000064400000001344150731645420017240 0ustar00getImageFormat() != 'JPEG') { $input = $input->coalesceImages(); } $image = new Image( new Driver(), new Core($input) ); // adjust image rotatation $image->modify(new AlignRotationModifier()); // set media type on origin $image->origin()->setMediaType($input->getImageMimeType()); return $image; } } src/Drivers/Imagick/Decoders/SplFileInfoImageDecoder.php000064400000001232150731645420017166 0ustar00getRealPath()); } } src/Drivers/Imagick/Driver.php000064400000007353150731645420012300 0ustar00newImage($width, $height, $background, 'png'); $imagick->setType(Imagick::IMGTYPE_UNDEFINED); $imagick->setImageType(Imagick::IMGTYPE_UNDEFINED); $imagick->setColorspace(Imagick::COLORSPACE_SRGB); $imagick->setImageResolution(96, 96); $imagick->setImageBackgroundColor($background); 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 ) { } /** * @throws RuntimeException */ public function add($source, float $delay = 1): self { $native = $this->driver->handleInput($source)->core()->native(); $native->setImageDelay(intval(round($delay * 100))); $this->imagick->addImage($native); return $this; } /** * @throws RuntimeException */ 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, array $decoders = []): ImageInterface|ColorInterface { return (new InputHandler($this->specializeMultiple($decoders)))->handle($input); } /** * {@inheritdoc} * * @see DriverInterface::colorProcessor() */ public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface { return new ColorProcessor($colorspace); } /** * {@inheritdoc} * * @see DriverInterface::fontProcessor() */ public function fontProcessor(): FontProcessorInterface { return new FontProcessor(); } } src/Drivers/Imagick/Encoders/AvifEncoder.php000064400000001615150731645420014767 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.php000064400000001360150731645420014615 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.php000064400000001362150731645420014606 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/HeicEncoder.php000064400000001455150731645420014754 0ustar00core()->native(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompressionQuality($this->quality); $imagick->setImageCompressionQuality($this->quality); return new EncodedImage($imagick->getImagesBlob(), 'image/heic'); } } src/Drivers/Imagick/Encoders/Jpeg2000Encoder.php000064400000002065150731645420015271 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/jp2'); } } src/Drivers/Imagick/Encoders/JpegEncoder.php000064400000002675150731645420014776 0ustar00driver() ->colorProcessor($image->colorspace()) ->colorToNative($image->blendingColor()); // set alpha value to 1 because Imagick renders // possible full transparent colors as black $background->setColorValue(Imagick::COLOR_ALPHA, 1); $imagick = $image->core()->native(); $imagick->setImageBackgroundColor($background); $imagick->setBackgroundColor($background); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); $imagick->setCompressionQuality($this->quality); $imagick->setImageCompressionQuality($this->quality); $imagick->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE); return new EncodedImage($imagick->getImagesBlob(), 'image/jpeg'); } } src/Drivers/Imagick/Encoders/PngEncoder.php000064400000001361150731645420014624 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/TiffEncoder.php000064400000001667150731645420015001 0ustar00core()->native(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($imagick->getImageCompression()); $imagick->setImageCompression($imagick->getImageCompression()); $imagick->setCompressionQuality($this->quality); $imagick->setImageCompressionQuality($this->quality); return new EncodedImage($imagick->getImagesBlob(), 'image/tiff'); } } src/Drivers/Imagick/Encoders/WebpEncoder.php000064400000001776150731645420015007 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/FontProcessor.php000064400000004021150731645420013640 0ustar00toImagickDraw($font); $dimensions = (new Imagick())->queryFontMetrics($draw, $text); return new Rectangle( intval(round($dimensions['textWidth'])), intval(round($dimensions['ascender'] + $dimensions['descender'])), ); } /** * Imagick::annotateImage() needs an ImagickDraw object - this method takes * the font object as the base and adds an optional passed color to the new * ImagickDraw object. * * @param FontInterface $font * @param null|ImagickPixel $color * @throws FontException * @throws ImagickDrawException * @throws ImagickException * @return ImagickDraw */ public function toImagickDraw(FontInterface $font, ?ImagickPixel $color = null): ImagickDraw { if (!$font->hasFilename()) { throw new FontException('No font file specified.'); } $draw = new ImagickDraw(); $draw->setStrokeAntialias(true); $draw->setTextAntialias(true); $draw->setFont($font->filename()); $draw->setFontSize($this->nativeFontSize($font)); $draw->setTextAlignment(Imagick::ALIGN_LEFT); if ($color) { $draw->setFillColor($color); } return $draw; } } src/Drivers/Imagick/Frame.php000064400000006765150731645420012105 0ustar00native->setImageBackgroundColor($background); $this->native->setBackgroundColor($background); } /** * {@inheritdoc} * * @see DriverInterface::toImage() */ public function toImage(DriverInterface $driver): ImageInterface { return new Image($driver, new Core($this->native())); } /** * {@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.php000064400000003567150731645420013445 0ustar00core()->native()->getImageOrientation()) { case Imagick::ORIENTATION_TOPRIGHT: // 2 $image->core()->native()->flopImage(); break; case Imagick::ORIENTATION_BOTTOMRIGHT: // 3 $image->core()->native()->rotateimage("#000", 180); break; case Imagick::ORIENTATION_BOTTOMLEFT: // 4 $image->core()->native()->rotateimage("#000", 180); $image->core()->native()->flopImage(); break; case Imagick::ORIENTATION_LEFTTOP: // 5 $image->core()->native()->rotateimage("#000", -270); $image->core()->native()->flopImage(); break; case Imagick::ORIENTATION_RIGHTTOP: // 6 $image->core()->native()->rotateimage("#000", -270); break; case Imagick::ORIENTATION_RIGHTBOTTOM: // 7 $image->core()->native()->rotateimage("#000", -90); $image->core()->native()->flopImage(); break; case Imagick::ORIENTATION_LEFTBOTTOM: // 8 $image->core()->native()->rotateimage("#000", -90); break; } // set new orientation in image $image->core()->native()->setImageOrientation(Imagick::ORIENTATION_TOPLEFT); return $image; } } src/Drivers/Imagick/Modifiers/BlendTransparencyModifier.php000064400000002155150731645420020056 0ustar00driver()->handleInput( $this->color ? $this->color : $image->blendingColor() ); // get imagickpixel from color $pixel = $this->driver() ->colorProcessor($image->colorspace()) ->colorToNative($color); // merge transparent areas with the background color foreach ($image as $frame) { $frame->native()->setImageBackgroundColor($pixel); $frame->native()->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE); $frame->native()->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN); } return $image; } } src/Drivers/Imagick/Modifiers/BlurModifier.php000064400000001110150731645420015332 0ustar00native()->blurImage(1 * $this->amount, 0.5 * $this->amount); } return $image; } } src/Drivers/Imagick/Modifiers/BrightnessModifier.php000064400000001107150731645420016544 0ustar00native()->modulateImage(100 + $this->level, 100, 100); } return $image; } } src/Drivers/Imagick/Modifiers/ColorizeModifier.php000064400000002343150731645420016225 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.php000064400000002666150731645420016541 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; } /** * @throws NotSupportedException */ private function getImagickColorspace(ColorspaceInterface $colorspace): int { if (!array_key_exists($colorspace::class, self::$mapping)) { throw new NotSupportedException('Given colorspace is not supported.'); } return self::$mapping[$colorspace::class]; } } src/Drivers/Imagick/Modifiers/ContainModifier.php000064400000006024150731645420016032 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.php000064400000001134150731645420016231 0ustar00native()->sigmoidalContrastImage($this->level > 0, abs($this->level / 4), 0); } return $image; } } src/Drivers/Imagick/Modifiers/CoverDownModifier.php000064400000000755150731645420016352 0ustar00scaleDown($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/CoverModifier.php000064400000002046150731645420015515 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.php000064400000006621150731645420015345 0ustar00size(); $crop = $this->crop($image); $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->background) ); $transparent = new ImagickPixel('transparent'); $draw = new ImagickDraw(); $draw->setFillColor($background); foreach ($image as $frame) { $frame->native()->setBackgroundColor($transparent); $frame->native()->setImageBackgroundColor($transparent); // crop image $frame->native()->extentImage( $crop->width(), $crop->height(), $crop->pivot()->x() + $this->offset_x, $crop->pivot()->y() + $this->offset_y ); // repage $frame->native()->setImagePage( $crop->width(), $crop->height(), 0, 0, ); // cover the possible newly created areas with background color if ($crop->width() > $originalSize->width() || $this->offset_x > 0) { $draw->rectangle( $originalSize->width() + ($this->offset_x * -1) - $crop->pivot()->x(), 0, $crop->width(), $crop->height() ); } // cover the possible newly created areas with background color if ($crop->height() > $originalSize->height() || $this->offset_y > 0) { $draw->rectangle( ($this->offset_x * -1) - $crop->pivot()->x(), $originalSize->height() + ($this->offset_y * -1) - $crop->pivot()->y(), ($this->offset_x * -1) + $originalSize->width() - 1 - $crop->pivot()->x(), $crop->height() ); } // cover the possible newly created areas with background color if ((($this->offset_x * -1) - $crop->pivot()->x() - 1) > 0) { $draw->rectangle( 0, 0, ($this->offset_x * -1) - $crop->pivot()->x() - 1, $crop->height() ); } // cover the possible newly created areas with background color if ((($this->offset_y * -1) - $crop->pivot()->y() - 1) > 0) { $draw->rectangle( ($this->offset_x * -1) - $crop->pivot()->x(), 0, ($this->offset_x * -1) + $originalSize->width() - $crop->pivot()->x() - 1, ($this->offset_y * -1) - $crop->pivot()->y() - 1, ); } $frame->native()->drawImage($draw); } return $image; } } src/Drivers/Imagick/Modifiers/DrawEllipseModifier.php000064400000002551150731645420016653 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.php000064400000002137150731645420016145 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.php000064400000001677150731645420016347 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.php000064400000003124150731645420016702 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.php000064400000003047150731645420017163 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.php000064400000003565150731645420015334 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.php000064400000001001150731645420015317 0ustar00native()->flipImage(); } return $image; } } src/Drivers/Imagick/Modifiers/FlopModifier.php000064400000001001150731645420015325 0ustar00native()->flopImage(); } return $image; } } src/Drivers/Imagick/Modifiers/GammaModifier.php000064400000001061150731645420015455 0ustar00native()->gammaImage($this->gamma); } return $image; } } src/Drivers/Imagick/Modifiers/GreyscaleModifier.php000064400000001025150731645420016351 0ustar00native()->modulateImage(100, 0, 100); } return $image; } } src/Drivers/Imagick/Modifiers/InvertModifier.php000064400000001012150731645420015676 0ustar00native()->negateImage(false); } return $image; } } src/Drivers/Imagick/Modifiers/PadModifier.php000064400000000210150731645420015132 0ustar00pixelateFrame($frame); } return $image; } protected function pixelateFrame(Frame $frame): void { $size = $frame->size(); $frame->native()->scaleImage( (int) round(max(1, $size->width() / $this->size)), (int) round(max(1, $size->height() / $this->size)) ); $frame->native()->scaleImage($size->width(), $size->height()); } } src/Drivers/Imagick/Modifiers/PlaceModifier.php000064400000002512150731645420015461 0ustar00driver()->handleInput($this->element); $position = $this->getPosition($image, $watermark); // set opacity of watermark if ($this->opacity < 100) { $watermark->core()->native()->evaluateImage( Imagick::EVALUATE_DIVIDE, $this->opacity > 0 ? 100 / $this->opacity : 1000, Imagick::CHANNEL_ALPHA, ); } foreach ($image as $frame) { $frame->native()->compositeImage( $watermark->core()->native(), Imagick::COMPOSITE_DEFAULT, $position->x(), $position->y() ); } return $image; } } src/Drivers/Imagick/Modifiers/ProfileModifier.php000064400000001457150731645420016044 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.php000064400000001304150731645420017361 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.php000064400000002142150731645420017416 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.php000064400000001521150731645420017531 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.php000064400000005053150731645420017035 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.php000064400000000542150731645420020527 0ustar00size()->resizeDown($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/ResizeModifier.php000064400000002004150731645420015672 0ustar00getAdjustedSize($image); foreach ($image as $frame) { $frame->native()->scaleImage( $resizeTo->width(), $resizeTo->height() ); } return $image; } /** * @throws RuntimeException */ protected function getAdjustedSize(ImageInterface $image): SizeInterface { return $image->size()->resize($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/ResolutionModifier.php000064400000001104150731645420016574 0ustar00core()->native(); $imagick->setImageResolution($this->x, $this->y); return $image; } } src/Drivers/Imagick/Modifiers/RotateModifier.php000064400000001507150731645420015676 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.php000064400000000712150731645420016314 0ustar00size()->scaleDown($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/ScaleModifier.php000064400000000702150731645420015463 0ustar00size()->scale($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/SharpenModifier.php000064400000001111150731645420016027 0ustar00native()->unsharpMaskImage(1, 1, $this->amount / 6.25, 0); } return $image; } } src/Drivers/Imagick/Modifiers/SliceAnimationModifier.php000064400000001361150731645420017335 0ustar00offset >= $image->count()) { throw new AnimationException('Offset is not in the range of frames.'); } $image->core()->slice($this->offset, $this->length); return $image; } } src/Drivers/Imagick/Modifiers/TextModifier.php000064400000011251150731645420015361 0ustar00processor()->textBlock($this->text, $this->font, $this->position); $drawText = $this->imagickDrawText($image, $this->font); $drawStroke = $this->imagickDrawStroke($image, $this->font); foreach ($image as $frame) { foreach ($lines as $line) { foreach ($this->strokeOffsets($this->font) as $offset) { // Draw the stroke outline under the actual text $this->maybeDrawTextline($frame, $line, $drawStroke, $offset); } // Draw the actual text $this->maybeDrawTextline($frame, $line, $drawText); } } return $image; } /** * Create an ImagickDraw object to draw text on the image * * @param ImageInterface $image * @param FontInterface $font * @throws RuntimeException * @throws ColorException * @throws FontException * @throws ImagickDrawException * @throws ImagickException * @return ImagickDraw */ private function imagickDrawText(ImageInterface $image, FontInterface $font): ImagickDraw { $color = $this->driver()->handleInput($font->color()); if ($font->hasStrokeEffect() && $color->isTransparent()) { throw new ColorException( 'The text color must be fully opaque when using the stroke effect.' ); } $color = $this->driver()->colorProcessor($image->colorspace())->colorToNative($color); return $this->processor()->toImagickDraw($font, $color); } /** * Create a ImagickDraw object to draw the outline stroke effect on the Image * * @param ImageInterface $image * @param FontInterface $font * @throws RuntimeException * @throws ColorException * @throws FontException * @throws ImagickDrawException * @throws ImagickException * @return null|ImagickDraw */ private function imagickDrawStroke(ImageInterface $image, FontInterface $font): ?ImagickDraw { if (!$font->hasStrokeEffect()) { return null; } $color = $this->driver()->handleInput($font->strokeColor()); if ($color->isTransparent()) { throw new ColorException( 'The stroke color must be fully opaque.' ); } $color = $this->driver()->colorProcessor($image->colorspace())->colorToNative($color); return $this->processor()->toImagickDraw($font, $color); } /** * Maybe draw given line of text on frame instance depending on given * ImageDraw instance. Optionally move line position by given offset. * * @param Frame $frame * @param Line $textline * @param null|ImagickDraw $draw * @param Point $offset * @return void */ private function maybeDrawTextline( Frame $frame, Line $textline, ?ImagickDraw $draw = null, Point $offset = new Point(), ): void { if ($draw !== null) { $frame->native()->annotateImage( $draw, $textline->position()->x() + $offset->x(), $textline->position()->y() + $offset->y(), $this->font->angle(), (string) $textline ); } } /** * Return imagick font processor * * @throws FontException * @return FontProcessor */ private function processor(): FontProcessor { $processor = $this->driver()->fontProcessor(); if (!($processor instanceof FontProcessor)) { throw new FontException('Font processor does not match the driver.'); } return $processor; } } src/EncodedImage.php000064400000002022150731645420010353 0ustar00mediaType; } /** * Alias of self::mediaType( * * @return string */ public function mimetype(): string { return $this->mediaType(); } /** * Transform encoded image data into an data uri string * * @return string */ public function toDataUri(): string { return sprintf('data:%s;base64,%s', $this->mediaType, base64_encode($this->data)); } } src/Encoders/AutoEncoder.php000064400000001031150731645420012020 0ustar00encode( $this->encoderByMediaType( $image->origin()->mediaType() ) ); } } src/Encoders/AvifEncoder.php000064400000000331150731645420011777 0ustar00encode( $this->encoderByFileExtension( is_null($this->extension) ? $image->origin()->fileExtension() : $this->extension ) ); } /** * Create matching encoder for given file extension * * @param string $extension * @throws EncoderException * @return EncoderInterface */ protected function encoderByFileExtension(?string $extension): EncoderInterface { if (empty($extension)) { throw new EncoderException('No encoder found for empty file extension.'); } return match (strtolower($extension)) { 'webp' => new WebpEncoder(quality: $this->quality), 'avif' => new AvifEncoder(quality: $this->quality), 'jpeg', 'jpg' => new JpegEncoder(quality: $this->quality), 'bmp' => new BmpEncoder(), 'gif' => new GifEncoder(), 'png' => new PngEncoder(), 'tiff', 'tif' => new TiffEncoder(quality: $this->quality), 'jp2', 'j2k', 'jpf', 'jpm', 'jpg2', 'j2c', 'jpc', 'jpx' => new Jpeg2000Encoder(quality: $this->quality), 'heic', 'heif' => new HeicEncoder(quality: $this->quality), default => throw new EncoderException('No encoder found for file extension (' . $extension . ').'), }; } } src/Encoders/FilePathEncoder.php000064400000002025150731645420012610 0ustar00encode( $this->encoderByFileExtension( is_null($this->path) ? $image->origin()->fileExtension() : pathinfo($this->path, PATHINFO_EXTENSION) ) ); } } src/Encoders/GifEncoder.php000064400000000330150731645420011616 0ustar00type) ? $image->origin()->mediaType() : $this->type; return $image->encode( $this->encoderByMediaType($type) ); } /** * Return new encoder by given media (MIME) type * * @param string $type * @throws EncoderException * @return EncoderInterface */ protected function encoderByMediaType(string $type): EncoderInterface { return match (strtolower($type)) { 'image/webp', 'image/x-webp' => new WebpEncoder(quality: $this->quality), 'image/avif', 'image/x-avif' => new AvifEncoder(quality: $this->quality), 'image/jpeg', 'image/jpg', 'image/pjpeg' => new JpegEncoder(quality: $this->quality), 'image/bmp', 'image/ms-bmp', 'image/x-bitmap', 'image/x-bmp', 'image/x-ms-bmp', 'image/x-win-bitmap', 'image/x-windows-bmp', 'image/x-xbitmap' => new BmpEncoder(), 'image/gif' => new GifEncoder(), 'image/png', 'image/x-png' => new PngEncoder(), 'image/tiff' => new TiffEncoder(quality: $this->quality), 'image/jp2', 'image/jpx', 'image/jpm' => new Jpeg2000Encoder(quality: $this->quality), 'image/heic', 'image/heif', => new HeicEncoder(quality: $this->quality), default => throw new EncoderException('No encoder found for media type (' . $type . ').'), }; } } src/Encoders/PngEncoder.php000064400000000330150731645420011635 0ustar00quality = $options[0] ?? self::DEFAULT_QUALITY; } else { $this->quality = $options['quality'] ?? self::DEFAULT_QUALITY; } } /** * {@inheritdoc} * * @see EncoderInterface::encode() */ public function encode(ImageInterface $image): EncodedImageInterface { return $image->encode($this); } } src/Encoders/TiffEncoder.php000064400000000331150731645420012002 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.php000064400000002373150731645420011054 0ustar00setWidth($diameter); $this->setHeight($diameter); return $this; } /** * Get diameter of circle * * @return int */ public function diameter(): int { return $this->width(); } /** * Set radius of circle * * @param int $radius * @return Circle */ public function setRadius(int $radius): self { return $this->setDiameter(intval($radius * 2)); } /** * Get radius of circle * * @return int */ public function radius(): int { return intval(round($this->diameter() / 2)); } } src/Geometry/Ellipse.php000064400000003757150731645420011257 0ustar00pivot; } /** * Return pivot point of Ellipse * * @return PointInterface */ public function pivot(): PointInterface { return $this->pivot; } /** * Set size of Ellipse * * @param int $width * @param int $height * @return Ellipse */ public function setSize(int $width, int $height): self { return $this->setWidth($width)->setHeight($height); } /** * Set width of Ellipse * * @param int $width * @return Ellipse */ public function setWidth(int $width): self { $this->width = $width; return $this; } /** * Set height of Ellipse * * @param int $height * @return Ellipse */ public function setHeight(int $height): self { $this->height = $height; return $this; } /** * Get width of Ellipse * * @return int */ public function width(): int { return $this->width; } /** * Get height of Ellipse * * @return int */ public function height(): int { return $this->height; } } src/Geometry/Factories/CircleFactory.php000064400000001241150731645420014314 0ustar00ellipse->setSize($radius * 2, $radius * 2); return $this; } /** * Set the diameter of the circle to be produced * * @param int $diameter * @return CircleFactory */ public function diameter(int $diameter): self { $this->ellipse->setSize($diameter, $diameter); return $this; } } src/Geometry/Factories/EllipseFactory.php000064400000004207150731645420014515 0ustar00ellipse = is_a($init, Ellipse::class) ? $init : new Ellipse(0, 0, $pivot); if (is_callable($init)) { $init($this); } } /** * Set the size of the ellipse to be produced * * @param int $width * @param int $height * @return EllipseFactory */ public function size(int $width, int $height): self { $this->ellipse->setSize($width, $height); return $this; } /** * Set the width of the ellipse to be produced * * @param int $width * @return EllipseFactory */ public function width(int $width): self { $this->ellipse->setWidth($width); return $this; } /** * Set the height of the ellipse to be produced * * @param int $height * @return EllipseFactory */ public function height(int $height): self { $this->ellipse->setHeight($height); return $this; } /** * Set the background color of the ellipse to be produced * * @param mixed $color * @return EllipseFactory */ public function background(mixed $color): self { $this->ellipse->setBackgroundColor($color); return $this; } /** * Set the border color & border size of the ellipse to be produced * * @param mixed $color * @param int $size * @return EllipseFactory */ public function border(mixed $color, int $size = 1): self { $this->ellipse->setBorder($color, $size); return $this; } /** * Produce the ellipse * * @return Ellipse */ public function __invoke(): Ellipse { return $this->ellipse; } } src/Geometry/Factories/LineFactory.php000064400000004764150731645420014017 0ustar00line = is_a($init, Line::class) ? $init : new Line(new Point(), new Point()); if (is_callable($init)) { $init($this); } } /** * Set the color of the line to be produced * * @param mixed $color * @return LineFactory */ public function color(mixed $color): self { $this->line->setBackgroundColor($color); $this->line->setBorderColor($color); return $this; } /** * Set the (background) color of the line to be produced * * @param mixed $color * @return LineFactory */ public function background(mixed $color): self { $this->line->setBackgroundColor($color); $this->line->setBorderColor($color); return $this; } /** * Set the border size & border color of the line to be produced * * @param mixed $color * @param int $size * @return LineFactory */ public function border(mixed $color, int $size = 1): self { $this->line->setBackgroundColor($color); $this->line->setBorderColor($color); $this->line->setWidth($size); return $this; } /** * Set the width of the line to be produced * * @param int $size * @return LineFactory */ public function width(int $size): self { $this->line->setWidth($size); return $this; } /** * Set the coordinates of the starting point of the line to be produced * * @param int $x * @param int $y * @return LineFactory */ public function from(int $x, int $y): self { $this->line->setStart(new Point($x, $y)); return $this; } /** * Set the coordinates of the end point of the line to be produced * * @param int $x * @param int $y * @return LineFactory */ public function to(int $x, int $y): self { $this->line->setEnd(new Point($x, $y)); return $this; } /** * Produce the line * * @return Line */ public function __invoke(): Line { return $this->line; } } src/Geometry/Factories/PolygonFactory.php000064400000003077150731645420014553 0ustar00polygon = is_a($init, Polygon::class) ? $init : new Polygon([]); if (is_callable($init)) { $init($this); } } /** * Add a point to the polygon to be produced * * @param int $x * @param int $y * @return PolygonFactory */ public function point(int $x, int $y): self { $this->polygon->addPoint(new Point($x, $y)); return $this; } /** * Set the background color of the polygon to be produced * * @param mixed $color * @return PolygonFactory */ public function background(mixed $color): self { $this->polygon->setBackgroundColor($color); return $this; } /** * Set the border color & border size of the polygon to be produced * * @param mixed $color * @param int $size * @return PolygonFactory */ public function border(mixed $color, int $size = 1): self { $this->polygon->setBorder($color, $size); return $this; } /** * Produce the polygon * * @return Polygon */ public function __invoke(): Polygon { return $this->polygon; } } src/Geometry/Factories/RectangleFactory.php000064400000004267150731645420015032 0ustar00rectangle = is_a($init, Rectangle::class) ? $init : new Rectangle(0, 0, $pivot); if (is_callable($init)) { $init($this); } } /** * Set the size of the rectangle to be produced * * @param int $width * @param int $height * @return RectangleFactory */ public function size(int $width, int $height): self { $this->rectangle->setSize($width, $height); return $this; } /** * Set the width of the rectangle to be produced * * @param int $width * @return RectangleFactory */ public function width(int $width): self { $this->rectangle->setWidth($width); return $this; } /** * Set the height of the rectangle to be produced * * @param int $height * @return RectangleFactory */ public function height(int $height): self { $this->rectangle->setHeight($height); return $this; } /** * Set the background color of the rectangle to be produced * * @param mixed $color * @return RectangleFactory */ public function background(mixed $color): self { $this->rectangle->setBackgroundColor($color); return $this; } /** * Set the border color & border size of the rectangle to be produced * * @param mixed $color * @param int $size * @return RectangleFactory */ public function border(mixed $color, int $size = 1): self { $this->rectangle->setBorder($color, $size); return $this; } /** * Produce the rectangle * * @return Rectangle */ public function __invoke(): Rectangle { return $this->rectangle; } } src/Geometry/Line.php000064400000004656150731645420010550 0ustar00start; } /** * Return line width * * @return int */ public function width(): int { return $this->width; } /** * Set line width * * @param int $width * @return Line */ public function setWidth(int $width): self { $this->width = $width; return $this; } /** * Get starting point of line * * @return Point */ public function start(): Point { return $this->start; } /** * get end point of line * * @return Point */ public function end(): Point { return $this->end; } /** * Set starting point of line * * @param Point $start * @return Line */ public function setStart(Point $start): self { $this->start = $start; return $this; } /** * Set starting point of line by coordinates * * @param int $x * @param int $y * @return Line */ public function from(int $x, int $y): self { $this->start()->setX($x); $this->start()->setY($y); return $this; } /** * Set end point of line by coordinates * * @param int $x * @param int $y * @return Line */ public function to(int $x, int $y): self { $this->end()->setX($x); $this->end()->setY($y); return $this; } /** * Set end point of line * * @param Point $end * @return Line */ public function setEnd(Point $end): self { $this->end = $end; return $this; } } src/Geometry/Pixel.php000064400000001601150731645420010725 0ustar00background = $background; return $this; } /** * {@inheritdoc} * * @see DrawableInterface::backgroundColor() */ public function backgroundColor(): ColorInterface { return $this->background; } } src/Geometry/Point.php000064400000004320150731645420010736 0ustar00x = $x; return $this; } /** * Get X coordinate * * @return int */ public function x(): int { return $this->x; } /** * Sets Y coordinate * * @param int $y */ public function setY(int $y): self { $this->y = $y; return $this; } /** * Get Y coordinate * * @return int */ public function y(): int { return $this->y; } /** * Move X coordinate * * @param int $value */ public function moveX(int $value): self { $this->x += $value; return $this; } /** * Move Y coordinate * * @param int $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 int $x * @param int $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, self $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.php000064400000022644150731645420011305 0ustar00pivot; } /** * Implement iteration through all points of polygon * * @return Traversable */ 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.php000064400000020345150731645420011556 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)); } /** * Set size of rectangle * * @param int $width * @param int $height * @return Rectangle */ public function setSize(int $width, int $height): self { return $this->setWidth($width)->setHeight($height); } /** * Set width of rectangle * * @param int $width * @return Rectangle */ public function setWidth(int $width): self { $this[1]->setX($this[0]->x() + $width); $this[2]->setX($this[3]->x() + $width); return $this; } /** * Set height of rectangle * * @param int $height * @return Rectangle */ public function setHeight(int $height): self { $this[2]->setY($this[1]->y() + $height); $this[3]->setY($this[0]->y() + $height); return $this; } /** * Return pivot point of rectangle * * @return PointInterface */ public function pivot(): PointInterface { return $this->pivot; } /** * Set pivot point of rectangle * * @param PointInterface $pivot * @return Rectangle */ public function setPivot(PointInterface $pivot): self { $this->pivot = $pivot; return $this; } /** * Move pivot to the given position in the rectangle and adjust the new * position by given offset values. * * @param string $position * @param int $offset_x * @param int $offset_y * @return Rectangle */ 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; } /** * Align pivot relative to given size at given position * * @param SizeInterface $size * @param string $position * @return Rectangle */ 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; } /** * Return relative position to given rectangle * * @param SizeInterface $rectangle * @return PointInterface */ public function relativePositionTo(SizeInterface $rectangle): PointInterface { return new Point( $this->pivot()->x() - $rectangle->pivot()->x(), $this->pivot()->y() - $rectangle->pivot()->y() ); } /** * Return aspect ration of rectangle * * @return float */ public function aspectRatio(): float { return $this->width() / $this->height(); } /** * Determine if rectangle fits into given rectangle * * @param SizeInterface $size * @return bool */ public function fitsInto(SizeInterface $size): bool { if ($this->width() > $size->width()) { return false; } if ($this->height() > $size->height()) { return false; } return true; } /** * Determine if rectangle has landscape format * * @return bool */ public function isLandscape(): bool { return $this->width() > $this->height(); } /** * Determine if rectangle has landscape format * * @return bool */ public function isPortrait(): bool { return $this->width() < $this->height(); } /** * Return most top left point of rectangle * * @return PointInterface */ public function topLeftPoint(): PointInterface { return $this->points[0]; } /** * Return bottom right point of rectangle * * @return PointInterface */ public function bottomRightPoint(): PointInterface { return $this->points[2]; } /** * @throws GeometryException */ 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.php000064400000020305150731645420014216 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; } /** * @throws GeometryException */ 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 max([1, (int) round($this->height * $size->aspectRatio())]); } protected function getProportionalHeight(SizeInterface $size): int { if (!$this->hasTargetWidth()) { return $size->height(); } return max([1, (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 * @throws GeometryException * @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 * @throws GeometryException * @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 * @throws GeometryException * @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.php000064400000001413150731645420014625 0ustar00backgroundColor = $color; return $this; } /** * {@inheritdoc} * * @see DrawableInterface::backgroundColor() */ public function backgroundColor(): mixed { return $this->backgroundColor; } /** * {@inheritdoc} * * @see DrawableInterface::hasBackgroundColor() */ public function hasBackgroundColor(): bool { return !empty($this->backgroundColor); } } src/Geometry/Traits/HasBorder.php000064400000002571150731645420012772 0ustar00setBorderSize($size)->setBorderColor($color); } /** * {@inheritdoc} * * @see DrawableInterface::setBorderSize() */ public function setBorderSize(int $size): self { $this->borderSize = $size; return $this; } /** * {@inheritdoc} * * @see DrawableInterface::borderSize() */ public function borderSize(): int { return $this->borderSize; } /** * {@inheritdoc} * * @see DrawableInterface::setBorderColor() */ public function setBorderColor(mixed $color): self { $this->borderColor = $color; return $this; } /** * {@inheritdoc} * * @see DrawableInterface::borderColor() */ public function borderColor(): mixed { return $this->borderColor; } /** * {@inheritdoc} * * @see DrawableInterface::hasBorder() */ public function hasBorder(): bool { return $this->borderSize > 0 && !is_null($this->borderColor); } } src/Image.php000064400000062520150731645420007102 0ustar00origin = new Origin(); $this->blendingColor = $this->colorspace()->importColor( new Color(255, 255, 255, 0) ); } /** * {@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::sliceAnimation() */ public function sliceAnimation(int $offset = 0, ?int $length = null): ImageInterface { return $this->modify(new SliceAnimationModifier($offset, $length)); } /** * {@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 ImgageInterface::setExif() */ public function setExif(CollectionInterface $exif): ImageInterface { $this->exif = $exif; return $this; } /** * {@inheritdoc} * * @see ImageInterface::modify() */ public function modify(ModifierInterface $modifier): ImageInterface { return $this->driver->specialize($modifier)->apply($this); } /** * {@inheritdoc} * * @see ImageInterface::analyze() */ public function analyze(AnalyzerInterface $analyzer): mixed { return $this->driver->specialize($analyzer)->analyze($this); } /** * {@inheritdoc} * * @see ImageInterface::encode() */ public function encode(EncoderInterface $encoder = new AutoEncoder()): EncodedImageInterface { return $this->driver->specialize($encoder)->encode($this); } /** * {@inheritdoc} * * @see ImageInterface::save() */ public function save(?string $path = null, ...$options): ImageInterface { $path = is_null($path) ? $this->origin()->filePath() : $path; if (is_null($path)) { throw new EncoderException('Could not determine file path to save.'); } try { // try to determine encoding format by file extension of the path $encoded = $this->encodeByPath($path, ...$options); } catch (EncoderException) { // fallback to encoding format by media type $encoded = $this->encodeByMediaType(null, ...$options); } $encoded->save($path); return $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::blendingColor() */ public function blendingColor(): ColorInterface { return $this->blendingColor; } /** * {@inheritdoc} * * @see ImageInterface::setBlendingColor() */ public function setBlendingColor(mixed $color): ImageInterface { $this->blendingColor = $this->driver()->handleInput($color); return $this; } /** * {@inheritdoc} * * @see ImageInterface::blendTransparency() */ public function blendTransparency(mixed $color = null): ImageInterface { return $this->modify(new BlendTransparencyModifier($color)); } /** * {@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, mixed $background = 'ffffff', string $position = 'top-left' ): ImageInterface { return $this->modify(new CropModifier($width, $height, $offset_x, $offset_y, $background, $position)); } /** * {@inheritdoc} * * @see ImageInterface::place() */ public function place( mixed $element, string $position = 'top-left', int $offset_x = 0, int $offset_y = 0, int $opacity = 100 ): ImageInterface { return $this->modify(new PlaceModifier($element, $position, $offset_x, $offset_y, $opacity)); } /** * {@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::encodeByMediaType() */ public function encodeByMediaType(?string $type = null, ...$options): EncodedImageInterface { return $this->encode(new MediaTypeEncoder($type, ...$options)); } /** * {@inheritdoc} * * @see ImageInterface::encodeByExtension() */ public function encodeByExtension(?string $extension = null, mixed ...$options): EncodedImageInterface { return $this->encode(new FileExtensionEncoder($extension, ...$options)); } /** * {@inheritdoc} * * @see ImageInterface::encodeByPath() */ public function encodeByPath(?string $path = null, mixed ...$options): EncodedImageInterface { return $this->encode(new FilePathEncoder($path, ...$options)); } /** * {@inheritdoc} * * @see ImageInterface::toJpeg() */ public function toJpeg(mixed ...$options): EncodedImageInterface { return $this->encode(new JpegEncoder(...$options)); } /** * Alias of self::toJpeg() * * @param mixed $options * @throws RuntimeException * @return EncodedImageInterface */ public function toJpg(mixed ...$options): EncodedImageInterface { return $this->toJpeg(...$options); } /** * {@inheritdoc} * * @see ImageInterface::toJpeg() */ public function toJpeg2000(mixed ...$options): EncodedImageInterface { return $this->encode(new Jpeg2000Encoder(...$options)); } /** * ALias of self::toJpeg2000() * * @param mixed $options * @throws RuntimeException * @return EncodedImageInterface */ public function toJp2(mixed ...$options): EncodedImageInterface { return $this->toJpeg2000(...$options); } /** * {@inheritdoc} * * @see ImageInterface::toPng() */ public function toPng(mixed ...$options): EncodedImageInterface { return $this->encode(new PngEncoder(...$options)); } /** * {@inheritdoc} * * @see ImageInterface::toGif() */ public function toGif(mixed ...$options): EncodedImageInterface { return $this->encode(new GifEncoder(...$options)); } /** * {@inheritdoc} * * @see ImageInterface::toWebp() */ public function toWebp(mixed ...$options): EncodedImageInterface { return $this->encode(new WebpEncoder(...$options)); } /** * {@inheritdoc} * * @see ImageInterface::toBitmap() */ public function toBitmap(mixed ...$options): EncodedImageInterface { return $this->encode(new BmpEncoder(...$options)); } /** * Alias if self::toBitmap() * * @throws RuntimeException * @return EncodedImageInterface */ public function toBmp(mixed ...$options): EncodedImageInterface { return $this->toBitmap(...$options); } /** * {@inheritdoc} * * @see ImageInterface::toAvif() */ public function toAvif(mixed ...$options): EncodedImageInterface { return $this->encode(new AvifEncoder(...$options)); } /** * {@inheritdoc} * * @see ImageInterface::toTiff() */ public function toTiff(mixed ...$options): EncodedImageInterface { return $this->encode(new TiffEncoder(...$options)); } /** * Alias of self::toTiff() * * @param mixed $options * @throws RuntimeException * @return EncodedImageInterface */ public function toTif(mixed ...$options): EncodedImageInterface { return $this->toTiff(...$options); } /** * {@inheritdoc} * * @see ImageInterface::toHeic() */ public function toHeic(mixed ...$options): EncodedImageInterface { return $this->encode(new HeicEncoder(...$options)); } /** * 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.php000064400000010505150731645420010371 0ustar00driver = $this->resolveDriver($driver); } /** * Create image mangager with given driver * * @link https://image.intervention.io/v3/basics/image-manager * @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 * * @link https://image.intervention.io/v3/basics/image-manager#static-gd-driver-constructor * @return ImageManager */ public static function gd(): self { return self::withDriver(GdDriver::class); } /** * Create image manager with Imagick driver * * @link https://image.intervention.io/v3/basics/image-manager#static-imagick-driver-constructor * @return ImageManager */ public static function imagick(): self { return self::withDriver(ImagickDriver::class); } /** * Create new image instance with given width & height * * @link https://image.intervention.io/v3/basics/instantiation#creating-new-images * @param int $width * @param int $height * @throws RuntimeException * @return ImageInterface */ public function create(int $width, int $height): ImageInterface { return $this->driver->createImage($width, $height); } /** * Create new image instance from given input 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 * * To decode the raw input data, you can optionally specify a decoding strategy * with the second parameter. This can be an array of class names or objects * of decoders to be processed in sequence. In this case, the input must be * decodedable with one of the decoders passed. It is also possible to pass * a single object or class name of a decoder. * * All decoders that implement the `DecoderInterface::class` can be passed. Usually * a selection of classes of the namespace `Intervention\Image\Decoders` * * If the second parameter is not set, an attempt to decode the input is made * with all available decoders of the driver. * * @link https://image.intervention.io/v3/basics/instantiation#reading-images * @param mixed $input * @param string|array|DecoderInterface $decoders * @throws RuntimeException * @return ImageInterface */ public function read(mixed $input, string|array|DecoderInterface $decoders = []): ImageInterface { return $this->driver->handleInput( $input, match (true) { is_string($decoders), is_a($decoders, DecoderInterface::class) => [$decoders], default => $decoders, } ); } /** * Create new animated image by given callback * * @link https://image.intervention.io/v3/basics/instantiation#creating-animations * @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.php000064400000000604150731645420013544 0ustar00modifiers as $modifier) { $modifier->apply($image); } return $image; } /** * Append new modifier to the stack * * @param ModifierInterface $modifier * @return ModifierStack */ public function push(ModifierInterface $modifier): self { $this->modifiers[] = $modifier; return $this; } } src/Modifiers/AlignRotationModifier.php000064400000000210150731645420014216 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.php000064400000002016150731645420013045 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.php000064400000000302150731645420013243 0ustar00size(); $crop = new Rectangle($this->width, $this->height); return $crop->contain( $imagesize->width(), $imagesize->height() )->alignPivotTo($imagesize, $this->position); } /** * @throws RuntimeException */ public function getResizeSize(SizeInterface $size): SizeInterface { return $size->scale($this->width, $this->height); } } src/Modifiers/CropModifier.php000064400000001612150731645420012356 0ustar00width, $this->height); $crop->align($this->position); return $crop->alignPivotTo( $image->size(), $this->position ); } } src/Modifiers/DrawEllipseModifier.php000064400000000366150731645420013673 0ustar00position); } } src/Modifiers/FlipModifier.php000064400000000177150731645420012352 0ustar00size() ->containMax( $this->width, $this->height ) ->alignPivotTo( $this->getResizeSize($image), $this->position ); } } src/Modifiers/PixelateModifier.php000064400000000301150731645420013220 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.php000064400000000405150731645420013052 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.php000064400000001712150731645420014051 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.php000064400000001303150731645420015541 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.php000064400000000176150731645420013550 0ustar00angle, 360); } } src/Modifiers/ScaleDownModifier.php000064400000000174150731645420013334 0ustar00modify($this); } } src/Modifiers/TextModifier.php000064400000000547150731645420012405 0ustar00mediaType; } /** * Alias of self::mediaType() */ public function mimetype(): string { return $this->mediaType(); } /** * Set media type of current instance * * @param string $type * @return Origin */ public function setMediaType(string $type): self { $this->mediaType = $type; return $this; } /** * Return file path of origin * * @return null|string */ public function filePath(): ?string { return $this->filePath; } /** * Set file path for origin * * @param string $path * @return Origin */ public function setFilePath(string $path): self { $this->filePath = $path; return $this; } /** * Return file extension if origin was created from file path * * @return null|string */ public function fileExtension(): ?string { return empty($this->filePath) ? null : pathinfo($this->filePath, PATHINFO_EXTENSION); } } src/Resolution.php000064400000005410150731645420010216 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.php000064400000000705150731645420013145 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::setStrokeColor() */ public function setStrokeColor(mixed $color): FontInterface { $this->strokeColor = $color; return $this; } /** * {@inheritdoc} * * @see FontInterface::strokeColor() */ public function strokeColor(): mixed { return $this->strokeColor; } /** * {@inheritdoc} * * @see FontInterface::setStrokeWidth() */ public function setStrokeWidth(int $width): FontInterface { if (!in_array($width, range(0, 10))) { throw new FontException( 'The stroke width must be in the range from 0 to 10.' ); } $this->strokeWidth = $width; return $this; } /** * {@inheritdoc} * * @see FontInterface::strokeWidth() */ public function strokeWidth(): int { return $this->strokeWidth; } /** * {@inheritdoc} * * @see FontInterface::hasStrokeEffect() */ public function hasStrokeEffect(): bool { return $this->strokeWidth > 0; } /** * {@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; } /** * {@inheritdoc} * * @see FontInterface::setWrapWidth() */ public function setWrapWidth(?int $width): FontInterface { $this->wrapWidth = $width; return $this; } /** * {@inheritdoc} * * @see FontInterface::wrapWidth() */ public function wrapWidth(): ?int { return $this->wrapWidth; } } src/Typography/FontFactory.php000064400000006406150731645420012465 0ustar00font = is_a($init, FontInterface::class) ? $init : new Font(); if (is_callable($init)) { $init($this); } } /** * Build font * * @return FontInterface */ public function __invoke(): FontInterface { return $this->font; } /** * Set the filename of the font to be built * * @param string $value * @return FontFactory */ public function filename(string $value): self { $this->font->setFilename($value); return $this; } /** * {@inheritdoc} * * @see self::filename() */ public function file(string $value): self { return $this->filename($value); } /** * Set outline stroke effect for the font to be built * * @param mixed $color * @param int $width * @throws FontException * @return FontFactory */ public function stroke(mixed $color, int $width = 1): self { $this->font->setStrokeWidth($width); $this->font->setStrokeColor($color); return $this; } /** * Set color for the font to be built * * @param mixed $value * @return FontFactory */ public function color(mixed $value): self { $this->font->setColor($value); return $this; } /** * Set the size for the font to be built * * @param float $value * @return FontFactory */ public function size(float $value): self { $this->font->setSize($value); return $this; } /** * Set the horizontal alignment of the font to be built * * @param string $value * @return FontFactory */ public function align(string $value): self { $this->font->setAlignment($value); return $this; } /** * Set the vertical alignment of the font to be built * * @param string $value * @return FontFactory */ public function valign(string $value): self { $this->font->setValignment($value); return $this; } /** * Set the line height of the font to be built * * @param float $value * @return FontFactory */ public function lineHeight(float $value): self { $this->font->setLineHeight($value); return $this; } /** * Set the rotation angle of the font to be built * * @param float $value * @return FontFactory */ public function angle(float $value): self { $this->font->setAngle($value); return $this; } /** * Set the maximum width of the text block to be built * * @param int $width * @return FontFactory */ public function wrap(int $width): self { $this->font->setWrapWidth($width); return $this; } } src/Typography/Line.php000064400000003650150731645420011114 0ustar00segments = explode(" ", $text); } } /** * Add word to current line * * @param string $word * @return Line */ public function add(string $word): self { $this->segments[] = $word; return $this; } /** * Returns Iterator * * @return Traversable */ public function getIterator(): Traversable { return new ArrayIterator($this->segments); } /** * Get Position of line * * @return PointInterface */ public function position(): PointInterface { return $this->position; } /** * Set position of current line * * @param Point $point * @return Line */ public function setPosition(Point $point): self { $this->position = $point; return $this; } /** * Count segments of line * * @return int */ public function count(): int { return count($this->segments); } /** * Cast line to string * * @return string */ public function __toString(): string { return implode(" ", $this->segments); } } src/Typography/TextBlock.php000064400000002634150731645420012125 0ustar00push(new Line($line)); } } /** * Return array of lines in text block * * @return array */ public function lines(): array { return $this->items; } /** * Set lines of the text block * * @param array $lines * @return self */ public function setLines(array $lines): self { $this->items = $lines; return $this; } /** * Get line by given key * * @param mixed $key * @return null|Line */ 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 (Line $a, Line $b) { if (mb_strlen((string) $a) === mb_strlen((string) $b)) { return 0; } return mb_strlen((string) $a) > mb_strlen((string) $b) ? -1 : 1; }); return $lines[0]; } }