Dockerfile000064400000001653150731645660006560 0ustar00FROM php:8-cli RUN apt update \ && apt install -y \ libpng-dev \ libicu-dev \ libavif-dev \ libpq-dev \ libzip-dev \ zip \ zlib1g-dev \ locales \ locales-all \ libmagickwand-dev \ libwebp-dev \ && pecl install imagick \ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp --with-avif \ && docker-php-ext-enable imagick \ && docker-php-ext-install \ intl \ opcache \ pdo \ pdo_pgsql \ pdo_mysql \ pgsql \ fileinfo \ mysqli \ gd \ bcmath \ exif \ zip \ && apt-get clean # install composer # RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer LICENSE000064400000002066150731645660005572 0ustar00The MIT License (MIT) Copyright (c) 2021 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.json000064400000001537150731645660007311 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", "intervention/gif": "^3.0" }, "require-dev": { "phpunit/phpunit": "^9", "mockery/mockery": "^1.6", "phpstan/phpstan": "^1" }, "autoload": { "psr-4": { "Intervention\\Image\\": "src" } }, "autoload-dev": { "psr-4": { "Intervention\\Image\\Tests\\": "tests" } }, "minimum-stability": "alpha" } docker-compose.yml000064400000000535150731645660010221 0ustar00version: '3' services: tests: build: ./ working_dir: /project command: bash -c "composer install && ./vendor/bin/phpunit -vvv" volumes: - ./:/project analysis: build: ./ working_dir: /project command: bash -c "composer install && ./vendor/bin/phpstan analyze --level=4 ./src" volumes: - ./:/project phpunit.xml.dist000064400000001264150731645660007737 0ustar00 ./tests/ src readme.md000064400000004137150731645660006345 0ustar00# Intervention Image ## PHP Image Manipulation [![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 **image handling and manipulation library written in PHP** providing an easier and expressive way to create, edit, and compose images. GD library or Imagick can be selected as the base layer for all operations. - Simple interface for common tasks - Interchangable driver architecture - Support for animated images - Framework-agnostic - PSR-12 compliant ## Code Examples ```php // create image manager with desired driver $manager = new ImageManager(driver: 'gd'); // 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 ## Installation ```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/). ## Development & Testing With this package comes a Docker image to build a test suite and analysis container. To build this container you have to have Docker installed on your system. You can run all tests with this command. ```bash docker-compose run --rm --build tests ``` Run the static analyzer on the code base. ```bash docker-compose run --rm --build analysis ``` ## License Intervention Image is licensed under the [MIT License](http://opensource.org/licenses/MIT). Copyright 2022 Oliver Vogel src/Collection.php000064400000007575150731645660010172 0ustar00items); } public function toArray(): array { return $this->items; } /** * Count items in collection * * @return integer */ public function count(): int { return count($this->items); } /** * Append new item to collection * * @param mixed $item * @return CollectionInterface */ public function push($item): CollectionInterface { $this->items[] = $item; return $this; } /** * Return first item in collection * * @return mixed */ public function first() { if ($item = reset($this->items)) { return $item; } return null; } /** * Returns last item in collection * * @return mixed */ public function last() { if ($item = end($this->items)) { return $item; } return null; } /** * Return item at given position starting at 0 * * @param integer $key * @return mixed */ public function getAtPosition(int $key = 0, $default = null) { if ($this->count() == 0) { return $default; } $positions = array_values($this->items); if (! array_key_exists($key, $positions)) { return $default; } return $positions[$key]; } public function get(int|string $query, $default = null) { if ($this->count() == 0) { return $default; } if (is_int($query) && array_key_exists($query, $this->items)) { return $this->items[$query]; } if (is_string($query) && strpos($query, '.') === false) { return array_key_exists($query, $this->items) ? $this->items[$query] : $default; } $query = explode('.', $query); $result = $default; $items = $this->items; foreach ($query as $key) { if (!is_array($items) || !array_key_exists($key, $items)) { $result = $default; break; } $result = $items[$key]; $items = $result; } return $result; } public function map(callable $callback): self { $items = array_map(function ($item) use ($callback) { return $callback($item); }, $this->items); return new self($items); } /** * Run callback on each item of the collection an remove it if it does not return true * * @param callable $callback * @return Collection */ public function filter(callable $callback): self { $items = array_filter($this->items, function ($item) use ($callback) { return $callback($item); }); return new self($items); } public function pushEach(array $data, ?callable $callback = null): CollectionInterface { if (! is_iterable($data)) { throw new RuntimeException('Unable to iterate given data.'); } foreach ($data as $item) { $this->push(is_callable($callback) ? $callback($item) : $item); } return $this; } } src/Colors/Cmyk/Channels/Cyan.php000064400000003033150731645660012651 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') } ); } public function value(): int { return $this->value; } public function normalize($precision = 32): float { return round($this->value() / $this->max(), $precision); } public function min(): int { return 0; } public function max(): int { return 100; } public function validate(mixed $value): mixed { if ($value < $this->min() || $value > $this->max()) { throw new ColorException('CMYK color values must be in range 0-100.'); } return $value; } public function toString(): string { return (string) $this->value(); } public function __toString(): string { return $this->toString(); } } src/Colors/Cmyk/Channels/Key.php000064400000000135150731645660012507 0ustar00channels = [ new Cyan($c), new Magenta($m), new Yellow($y), new Key($k), ]; } public static function create(mixed $input): ColorInterface { return (new class ([ Decoders\StringColorDecoder::class, ]) extends AbstractInputHandler { })->handle($input); } public function colorspace(): ColorspaceInterface { return new Colorspace(); } public function toHex(string $prefix = ''): string { return $this->convertTo(RgbColorspace::class)->toHex($prefix); } public function channels(): array { return $this->channels; } public function cyan(): ColorChannelInterface { return $this->channel(Cyan::class); } public function magenta(): ColorChannelInterface { return $this->channel(Magenta::class); } public function yellow(): ColorChannelInterface { return $this->channel(Yellow::class); } public function key(): ColorChannelInterface { return $this->channel(Key::class); } public function toArray(): array { return [ $this->cyan()->value(), $this->magenta()->value(), $this->yellow()->value(), $this->key()->value(), ]; } public function convertTo(string|ColorspaceInterface $colorspace): ColorInterface { $colorspace = match (true) { is_object($colorspace) => $colorspace, default => new $colorspace(), }; return $colorspace->convertColor($this); } public function toString(): string { return sprintf( 'cmyk(%d%%, %d%%, %d%%, %d%%)', $this->cyan()->value(), $this->magenta()->value(), $this->yellow()->value(), $this->key()->value() ); } public function isGreyscale(): bool { return 0 === array_sum([ $this->cyan()->value(), $this->magenta()->value(), $this->yellow()->value(), ]); } public function __toString(): string { return $this->toString(); } } src/Colors/Cmyk/Colorspace.php000064400000003215150731645660012320 0ustar00value(); }, self::$channels, $normalized); return new Color(...$values); } /** * {@inheritdoc} * * @see ColorspaceInterface::convertColor() */ public function convertColor(ColorInterface $color): ColorInterface { return match (get_class($color)) { RgbColor::class => $this->convertRgbColor($color), default => $color, }; } protected function convertRgbColor(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.php000064400000002356150731645660015516 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/Profile.php000064400000000317150731645660010723 0ustar00normalize(), 6)); } public function __toString(): string { return $this->toString(); } } src/Colors/Rgb/Channels/Blue.php000064400000000134150731645660012454 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->max(), $precision); } /** * {@inheritdoc} * * @see ColorChannelInterface::min() */ public function min(): int { return 0; } /** * {@inheritdoc} * * @see ColorChannelInterface::max() */ public function max(): int { return 255; } /** * {@inheritdoc} * * @see ColorChannelInterface::validate() */ public function validate(mixed $value): mixed { if ($value < $this->min() || $value > $this->max()) { throw new ColorException('RGB color values must be in range 0-255.'); } 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/Rgb/Color.php000064400000011472150731645660011117 0ustar00channels = [ new Red($r), new Green($g), new Blue($b), new Alpha($a), ]; } public function colorspace(): ColorspaceInterface { return new Colorspace(); } /** * {@inheritdoc} * * @see ColorInterface::create() */ public static function create(mixed $input): ColorInterface { return (new class ([ Decoders\HexColorDecoder::class, Decoders\StringColorDecoder::class, Decoders\TransparentColorDecoder::class, Decoders\HtmlColornameDecoder::class, ]) extends AbstractInputHandler { })->handle($input); } /** * Return the RGB red color channel * * @return ColorChannelInterface */ public function red(): ColorChannelInterface { return $this->channel(Red::class); } /** * Return the RGB green color channel * * @return ColorChannelInterface */ public function green(): ColorChannelInterface { return $this->channel(Green::class); } /** * Return the RGB blue color channel * * @return ColorChannelInterface */ public function blue(): ColorChannelInterface { return $this->channel(Blue::class); } /** * Return the colors alpha channel * * @return ColorChannelInterface */ public function alpha(): ColorChannelInterface { return $this->channel(Alpha::class); } /** * {@inheritdoc} * * @see ColorInterface::toArray() */ public function toArray(): array { return array_map(function (ColorChannelInterface $channel) { return $channel->value(); }, $this->channels()); } /** * {@inheritdoc} * * @see ColorInterface::toHex() */ public function toHex(string $prefix = ''): string { if ($this->isFullyOpaque()) { return sprintf( '%s%02x%02x%02x', $prefix, $this->red()->value(), $this->green()->value(), $this->blue()->value() ); } return sprintf( '%s%02x%02x%02x%02x', $prefix, $this->red()->value(), $this->green()->value(), $this->blue()->value(), $this->alpha()->value() ); } /** * {@inheritdoc} * * @see ColorInterface::convertTo() */ public function convertTo(string|ColorspaceInterface $colorspace): ColorInterface { $colorspace = match (true) { is_object($colorspace) => $colorspace, default => new $colorspace(), }; return $colorspace->convertColor($this); } public function isFullyOpaque(): bool { return $this->alpha()->value() === 255; } /** * {@inheritdoc} * * @see ColorInterface::toString() */ public function toString(): string { if ($this->isFullyOpaque()) { return sprintf( 'rgb(%d, %d, %d)', $this->red()->value(), $this->green()->value(), $this->blue()->value() ); } return sprintf( 'rgba(%d, %d, %d, %.1F)', $this->red()->value(), $this->green()->value(), $this->blue()->value(), $this->alpha()->normalize(), ); } /** * {@inheritdoc} * * @see ColorInterface::isGreyscale() */ public function isGreyscale(): bool { $values = [$this->red()->value(), $this->green()->value(), $this->blue()->value()]; return count(array_unique($values, SORT_REGULAR)) === 1; } /** * {@inheritdoc} * * @see ColorInterface::__toString() */ public function __toString(): string { return $this->toString(); } } src/Colors/Rgb/Colorspace.php000064400000002700150731645660012125 0ustar00value(); }, self::$channels, $normalized); return new Color(...$values); } public function convertColor(ColorInterface $color): ColorInterface { return match (get_class($color)) { CmykColor::class => $this->convertCmykColor($color), default => $color, }; } protected function convertCmykColor(CmykColor $color): Color { 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())), ); } } src/Colors/Rgb/Decoders/HexColorDecoder.php000064400000003160150731645660014575 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.php000064400000014050150731645660015616 0ustar00 '#ffa07a', 'salmon' => '#fa8072', 'darksalmon' => '#e9967a', 'lightcoral' => '#f08080', 'indianred' => '#cd5c5c', 'crimson' => '#dc143c', 'firebrick' => '#b22222', 'red' => '#ff0000', 'darkred' => '#8b0000', 'coral' => '#ff7f50', 'tomato' => '#ff6347', 'orangered' => '#ff4500', 'gold' => '#ffd700', 'orange' => '#ffa500', 'darkorange' => '#ff8c00', 'lightyellow' => '#ffffe0', 'lemonchiffon' => '#fffacd', 'lightgoldenrodyellow' => '#fafad2', 'papayawhip' => '#ffefd5', 'moccasin' => '#ffe4b5', 'peachpuff' => '#ffdab9', 'palegoldenrod' => '#eee8aa', 'khaki' => '#f0e68c', 'darkkhaki' => '#bdb76b', 'yellow' => '#ffff00', 'lawngreen' => '#7cfc00', 'chartreuse' => '#7fff00', 'limegreen' => '#32cd32', 'lime' => '#00ff00', 'forestgreen' => '#228b22', 'green' => '#008000', 'darkgreen' => '#006400', 'greenyellow' => '#adff2f', 'yellowgreen' => '#9acd32', 'springgreen' => '#00ff7f', 'mediumspringgreen' => '#00fa9a', 'lightgreen' => '#90ee90', 'palegreen' => '#98fb98', 'darkseagreen' => '#8fbc8f', 'mediumseagre' => 'en #3cb371', 'seagreen' => '#2e8b57', 'olive' => '#808000', 'darkolivegreen' => '#556b2f', 'olivedrab' => '#6b8e23', 'lightcyan' => '#e0ffff', 'cyan' => '#00ffff', 'aqua' => '#00ffff', 'aquamarine' => '#7fffd4', 'mediumaquamarine' => '#66cdaa', 'paleturquoise' => '#afeeee', 'turquoise' => '#40e0d0', 'mediumturquoise' => '#48d1cc', 'darkturquoise' => '#00ced1', 'lightseagreen' => '#20b2aa', 'cadetblue' => '#5f9ea0', 'darkcyan' => '#008b8b', 'teal' => '#008080', 'powderblue' => '#b0e0e6', 'lightblue' => '#add8e6', 'lightskyblue' => '#87cefa', 'skyblue' => '#87ceeb', 'deepskyblue' => '#00bfff', 'lightsteelblue' => '#b0c4de', 'dodgerblue' => '#1e90ff', 'cornflowerblue' => '#6495ed', 'steelblue' => '#4682b4', 'royalblue' => '#4169e1', 'blue' => '#0000ff', 'mediumblue' => '#0000cd', 'darkblue' => '#00008b', 'navy' => '#000080', 'midnightblue' => '#191970', 'mediumslateblue' => '#7b68ee', 'slateblue' => '#6a5acd', 'darkslateblue' => '#483d8b', 'lavender' => '#e6e6fa', 'thistle' => '#d8bfd8', 'plum' => '#dda0dd', 'violet' => '#ee82ee', 'orchid' => '#da70d6', 'fuchsia' => '#ff00ff', 'magenta' => '#ff00ff', 'mediumorchid' => '#ba55d3', 'mediumpurple' => '#9370db', 'blueviolet' => '#8a2be2', 'darkviolet' => '#9400d3', 'darkorchid' => '#9932cc', 'darkmagenta' => '#8b008b', 'purple' => '#800080', 'indigo' => '#4b0082', 'pink' => '#ffc0cb', 'lightpink' => '#ffb6c1', 'hotpink' => '#ff69b4', 'deeppink' => '#ff1493', 'palevioletred' => '#db7093', 'mediumvioletred' => '#c71585', 'white' => '#ffffff', 'snow' => '#fffafa', 'honeydew' => '#f0fff0', 'mintcream' => '#f5fffa', 'azure' => '#f0ffff', 'aliceblue' => '#f0f8ff', 'ghostwhite' => '#f8f8ff', 'whitesmoke' => '#f5f5f5', 'seashell' => '#fff5ee', 'beige' => '#f5f5dc', 'oldlace' => '#fdf5e6', 'floralwhite' => '#fffaf0', 'ivory' => '#fffff0', 'antiquewhite' => '#faebd7', 'linen' => '#faf0e6', 'lavenderblush' => '#fff0f5', 'mistyrose' => '#ffe4e1', 'gainsboro' => '#dcdcdc', 'lightgray' => '#d3d3d3', 'silver' => '#c0c0c0', 'darkgray' => '#a9a9a9', 'gray' => '#808080', 'dimgray' => '#696969', 'lightslategray' => '#778899', 'slategray' => '#708090', 'darkslategray' => '#2f4f4f', 'black' => '#000000', 'cornsilk' => '#fff8dc', 'blanchedalmond' => '#ffebcd', 'bisque' => '#ffe4c4', 'navajowhite' => '#ffdead', 'wheat' => '#f5deb3', 'burlywood' => '#deb887', 'tan' => '#d2b48c', 'rosybrown' => '#bc8f8f', 'sandybrown' => '#f4a460', 'goldenrod' => '#daa520', 'peru' => '#cd853f', 'chocolate' => '#d2691e', 'saddlebrown' => '#8b4513', 'sienna' => '#a0522d', 'brown' => '#a52a2a', 'maroon' => '#800000', ]; /** * Decode html color names * * @param mixed $input * @return ImageInterface|ColorInterface */ public function decode($input): ImageInterface|ColorInterface { if (! is_string($input)) { throw new DecoderException('Unable to decode input'); } if (!array_key_exists(strtolower($input), static::$names)) { throw new DecoderException('Unable to decode input'); } return parent::decode(static::$names[strtolower($input)]); } } src/Colors/Rgb/Decoders/StringColorDecoder.php000064400000003350150731645660015320 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.php000064400000001167150731645660016357 0ustar00channels; } public function channel(string $classname): ColorChannelInterface { $channels = array_filter($this->channels(), function (ColorChannelInterface $channel) use ($classname) { return get_class($channel) == $classname; }); if (count($channels) == 0) { throw new ColorException('Channel ' . $classname . ' could not be found.'); } return reset($channels); } public function normalize(): array { return array_map(function (ColorChannelInterface $channel) { return $channel->normalize(); }, $this->channels()); } } src/Drivers/Abstract/AbstractFont.php000064400000004715150731645660013643 0ustar00size = $size; return $this; } public function getSize(): float { return $this->size; } public function angle(float $angle): FontInterface { $this->angle = $angle; return $this; } public function getAngle(): float { return $this->angle; } public function filename(string $filename): FontInterface { $this->filename = $filename; return $this; } public function getFilename(): ?string { return $this->filename; } public function hasFilename(): bool { return !is_null($this->filename) && is_file($this->filename); } public function color($color): FontInterface { $this->color = $color; return $this; } public function getColor(): ColorInterface { return $this->handleInput($this->color); } public function align(string $align): FontInterface { $this->align = $align; return $this; } public function getValign(): string { return $this->valign; } public function valign(string $valign): FontInterface { $this->valign = $valign; return $this; } public function getAlign(): string { return $this->align; } public function lineHeight(float $height): FontInterface { $this->lineHeight = $height; return $this; } public function getLineHeight(): float { return $this->lineHeight; } public function leadingInPixels(): int { return intval(round($this->fontSizeInPixels() * $this->getLineHeight())); } public function capHeight(): int { return $this->getBoxSize('T')->height(); } public function fontSizeInPixels(): int { return $this->getBoxSize('Hy')->height(); } } src/Drivers/Abstract/AbstractImage.php000064400000027142150731645660013756 0ustar00width(), $this->height()); } public function modify(ModifierInterface $modifier): ImageInterface { return $modifier->apply($this); } public function encode(EncoderInterface $encoder): EncodedImage { return $encoder->encode($this); } public function toJpeg(int $quality = 75): EncodedImage { return $this->encode( $this->resolveDriverClass('Encoders\JpegEncoder', $quality) ); } public function toJpg(int $quality = 75): EncodedImage { return $this->toJpeg($quality); } public function toWebp(int $quality = 75): EncodedImage { return $this->encode( $this->resolveDriverClass('Encoders\WebpEncoder', $quality) ); } public function toGif(int $color_limit = 0): EncodedImage { return $this->encode( $this->resolveDriverClass('Encoders\GifEncoder', $color_limit) ); } public function toPng(int $color_limit = 0): EncodedImage { return $this->encode( $this->resolveDriverClass('Encoders\PngEncoder', $color_limit) ); } public function toBitmap(int $color_limit = 0): EncodedImage { return $this->encode( $this->resolveDriverClass('Encoders\BmpEncoder', $color_limit) ); } public function toBmp(int $color_limit = 0): EncodedImage { return $this->toBitmap($color_limit); } public function toAvif(int $quality = 75): EncodedImage { return $this->encode( $this->resolveDriverClass('Encoders\AvifEncoder', $quality) ); } public function greyscale(): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\GreyscaleModifier') ); } public function invert(): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\InvertModifier') ); } public function brightness(int $level): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\BrightnessModifier', $level) ); } public function contrast(int $level): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\ContrastModifier', $level) ); } public function gamma(float $gamma): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\GammaModifier', $gamma) ); } public function colorize(int $red = 0, int $green = 0, int $blue = 0): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\ColorizeModifier', $red, $green, $blue) ); } public function blur(int $amount = 5): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\BlurModifier', $amount) ); } public function rotate(float $angle, $background = 'ffffff'): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\RotateModifier', $angle, $background) ); } /** * Creates a vertical mirror image * * @return ImageInterface */ public function flip(): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\FlipModifier') ); } /** * Creates a horizontal mirror image * * @return ImageInterface */ public function flop(): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\FlopModifier') ); } public function place($element, string $position = 'top-left', int $offset_x = 0, int $offset_y = 0): ImageInterface { return $this->modify( $this->resolveDriverClass( 'Modifiers\PlaceModifier', $element, $position, $offset_x, $offset_y ) ); } public function fill($color, ?int $x = null, ?int $y = null): ImageInterface { $color = $this->handleInput($color); $position = (is_null($x) && is_null($y)) ? null : new Point($x, $y); return $this->modify( $this->resolveDriverClass( 'Modifiers\FillModifier', $color, $position ) ); } public function pixelate(int $size): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\PixelateModifier', $size) ); } public function sharpen(int $amount = 10): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\SharpenModifier', $amount) ); } /** * {@inheritdoc} * * @see ImageInterface::pickColors() */ public function pickColors(int $x, int $y): CollectionInterface { $colors = new Collection(); foreach ($this as $key => $frame) { $colors->push($this->pickColor($x, $y, $key)); } return $colors; } public function text(string $text, int $x, int $y, ?callable $init = null): ImageInterface { $font = $this->runCallback($init, $this->resolveDriverClass('Font')); $modifier = $this->resolveDriverClass('Modifiers\TextWriter', new Point($x, $y), $font, $text); return $this->modify($modifier); } public function drawPixel(int $x, int $y, $color = null): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\DrawPixelModifier', new Point($x, $y), $color) ); } public function drawRectangle(int $x, int $y, ?callable $init = null): ImageInterface { $rectangle = $this->runCallback($init, new Rectangle(0, 0)); $modifier = $this->resolveDriverClass('Modifiers\DrawRectangleModifier', new Point($x, $y), $rectangle); return $this->modify($modifier); } public function drawEllipse(int $x, int $y, ?callable $init = null): ImageInterface { $ellipse = $this->runCallback($init, new Ellipse(0, 0)); $modifier = $this->resolveDriverClass('Modifiers\DrawEllipseModifier', new Point($x, $y), $ellipse); return $this->modify($modifier); } public function drawCircle(int $x, int $y, ?callable $init = null): ImageInterface { $circle = $this->runCallback($init, new Circle(0)); $modifier = $this->resolveDriverClass('Modifiers\DrawEllipseModifier', new Point($x, $y), $circle); return $this->modify($modifier); } public function drawLine(callable $init = null): ImageInterface { $line = $this->runCallback($init, new Line(new Point(), new Point())); $modifier = $this->resolveDriverClass('Modifiers\DrawLineModifier', $line->getStart(), $line); return $this->modify($modifier); } public function drawPolygon(callable $init = null): ImageInterface { $polygon = $this->runCallback($init, new Polygon()); $modifier = $this->resolveDriverClass('Modifiers\DrawPolygonModifier', $polygon); return $this->modify($modifier); } public function resize(?int $width = null, ?int $height = null): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\ResizeModifier', $width, $height) ); } public function resizeDown(?int $width = null, ?int $height = null): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\ResizeDownModifier', $width, $height) ); } public function scale(?int $width = null, ?int $height = null): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\ScaleModifier', $width, $height) ); } public function scaleDown(?int $width = null, ?int $height = null): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\ScaleDownModifier', $width, $height) ); } public function fit(int $width, int $height, string $position = 'center'): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\FitModifier', $width, $height, $position) ); } public function fitDown(int $width, int $height, string $position = 'center'): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\FitDownModifier', $width, $height, $position) ); } public function pad(int $width, int $height, $background = 'ffffff', string $position = 'center'): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\PadModifier', $width, $height, $background, $position) ); } public function padDown( int $width, int $height, $background = 'ffffff', string $position = 'center' ): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\PadDownModifier', $width, $height, $background, $position) ); } public function crop( int $width, int $height, int $offset_x = 0, int $offset_y = 0, string $position = 'top-left' ): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\CropModifier', $width, $height, $offset_x, $offset_y, $position) ); } public function removeAnimation(int|string $position = 0): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\RemoveAnimationModifier', $position) ); } public function setExif(array $data): ImageInterface { $this->exif = new Collection($data); return $this; } public function exif(?string $query = null): mixed { if (!isset($this->exif)) { return new Collection(); } return is_null($query) ? $this->exif : $this->exif->get($query); } public function setResolution(float $x, float $y): ImageInterface { return $this->modify( $this->resolveDriverClass('Modifiers\ResolutionModifier', $x, $y) ); } public function destroy(): void { $this->modify( $this->resolveDriverClass('Modifiers\DestroyModifier') ); } } src/Drivers/Abstract/AbstractInputHandler.php000064400000003162150731645660015325 0ustar00decoders = count($decoders) ? $decoders : $this->decoders; } /** * {@inheritdoc} * * @see InputHandlerInterface::handle() */ public function handle($input): ImageInterface|ColorInterface { return $this->chain()->handle($input); } /** * Stack the decoder array into a nested decoder object * * @return AbstractDecoder */ protected function chain(): AbstractDecoder { if (count($this->decoders) == 0) { throw new DecoderException('No decoders found in ' . get_class($this)); } // get instance of last decoder in stack list($classname) = array_slice(array_reverse($this->decoders), 0, 1); $chain = new $classname(); // build decoder chain foreach (array_slice(array_reverse($this->decoders), 1) as $classname) { $chain = new $classname($chain); } return $chain; } } src/Drivers/Abstract/AbstractTextWriter.php000064400000003732150731645660015054 0ustar00font; } protected function getPosition(): Point { return $this->position; } /** * Build TextBlock object from text string and align every line * according to text writers font object and position. * * @return TextBlock */ public function getAlignedTextBlock(): TextBlock { $lines = new TextBlock($this->text); $position = $this->getPosition(); $font = $this->getFont(); $boundingBox = $lines->getBoundingBox($font, $position); $pivot = $boundingBox->last(); $leading = $font->leadingInPixels(); $blockWidth = $lines->longestLine()->widthInFont($font); $x = $pivot->x(); $y = $font->hasFilename() ? $pivot->y() + $font->capHeight() : $pivot->y(); $x_adjustment = 0; foreach ($lines as $line) { $x_adjustment = $font->getAlign() == 'left' ? 0 : $blockWidth - $line->widthInFont($font); $x_adjustment = $font->getAlign() == 'right' ? intval(round($x_adjustment)) : $x_adjustment; $x_adjustment = $font->getAlign() == 'center' ? intval(round($x_adjustment / 2)) : $x_adjustment; $position = new Point($x + $x_adjustment, $y); $position->rotate($font->getAngle(), $pivot); $line->setPosition($position); $y += $leading; } return $lines; } } src/Drivers/Abstract/Decoders/AbstractDecoder.php000064400000007701150731645660016030 0ustar00decode($input); } catch (DecoderException $e) { if (!$this->hasSuccessor()) { throw new DecoderException($e->getMessage()); } return $this->successor->handle($input); } return $decoded; } protected function hasSuccessor(): bool { return $this->successor !== null; } /** * Return media type (MIME) of given input * * @param string $input * @return string */ protected function mediaType(string $input): string { $pointer = $this->buildFilePointer($input); $type = mime_content_type($pointer); fclose($pointer); return $type; } protected function decodeExifData(string $image_data): array { if (!function_exists('exif_read_data')) { return []; } try { $pointer = $this->buildFilePointer($image_data); $data = @exif_read_data($pointer, null, true); fclose($pointer); } catch (Exception $e) { $data = []; } return is_array($data) ? $data : []; } protected function isValidBase64($input): bool { if (!is_string($input)) { return false; } return base64_encode(base64_decode($input)) === str_replace(["\n", "\r"], '', $input); } /** * Parse data uri * * @param mixed $value * @return object */ protected function parseDataUri($value): object { $pattern = "/^data:(?P\w+\/[-+.\w]+)?" . "(?P(;[-\w]+=[-\w]+)*)(?P;base64)?,(?P.*)/"; $result = preg_match($pattern, $value, $matches); return new class ($matches, $result) { private $matches; private $result; public function __construct($matches, $result) { $this->matches = $matches; $this->result = $result; } public function isValid(): bool { return (bool) $this->result; } public function mediaType(): ?string { if (isset($this->matches['mediatype']) && !empty($this->matches['mediatype'])) { return $this->matches['mediatype']; } return null; } public function hasMediaType(): bool { return !empty($this->mediaType()); } public function parameters(): array { if (isset($this->matches['parameters']) && !empty($this->matches['parameters'])) { return explode(';', trim($this->matches['parameters'], ';')); } return []; } public function isBase64Encoded(): bool { if (isset($this->matches['base64']) && $this->matches['base64'] === ';base64') { return true; } return false; } public function data(): ?string { if (isset($this->matches['data']) && !empty($this->matches['data'])) { return $this->matches['data']; } return null; } }; } } src/Drivers/Abstract/Encoders/AbstractEncoder.php000064400000001370150731645660016050 0ustar00quality = $quality; return $this; } public function getQuality(): int { return $this->quality; } } src/Drivers/Abstract/Modifiers/AbstractDrawModifier.php000064400000005214150731645660017225 0ustar00drawable; } protected function getBackgroundColor(): ColorInterface { try { $color = $this->handleInput($this->drawable->getBackgroundColor()); } catch (DecoderException $e) { return $this->handleInput('transparent'); } return $color; } protected function getBorderColor(): ColorInterface { try { $color = $this->handleInput($this->drawable->getBorderColor()); } catch (DecoderException $e) { return $this->handleInput('transparent'); } return $color; } public function polygon(): Polygon { if (!is_a($this->drawable(), Polygon::class)) { throw new GeometryException( 'Shape mismatch. Excepted Polygon::class, found ' . get_class($this->drawable()) ); } return $this->drawable(); } public function ellipse(): Ellipse { if (!is_a($this->drawable(), Ellipse::class)) { throw new GeometryException( 'Shape mismatch. Excepted Ellipse::class, found ' . get_class($this->drawable()) ); } return $this->drawable(); } public function line(): Line { if (!is_a($this->drawable(), Line::class)) { throw new GeometryException( 'Shape mismatch. Excepted Line::class, found ' . get_class($this->drawable()) ); } return $this->drawable(); } public function rectangle(): Rectangle { if (!is_a($this->drawable(), Rectangle::class)) { throw new GeometryException( 'Shape mismatch. Excepted Rectangle::class, found ' . get_class($this->drawable()) ); } return $this->drawable(); } } src/Drivers/Abstract/Modifiers/AbstractFitModifier.php000064400000001623150731645660017052 0ustar00size(); $crop = new Rectangle($this->width, $this->height); $crop = $crop->contain( $imagesize->width(), $imagesize->height() )->alignPivotTo($imagesize, $this->position); return $crop; } protected function getResizeSize(SizeInterface $size): SizeInterface { return $size->scale($this->width, $this->height); } } src/Drivers/Abstract/Modifiers/AbstractPadModifier.php000064400000001626150731645660017037 0ustar00size() ->contain($this->width, $this->height) ->alignPivotTo($this->getResizeSize($image), $this->position); } protected function getResizeSize(ImageInterface $image): SizeInterface { return new Rectangle($this->width, $this->height); } } src/Drivers/Abstract/Modifiers/AbstractRemoveAnimationModifier.php000064400000001702150731645660021423 0ustar00frame($position); } if (preg_match("/^(?P[0-9]{1,3})%$/", $position, $matches) != 1) { throw new InputException( 'Input value of Image::removeAnimation() 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->frame($position); } } src/Drivers/Abstract/Modifiers/AbstractRotateModifier.php000064400000001755150731645660017574 0ustar00angle, 360); } protected function backgroundColor(): ColorInterface { try { return $this->handleInput($this->background); } catch (DecoderException $e) { throw new TypeException("rotate(): Argument #2 must be a color value."); } } } src/Drivers/Gd/Decoders/Base64ImageDecoder.php000064400000001132150731645660015033 0ustar00isValidBase64($input)) { throw new DecoderException('Unable to decode input'); } return parent::decode(base64_decode($input)); } } src/Drivers/Gd/Decoders/BinaryImageDecoder.php000064400000005153150731645660015242 0ustar00mediaType($input) == 'image/gif') { return $this->decodeGif($input); // decode (animated) gif } $gd = $this->coreFromString($input); // build image instance $image = new Image(new Collection([new Frame($gd)])); $image->setExif($this->decodeExifData($input)); // fix image orientation return match ($image->exif('IFD0.Orientation')) { 2 => $image->flip(), 3 => $image->rotate(180), 4 => $image->rotate(180)->flip(), 5 => $image->rotate(270)->flip(), 6 => $image->rotate(270), 7 => $image->rotate(90)->flip(), 8 => $image->rotate(90), default => $image }; } private function coreFromString(string $input): GdImage { $gd = @imagecreatefromstring($input); if ($gd === false) { throw new DecoderException('Unable to decode input'); } if (!imageistruecolor($gd)) { imagepalettetotruecolor($gd); } imagesavealpha($gd, true); return $gd; } private function decodeGif(string $input): ImageInterface { $gif = GifDecoder::decode($input); if (!$gif->isAnimated()) { return new Image( new Collection([new Frame( $this->coreFromString($input) )]), ); } $image = new Image(new Collection()); $image->setLoops($gif->getMainApplicationExtension()?->getLoops()); $splitter = GifSplitter::create($gif)->split(); $delays = $splitter->getDelays(); foreach ($splitter->coalesceToResources() as $key => $gd) { $image->addFrame((new Frame($gd))->setDelay($delays[$key] / 100)); } return $image; } } src/Drivers/Gd/Decoders/ColorObjectDecoder.php000064400000001201150731645660015246 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.php000064400000001532150731645660015507 0ustar00getRealPath()); } } src/Drivers/Gd/Encoders/AvifEncoder.php000064400000001303150731645660013755 0ustar00quality = $quality; } public function encode(ImageInterface $image): EncodedImage { $data = $this->getBuffered(function () use ($image) { imageavif($image->frame()->core(), null, $this->quality); }); return new EncodedImage($data, 'image/avif'); } } src/Drivers/Gd/Encoders/BmpEncoder.php000064400000001500150731645660013605 0ustar00modify(new LimitColorsModifier($this->color_limit)); $data = $this->getBuffered(function () use ($image) { imagebmp($image->frame()->core(), null, false); }); return new EncodedImage($data, 'image/bmp'); } } src/Drivers/Gd/Encoders/GifEncoder.php000064400000002625150731645660013605 0ustar00isAnimated()) { return $this->encodeAnimated($image); } $image = $image->modify(new LimitColorsModifier($this->color_limit)); $data = $this->getBuffered(function () use ($image) { imagegif($image->frame()->core()); }); return new EncodedImage($data, 'image/gif'); } protected function encodeAnimated(ImageInterface $image): EncodedImage { $builder = GifBuilder::canvas( $image->width(), $image->height(), $image->loops() ); foreach ($image as $frame) { $builder->addFrame( $this->encode($frame->toImage()), $frame->delay() ); } return new EncodedImage($builder->encode(), 'image/gif'); } } src/Drivers/Gd/Encoders/JpegEncoder.php000064400000001303150731645660013755 0ustar00quality = $quality; } public function encode(ImageInterface $image): EncodedImage { $data = $this->getBuffered(function () use ($image) { imagejpeg($image->frame()->core(), null, $this->quality); }); return new EncodedImage($data, 'image/jpeg'); } } src/Drivers/Gd/Encoders/PngEncoder.php000064400000001475150731645660013626 0ustar00modify(new LimitColorsModifier($this->color_limit)); $data = $this->getBuffered(function () use ($image) { imagepng($image->frame()->core(), null, -1); }); return new EncodedImage($data, 'image/png'); } } src/Drivers/Gd/Encoders/WebpEncoder.php000064400000001303150731645660013765 0ustar00quality = $quality; } public function encode(ImageInterface $image): EncodedImage { $data = $this->getBuffered(function () use ($image) { imagewebp($image->frame()->core(), null, $this->quality); }); return new EncodedImage($data, 'image/webp'); } } src/Drivers/Gd/Factory.php000064400000004501150731645660011440 0ustar00newCore($width, $height)) ]) ); } /** * {@inheritdoc} * * @see FactoryInterface::newAnimation() */ public function newAnimation(callable $callback): ImageInterface { $frames = new Collection(); $animation = new class ($frames) extends Factory { public function __construct(public Collection $frames) { // } public function add($source, float $delay = 1): self { $this->frames->push( $this->handleInput($source) ->frame() ->setDelay($delay) ); return $this; } }; $callback($animation); return new Image($frames); } /** * {@inheritdoc} * * @see FactoryInterface::newCore() */ public function newCore(int $width, int $height, ?ColorInterface $background = null) { $core = imagecreatetruecolor($width, $height); imagesavealpha($core, true); $color = match (is_null($background)) { true => imagecolorallocatealpha($core, 255, 0, 255, 127), default => $this->allocateColor($core, $background), }; imagealphablending($core, false); imagefill($core, 0, 0, $color); if ($background && $background->channel(Alpha::class)->value() == 0) { imagecolortransparent($core, $color); } return $core; } } src/Drivers/Gd/Font.php000064400000003773150731645660010751 0ustar00hasFilename()) { // calculate box size from gd font $box = new Rectangle(0, 0); $chars = mb_strlen($text); if ($chars > 0) { $box->setWidth($chars * $this->getGdFontWidth()); $box->setHeight($this->getGdFontHeight()); } return $box; } // calculate box size from font file with angle 0 $box = imageftbbox( $this->getSize(), 0, $this->getFilename(), $text ); // build polygon from points $polygon = new Polygon(); $polygon->addPoint(new Point($box[6], $box[7])); $polygon->addPoint(new Point($box[4], $box[5])); $polygon->addPoint(new Point($box[2], $box[3])); $polygon->addPoint(new Point($box[0], $box[1])); return $polygon; } public function getGdFont(): int { if (is_numeric($this->filename)) { return $this->filename; } return 1; } protected function getGdFontWidth(): int { return $this->getGdFont() + 4; } protected function getGdFontHeight(): int { switch ($this->getGdFont()) { case 2: return 14; case 3: return 14; case 4: return 16; case 5: return 16; default: case 1: return 8; } } } src/Drivers/Gd/Frame.php000064400000004101150731645660011057 0ustar00core = $core; return $this; } public function core(): GdImage { return $this->core; } public function unsetCore(): void { unset($this->core); } public function size(): SizeInterface { return new Rectangle(imagesx($this->core), imagesy($this->core)); } public function delay(): float { return $this->delay; } public function setDelay(float $delay): FrameInterface { $this->delay = $delay; return $this; } public function dispose(): int { return $this->dispose; } public function setDispose(int $dispose): FrameInterface { $this->dispose = $dispose; return $this; } public function setOffset(int $left, int $top): FrameInterface { $this->offset_left = $left; $this->offset_top = $top; return $this; } public function offsetLeft(): int { return $this->offset_left; } public function setOffsetLeft(int $offset): FrameInterface { $this->offset_left = $offset; return $this; } public function offsetTop(): int { return $this->offset_top; } public function setOffsetTop(int $offset): FrameInterface { $this->offset_top = $offset; return $this; } public function toImage(): ImageInterface { return new Image(new Collection([$this])); } } src/Drivers/Gd/Image.php000064400000010571150731645660011057 0ustar00frames; } public function setFrames(Collection $frames): ImageInterface { $this->frames = $frames; return $this; } public function getIterator(): Traversable { return $this->frames; } public function count(): int { return $this->frames->count(); } public function isAnimated(): bool { return $this->count() > 1; } public function loops(): int { return $this->loops; } public function setLoops(int $count): self { $this->loops = $count; return $this; } public function frame(int $position = 0): FrameInterface { if ($frame = $this->frames->get($position)) { return $frame; } throw new AnimationException('Frame #' . $position . ' is not be found in the image.'); } public function addFrame(FrameInterface $frame): ImageInterface { $this->frames->push($frame); return $this; } public function width(): int { return imagesx($this->frame()->core()); } public function height(): int { return imagesy($this->frame()->core()); } /** * {@inheritdoc} * * @see ImageInterface::resolution() */ public function resolution(): ResolutionInterface { return new Resolution(...imageresolution($this->frame()->core())); } public function pickColor(int $x, int $y, int $frame_key = 0): ColorInterface { $gd = $this->frame($frame_key)->core(); $index = @imagecolorat($gd, $x, $y); if ($index === false) { throw new GeometryException('The specified position is not in the valid image area.'); } $colors = imagecolorsforindex($gd, $index); return $this->arrayToColor($colors); } /** * {@inheritdoc} * * @see ImageInterface::getColorspace() */ public function colorspace(): ColorspaceInterface { return new RgbColorspace(); } /** * {@inheritdoc} * * @see ImageInterface::setColorspace() */ public function setColorspace(string|ColorspaceInterface $target): ImageInterface { if (is_string($target) && !in_array($target, ['rgb', RgbColorspace::class])) { throw new NotSupportedException('Only RGB colorspace is supported with GD driver.'); } if (is_object($target) && !is_a($target, RgbColorspace::class)) { throw new NotSupportedException('Only RGB colorspace is supported with GD driver.'); } return $this; } /** * {@inheritdoc} * * @see ImageInterface::setProfile() */ public function setProfile(string|ProfileInterface $input): ImageInterface { throw new NotSupportedException('Color profiles are not supported by GD driver.'); } /** * {@inheritdoc} * * @see ImageInterface::profile() */ public function profile(): ProfileInterface { throw new NotSupportedException('Color profiles are not supported by GD driver.'); } /** * {@inheritdoc} * * @see ImageInterface::removeProfile() */ public function removeProfile(): ImageInterface { throw new NotSupportedException('Color profiles are not supported by GD driver.'); } } src/Drivers/Gd/InputHandler.php000064400000003075150731645660012433 0ustar00blurFrame($frame); } return $image; } protected function blurFrame(FrameInterface $frame): void { for ($i = 0; $i < $this->amount; $i++) { imagefilter($frame->core(), IMG_FILTER_GAUSSIAN_BLUR); } } } src/Drivers/Gd/Modifiers/BrightnessModifier.php000064400000001034150731645660015537 0ustar00core(), IMG_FILTER_BRIGHTNESS, intval($this->level * 2.55)); } return $image; } } src/Drivers/Gd/Modifiers/ColorizeModifier.php000064400000001406150731645660015220 0ustar00red * 2.55); $green = round($this->green * 2.55); $blue = round($this->blue * 2.55); foreach ($image as $frame) { imagefilter($frame->core(), IMG_FILTER_COLORIZE, $red, $green, $blue); } return $image; } } src/Drivers/Gd/Modifiers/ContrastModifier.php000064400000001020150731645660015217 0ustar00core(), IMG_FILTER_CONTRAST, ($this->level * -1)); } return $image; } } src/Drivers/Gd/Modifiers/CropModifier.php000064400000004304150731645660014335 0ustar00width, $this->height); $crop->align($this->position); $crop->alignPivotTo($image->size(), $this->position); foreach ($image as $frame) { $this->cropFrame($frame, $crop); } return $image; } protected function cropFrame(FrameInterface $frame, SizeInterface $resizeTo): void { // create new image $modified = $this->imageFactory()->newCore( $resizeTo->width(), $resizeTo->height() ); // get original image $original = $frame->core(); // preserve transparency $transIndex = imagecolortransparent($original); if ($transIndex != -1) { $rgba = imagecolorsforindex($modified, $transIndex); $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); imagefill($modified, 0, 0, $transColor); imagecolortransparent($modified, $transColor); } // copy content from resource imagecopyresampled( $modified, $original, 0, 0, $resizeTo->pivot()->x() + $this->offset_x, $resizeTo->pivot()->y() + $this->offset_y, $resizeTo->width(), $resizeTo->height(), $resizeTo->width(), $resizeTo->height(), ); // set new content as recource $frame->setCore($modified); } } src/Drivers/Gd/Modifiers/DestroyModifier.php000064400000000617150731645660015066 0ustar00unsetCore(); } return $image; } } src/Drivers/Gd/Modifiers/DrawEllipseModifier.php000064400000004107150731645660015646 0ustar00mapFrames(function ($frame) { if ($this->ellipse()->hasBorder()) { // slightly smaller ellipse to keep 1px bordered edges clean if ($this->ellipse()->hasBackgroundColor()) { imagefilledellipse( $frame->core(), $this->position->x(), $this->position->y(), $this->ellipse()->getWidth() - 1, $this->ellipse()->getHeight() - 1, $this->colorToInteger($this->getBackgroundColor()) ); } imagesetthickness($frame->core(), $this->ellipse()->getBorderSize()); // gd's imageellipse ignores imagesetthickness so i use // imagearc with 360 degrees instead. imagearc( $frame->core(), $this->position->x(), $this->position->y(), $this->ellipse()->getWidth(), $this->ellipse()->getHeight(), 0, 360, $this->colorToInteger($this->getBorderColor()) ); } else { imagefilledellipse( $frame->core(), $this->position->x(), $this->position->y(), $this->ellipse()->getWidth(), $this->ellipse()->getHeight(), $this->colorToInteger($this->getBackgroundColor()) ); } }); } } src/Drivers/Gd/Modifiers/DrawLineModifier.php000064400000001547150731645660015145 0ustar00mapFrames(function ($frame) { imageline( $frame->core(), $this->line()->getStart()->x(), $this->line()->getStart()->y(), $this->line()->getEnd()->x(), $this->line()->getEnd()->y(), $this->colorToInteger($this->getBackgroundColor()) ); }); } } src/Drivers/Gd/Modifiers/DrawPixelModifier.php000064400000001646150731645660015337 0ustar00handleInput($this->color); return $image->mapFrames(function ($frame) use ($color) { imagesetpixel( $frame->core(), $this->position->x(), $this->position->y(), $this->colorToInteger($color) ); }); } } src/Drivers/Gd/Modifiers/DrawPolygonModifier.php000064400000002554150731645660015704 0ustar00mapFrames(function ($frame) { if ($this->polygon()->hasBackgroundColor()) { imagefilledpolygon( $frame->core(), $this->polygon()->toArray(), $this->colorToInteger($this->getBackgroundColor()) ); } if ($this->polygon()->hasBorder()) { imagesetthickness($frame->core(), $this->polygon()->getBorderSize()); imagepolygon( $frame->core(), $this->polygon()->toArray(), $this->polygon()->count(), $this->colorToInteger($this->getBorderColor()) ); } }); } } src/Drivers/Gd/Modifiers/DrawRectangleModifier.php000064400000003240150731645660016152 0ustar00mapFrames(function ($frame) { // draw background if ($this->rectangle()->hasBackgroundColor()) { imagefilledrectangle( $frame->core(), $this->position->x(), $this->position->y(), $this->position->x() + $this->rectangle()->bottomRightPoint()->x(), $this->position->y() + $this->rectangle()->bottomRightPoint()->y(), $this->colorToInteger($this->getBackgroundColor()) ); } if ($this->rectangle()->hasBorder()) { // draw border imagesetthickness($frame->core(), $this->rectangle()->getBorderSize()); imagerectangle( $frame->core(), $this->position->x(), $this->position->y(), $this->position->x() + $this->rectangle()->bottomRightPoint()->x(), $this->position->y() + $this->rectangle()->bottomRightPoint()->y(), $this->colorToInteger($this->getBorderColor()) ); } }); return $image; } } src/Drivers/Gd/Modifiers/FillModifier.php000064400000003113150731645660014315 0ustar00colorToInteger($this->color); foreach ($image as $frame) { if ($this->hasPosition()) { $this->floodFillWithColor($frame, $color); } else { $this->fillAllWithColor($frame, $color); } } return $image; } protected function floodFillWithColor(Frame $frame, int $color): void { imagefill( $frame->core(), $this->position->x(), $this->position->y(), $color ); } protected function fillAllWithColor(Frame $frame, int $color): void { imagealphablending($frame->core(), true); imagefilledrectangle( $frame->core(), 0, 0, $frame->size()->width() - 1, $frame->size()->height() - 1, $color ); } protected function hasPosition(): bool { return !empty($this->position); } } src/Drivers/Gd/Modifiers/FitDownModifier.php000064400000000456150731645660015010 0ustar00scaleDown($this->width, $this->height); } } src/Drivers/Gd/Modifiers/FitModifier.php000064400000003646150731645660014164 0ustar00getCropSize($image); $resize = $this->getResizeSize($crop); foreach ($image as $frame) { $this->modifyFrame($frame, $crop, $resize); } return $image; } protected function modifyFrame(FrameInterface $frame, SizeInterface $crop, SizeInterface $resize): void { // create new image $modified = $this->imageFactory()->newCore( $resize->width(), $resize->height() ); // get original image $original = $frame->core(); // preserve transparency $transIndex = imagecolortransparent($original); if ($transIndex != -1) { $rgba = imagecolorsforindex($modified, $transIndex); $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); imagefill($modified, 0, 0, $transColor); imagecolortransparent($modified, $transColor); } // copy content from resource imagecopyresampled( $modified, $original, 0, 0, $crop->pivot()->x(), $crop->pivot()->y(), $resize->width(), $resize->height(), $crop->width(), $crop->height() ); // set new content as resource $frame->setCore($modified); } } src/Drivers/Gd/Modifiers/FlipModifier.php000064400000000645150731645660014330 0ustar00core(), IMG_FLIP_VERTICAL); } return $image; } } src/Drivers/Gd/Modifiers/FlopModifier.php000064400000000647150731645660014340 0ustar00core(), IMG_FLIP_HORIZONTAL); } return $image; } } src/Drivers/Gd/Modifiers/GammaModifier.php000064400000000774150731645660014463 0ustar00core(), 1, $this->gamma); } return $image; } } src/Drivers/Gd/Modifiers/GreyscaleModifier.php000064400000000657150731645660015357 0ustar00core(), IMG_FILTER_GRAYSCALE); } return $image; } } src/Drivers/Gd/Modifiers/InvertModifier.php000064400000000651150731645660014702 0ustar00core(), IMG_FILTER_NEGATE); } return $image; } } src/Drivers/Gd/Modifiers/LimitColorsModifier.php000064400000003206150731645660015672 0ustar00limit === 0) { return $image; } // limit is over threshold: no reduction if ($this->limit > $this->threshold) { return $image; } $width = $image->width(); $height = $image->height(); foreach ($image as $frame) { // create empty gd $reduced = imagecreatetruecolor($width, $height); // create matte $matte = imagecolorallocatealpha($reduced, 255, 255, 255, 127); // fill with matte imagefill($reduced, 0, 0, $matte); imagealphablending($reduced, false); // set transparency and get transparency index imagecolortransparent($reduced, $matte); // copy original image imagecopy($reduced, $frame->core(), 0, 0, 0, 0, $width, $height); // reduce limit by one to include possible transparency in palette $limit = imagecolortransparent($frame->core()) === -1 ? $this->limit : $this->limit - 1; // decrease colors imagetruecolortopalette($reduced, true, $limit); $frame->setCore($reduced); } return $image; } } src/Drivers/Gd/Modifiers/PadDownModifier.php000064400000001342150731645660014765 0ustar00getResizeSize($image); return $image->size() ->contain($resize->width(), $resize->height()) ->alignPivotTo($resize, $this->position); } protected function getResizeSize(ImageInterface $image): SizeInterface { return (new Rectangle($this->width, $this->height)) ->resizeDown($image->width(), $image->height()); } } src/Drivers/Gd/Modifiers/PadModifier.php000064400000004670150731645660014144 0ustar00getCropSize($image); $resize = $this->getResizeSize($image); $background = $this->handleInput($this->background); foreach ($image as $frame) { $this->modify($frame, $crop, $resize, $background); } return $image; } protected function modify( FrameInterface $frame, SizeInterface $crop, SizeInterface $resize, ColorInterface $background ): void { // create new gd image $modified = $this->imageFactory()->newCore( $resize->width(), $resize->height(), $background ); // make image area transparent to keep transparency // even if background-color is set $transparent = imagecolorallocatealpha($modified, 255, 0, 255, 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->core(), $crop->pivot()->x(), $crop->pivot()->y(), 0, 0, $crop->width(), $crop->height(), $frame->size()->width(), $frame->size()->height() ); // set new content as recource $frame->setCore($modified); } } src/Drivers/Gd/Modifiers/PixelateModifier.php000064400000001077150731645660015211 0ustar00core(), IMG_FILTER_PIXELATE, $this->size, true); } return $image; } } src/Drivers/Gd/Modifiers/PlaceModifier.php000064400000003004150731645660014452 0ustar00handleInput($this->element); $position = $this->getPosition($image, $watermark); foreach ($image as $frame) { imagealphablending($frame->core(), true); imagecopy( $frame->core(), $watermark->frame()->core(), $position->x(), $position->y(), 0, 0, $watermark->width(), $watermark->height() ); } return $image; } protected function getPosition(ImageInterface $image, ImageInterface $watermark): PointInterface { $image_size = $image->size()->movePivot($this->position, $this->offset_x, $this->offset_y); $watermark_size = $watermark->size()->movePivot($this->position); return $image_size->relativePositionTo($watermark_size); } } src/Drivers/Gd/Modifiers/RemoveAnimationModifier.php000064400000001370150731645660016527 0ustar00failIfNotClass($image, Image::class); return $image->setFrames(new Collection([ $this->chosenFrame($image, $this->position) ])); } } src/Drivers/Gd/Modifiers/ResizeDownModifier.php000064400000000564150731645660015527 0ustar00size()->resizeDown($this->width, $this->height); } } src/Drivers/Gd/Modifiers/ResizeModifier.php000064400000004124150731645660014673 0ustar00getAdjustedSize($image); foreach ($image as $frame) { $this->resizeFrame($frame, $resizeTo); } return $image; } protected function getAdjustedSize(ImageInterface $image): SizeInterface { return $image->size()->resize($this->width, $this->height); } protected function resizeFrame(FrameInterface $frame, SizeInterface $resizeTo): void { // create new image $modified = imagecreatetruecolor( $resizeTo->width(), $resizeTo->height() ); // get current image $current = $frame->core(); // preserve transparency $transIndex = imagecolortransparent($current); if ($transIndex != -1) { $rgba = imagecolorsforindex($modified, $transIndex); $transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127); imagefill($modified, 0, 0, $transColor); imagecolortransparent($modified, $transColor); } else { imagealphablending($modified, false); imagesavealpha($modified, true); } // copy content from resource imagecopyresampled( $modified, $current, $resizeTo->pivot()->x(), $resizeTo->pivot()->y(), 0, 0, $resizeTo->width(), $resizeTo->height(), $frame->size()->width(), $frame->size()->height() ); // set new content as recource $frame->setCore($modified); } } src/Drivers/Gd/Modifiers/ResolutionModifier.php000064400000001123150731645660015571 0ustar00x)); $y = intval(round($this->y)); foreach ($image as $frame) { imageresolution($frame->core(), $x, $y); } return $image; } } src/Drivers/Gd/Modifiers/RotateModifier.php000064400000007023150731645660014671 0ustar00handleInput($this->background); foreach ($image as $frame) { $this->modify($frame, $background); } return $image; } /** * Apply rotation modification on given frame, given background * color is used for newly create image areas * * @param FrameInterface $frame * @param ColorInterface $background * @return void * @throws RuntimeException * @throws MissingDriverComponentException * @throws ReflectionException */ protected function modify(FrameInterface $frame, ColorInterface $background): void { // get transparent color from frame core $transparent = match ($transparent = imagecolortransparent($frame->core())) { -1 => imagecolorallocatealpha( $frame->core(), $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->core(), $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->core()), imagesy($frame->core()), $container->pivot() ))->align('center') ->valign('center') ->rotate($this->rotationAngle() * -1); // create new gd core $modified = $this->imageFactory()->newCore( imagesx($rotated), imagesy($rotated), $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->setCore($modified); } } src/Drivers/Gd/Modifiers/ScaleDownModifier.php000064400000000562150731645660015313 0ustar00size()->scaleDown($this->width, $this->height); } } src/Drivers/Gd/Modifiers/ScaleModifier.php000064400000000552150731645660014462 0ustar00size()->scale($this->width, $this->height); } } src/Drivers/Gd/Modifiers/SharpenModifier.php000064400000001545150731645660015036 0ustar00matrix(); foreach ($image as $frame) { imageconvolution($frame->core(), $matrix, 1, 0); } return $image; } protected 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/TextWriter.php000064400000003047150731645660014077 0ustar00getAlignedTextBlock(); $font = $this->failIfNotClass($this->getFont(), Font::class); $color = $this->colorToInteger($font->getColor()); foreach ($image as $frame) { if ($this->font->hasFilename()) { foreach ($lines as $line) { imagettftext( $frame->core(), $font->getSize(), $font->getAngle() * (-1), $line->getPosition()->x(), $line->getPosition()->y(), $color, $font->getFilename(), $line ); } } else { foreach ($lines as $line) { imagestring( $frame->core(), $font->getGdFont(), $line->getPosition()->x(), $line->getPosition()->y(), $line, $color ); } } } return $image; } } src/Drivers/Gd/Traits/CanHandleColors.php000064400000006331150731645660014301 0ustar00channel(Red::class)->value(), $color->channel(Green::class)->value(), $color->channel(Blue::class)->value(), $this->convertRange($color->channel(Alpha::class)->value(), 0, 255, 127, 0) ); } /** * Transforms array result from imagecolorsforindex() to Color object * * @param array $values * @return Color */ protected function arrayToColor(array $values): Color { list($r, $g, $b, $a) = array_values($values); // 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); } /** * Transforms GD Library integer color value to RGB color object * * @param int $value * @return Color */ public function integerToColor(int $value): Color { $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); } /** * Transforms given color to the corresponding GD Library integer value * * @param Color $color * @return int */ public function colorToInteger(Color $color): int { $r = $color->red()->value(); $g = $color->green()->value(); $b = $color->blue()->value(); $a = $color->alpha()->value(); // convert alpha value to gd alpha // ([opaque]255-0[transparent]) to ([opaque]0-127[transparent]) $a = (int) static::convertRange($a, 0, 255, 127, 0); return ($a << 24) + ($r << 16) + ($g << 8) + $b; } /** * 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 static 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/Imagick/Decoders/Base64ImageDecoder.php000064400000001137150731645660016052 0ustar00isValidBase64($input)) { throw new DecoderException('Unable to decode input'); } return parent::decode(base64_decode($input)); } } src/Drivers/Imagick/Decoders/BinaryImageDecoder.php000064400000003247150731645660016256 0ustar00readImageBlob($input); } catch (ImagickException $e) { throw new DecoderException('Unable to decode input'); } $imagick = $imagick->coalesceImages(); // fix image orientation switch ($imagick->getImageOrientation()) { case Imagick::ORIENTATION_BOTTOMRIGHT: $imagick->rotateimage("#000", 180); break; case Imagick::ORIENTATION_RIGHTTOP: $imagick->rotateimage("#000", 90); break; case Imagick::ORIENTATION_LEFTBOTTOM: $imagick->rotateimage("#000", -90); break; } // set new orientation in image $imagick->setImageOrientation(Imagick::ORIENTATION_TOPLEFT); $image = new Image($imagick); $image->setLoops($imagick->getImageIterations()); $image->setExif($this->decodeExifData($input)); return $image; } } src/Drivers/Imagick/Decoders/ColorObjectDecoder.php000064400000001206150731645660016265 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.php000064400000001537150731645660016526 0ustar00getRealPath()); } } src/Drivers/Imagick/Encoders/AvifEncoder.php000064400000001776150731645660015005 0ustar00quality = $quality; } public function encode(ImageInterface $image): EncodedImage { $format = 'AVIF'; $compression = Imagick::COMPRESSION_ZIP; $imagick = $image->frame()->core(); $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.php000064400000002021150731645660014616 0ustar00modify(new LimitColorsModifier($this->color_limit)); $imagick = $image->frame()->core(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); return new EncodedImage($imagick->getImagesBlob(), 'image/bmp'); } } src/Drivers/Imagick/Encoders/GifEncoder.php000064400000002575150731645660014623 0ustar00failIfNotClass($image, Image::class); $image = $image->modify(new LimitColorsModifier($this->color_limit)); $imagick = $image->getImagick(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); return new EncodedImage($imagick->getImagesBlob(), 'image/gif'); } } src/Drivers/Imagick/Encoders/JpegEncoder.php000064400000002142150731645660014771 0ustar00quality = $quality; } public function encode(ImageInterface $image): EncodedImage { $format = 'jpeg'; $compression = Imagick::COMPRESSION_JPEG; $imagick = $image->frame()->core(); $imagick->setImageBackgroundColor('white'); $imagick->setBackgroundColor('white'); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); $imagick->setCompressionQuality($this->quality); $imagick->setImageCompressionQuality($this->quality); return new EncodedImage($imagick->getImagesBlob(), 'image/jpeg'); } } src/Drivers/Imagick/Encoders/PngEncoder.php000064400000002022150731645660014625 0ustar00modify(new LimitColorsModifier($this->color_limit)); $imagick = $image->frame()->core(); $imagick->setFormat($format); $imagick->setImageFormat($format); $imagick->setCompression($compression); $imagick->setImageCompression($compression); return new EncodedImage($imagick->getImagesBlob(), 'image/png'); } } src/Drivers/Imagick/Encoders/WebpEncoder.php000064400000002157150731645660015007 0ustar00quality = $quality; } public function encode(ImageInterface $image): EncodedImage { $format = 'webp'; $compression = Imagick::COMPRESSION_ZIP; $imagick = $image->frame()->core(); $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/Factory.php000064400000004566150731645660012465 0ustar00newCore($width, $height)); } /** * {@inheritdoc} * * @see FactoryInterface::newAnimation() */ public function newAnimation(callable $callback): ImageInterface { $imagick = new Imagick(); $imagick->setFormat('gif'); $animation = new class ($imagick) extends Factory { public function __construct(public Imagick $imagick) { // } public function add($source, float $delay = 1): self { $imagick = $this->failIfNotClass( $this->handleInput($source), Image::class, )->getImagick(); $imagick->setImageDelay($delay * 100); $this->imagick->addImage($imagick); return $this; } }; $callback($animation); return new Image($animation->imagick); } /** * {@inheritdoc} * * @see FactoryInterface::newCore() */ public function newCore(int $width, int $height, ?ColorInterface $background = null) { $pixel = $background ? $this->colorToPixel( $background, new Colorspace() ) : new ImagickPixel('rgba(0, 0, 0, 0)'); $imagick = new Imagick(); $imagick->newImage($width, $height, $pixel, 'png'); $imagick->setType(Imagick::IMGTYPE_UNDEFINED); $imagick->setImageType(Imagick::IMGTYPE_UNDEFINED); $imagick->setColorspace(Imagick::COLORSPACE_SRGB); $imagick->setImageResolution(96, 96); return $imagick; } } src/Drivers/Imagick/Font.php000064400000003324150731645660011753 0ustar00hasFilename()) { throw new FontException('No font file specified.'); } $draw = new ImagickDraw(); $draw->setStrokeAntialias(true); $draw->setTextAntialias(true); $draw->setFont($this->getFilename()); $draw->setFontSize($this->getSize()); $draw->setTextAlignment(Imagick::ALIGN_LEFT); if ($colorspace) { $draw->setFillColor( $this->colorToPixel($this->getColor(), $colorspace) ); } return $draw; } /** * Calculate box size of current font * * @return Polygon */ public function getBoxSize(string $text): Polygon { // no text - no box size if (mb_strlen($text) === 0) { return (new Rectangle(0, 0)); } $draw = $this->toImagickDraw(); $draw->setStrokeAntialias(true); $draw->setTextAntialias(true); $dimensions = (new Imagick())->queryFontMetrics($draw, $text); return (new Rectangle( intval(round($dimensions['textWidth'])), intval(round($dimensions['ascender'] + $dimensions['descender'])), )); } } src/Drivers/Imagick/Frame.php000064400000004063150731645660012100 0ustar00core = $core; return $this; } public function core(): Imagick { return $this->core; } public function size(): SizeInterface { return new Rectangle( $this->core->getImageWidth(), $this->core->getImageHeight() ); } public function delay(): float { return $this->core->getImageDelay() / 100; } public function setDelay(float $delay): FrameInterface { $this->core->setImageDelay(intval(round($delay * 100))); return $this; } public function dispose(): int { return $this->core->getImageDispose(); } public function setDispose(int $dispose): FrameInterface { $this->core->setImageDispose($dispose); return $this; } public function setOffset(int $left, int $top): FrameInterface { $this->core->setImagePage( $this->core->getImageWidth(), $this->core->getImageHeight(), $left, $top ); return $this; } public function offsetLeft(): int { return $this->core->getImagePage()['x']; } public function setOffsetLeft(int $offset): FrameInterface { return $this->setOffset($offset, $this->offsetTop()); } public function offsetTop(): int { return $this->core->getImagePage()['y']; } public function setOffsetTop(int $offset): FrameInterface { return $this->setOffset($this->offsetLeft(), $offset); } public function toImage(): ImageInterface { return new Image($this->core()); } } src/Drivers/Imagick/Image.php000064400000013042150731645660012065 0ustar00imagick; } public function setImagick(Imagick $imagick): ImageInterface { $this->imagick = $imagick; return $this; } public function frame(int $position = 0): FrameInterface { foreach ($this->imagick as $core) { if ($core->getIteratorIndex() == $position) { return new Frame($core); } } throw new AnimationException('Frame #' . $position . ' is not be found in the image.'); } public function addFrame(FrameInterface $frame): ImageInterface { $imagick = $frame->core(); $imagick->setImageDelay($frame->delay()); $imagick->setImageDispose($frame->dispose()); $size = $frame->size(); $imagick->setImagePage( $size->width(), $size->height(), $frame->offsetLeft(), $frame->offsetTop() ); $this->imagick->addImage($imagick); return $this; } public function setLoops(int $count): ImageInterface { $this->imagick = $this->imagick->coalesceImages(); $this->imagick->setImageIterations($count); return $this; } public function loops(): int { return $this->imagick->getImageIterations(); } public function isAnimated(): bool { return $this->count() > 1; } public function count(): int { return $this->imagick->getNumberImages(); } public function current(): mixed { $this->imagick->setIteratorIndex($this->iteratorIndex); return new Frame($this->imagick->current()); } public function key(): mixed { return $this->iteratorIndex; } public function next(): void { $this->iteratorIndex = $this->iteratorIndex + 1; } public function rewind(): void { $this->iteratorIndex = 0; } public function valid(): bool { try { $result = $this->imagick->setIteratorIndex($this->iteratorIndex); } catch (ImagickException $e) { return false; } return $result; } public function width(): int { return $this->frame()->core()->getImageWidth(); } public function height(): int { return $this->frame()->core()->getImageHeight(); } /** * {@inheritdoc} * * @see ImageInterface::resolution() */ public function resolution(): ResolutionInterface { return new Resolution(...$this->frame()->core()->getImageResolution()); } /** * {@inheritdoc} * * @see ImageInterface::pickColor() */ public function pickColor(int $x, int $y, int $frame_key = 0): ColorInterface { return $this->pixelToColor( $this->frame($frame_key)->core()->getImagePixelColor($x, $y), $this->colorspace() ); } public function colorspace(): ColorspaceInterface { return match ($this->imagick->getImageColorspace()) { Imagick::COLORSPACE_CMYK => new CmykColorspace(), default => new RgbColorspace(), }; } /** * {@inheritdoc} * * @see ImageInterface::setColorspace() */ public function setColorspace(string|ColorspaceInterface $colorspace): ImageInterface { return $this->modify(new ColorspaceModifier($colorspace)); } /** * {@inheritdoc} * * @see ImageInterface::setProfile() */ public function setProfile(string|ProfileInterface $input): ImageInterface { $profile = is_object($input) ? $input : new Profile(file_get_contents($input)); return $this->modify(new ProfileModifier($profile)); } /** * {@inheritdoc} * * @see ImageInterface::profile() */ public function profile(): ProfileInterface { $profiles = $this->imagick->getImageProfiles('icc'); if (!array_key_exists('icc', $profiles)) { throw new ColorException('No ICC profile found.'); } return new Profile($profiles['icc']); } /** * {@inheritdoc} * * @see ImageInterface::removeProfile() */ public function removeProfile(): ImageInterface { return $this->modify(new ProfileRemovalModifier()); } } src/Drivers/Imagick/InputHandler.php000064400000003152150731645660013441 0ustar00core()->blurImage(1 * $this->amount, 0.5 * $this->amount); } return $image; } } src/Drivers/Imagick/Modifiers/BrightnessModifier.php000064400000001015150731645660016550 0ustar00core()->modulateImage(100 + $this->level, 100, 100); } return $image; } } src/Drivers/Imagick/Modifiers/ColorizeModifier.php000064400000002352150731645660016233 0ustar00normalizeLevel($this->red); $green = $this->normalizeLevel($this->green); $blue = $this->normalizeLevel($this->blue); foreach ($image as $frame) { $qrange = $frame->core()->getQuantumRange(); $frame->core()->levelImage(0, $red, $qrange['quantumRangeLong'], Imagick::CHANNEL_RED); $frame->core()->levelImage(0, $green, $qrange['quantumRangeLong'], Imagick::CHANNEL_GREEN); $frame->core()->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.php000064400000003723150731645660016542 0ustar00 Imagick::COLORSPACE_SRGB, CmykColorspace::class => Imagick::COLORSPACE_CMYK, ]; public function __construct(protected string|ColorspaceInterface $target) { // } public function apply(ImageInterface $image): ImageInterface { $colorspace = $this->targetColorspace(); $imagick = $this->failIfNotClass($image, Image::class)->getImagick(); $imagick->transformImageColorspace( $this->getImagickColorspace($colorspace) ); return $image; } private function getImagickColorspace(ColorspaceInterface $colorspace): int { if (!array_key_exists(get_class($colorspace), self::$mapping)) { throw new NotSupportedException('Given colorspace is not supported.'); } return self::$mapping[get_class($colorspace)]; } private function targetColorspace(): ColorspaceInterface { if (is_object($this->target)) { 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/Drivers/Imagick/Modifiers/ContrastModifier.php000064400000001042150731645660016235 0ustar00core()->sigmoidalContrastImage($this->level > 0, abs($this->level / 4), 0); } return $image; } } src/Drivers/Imagick/Modifiers/CropModifier.php000064400000002025150731645660015345 0ustar00width, $this->height); $crop->align($this->position); $crop->alignPivotTo($image->size(), $this->position); foreach ($image as $frame) { $frame->core()->extentImage( $crop->width(), $crop->height(), $crop->pivot()->x() + $this->offset_x, $crop->pivot()->y() + $this->offset_y ); } return $image; } } src/Drivers/Imagick/Modifiers/DestroyModifier.php000064400000000630150731645660016073 0ustar00core()->clear(); } return $image; } } src/Drivers/Imagick/Modifiers/DrawEllipseModifier.php000064400000002642150731645660016662 0ustar00colorspace(); $background_color = $this->colorToPixel($this->getBackgroundColor(), $colorspace); $border_color = $this->colorToPixel($this->getBorderColor(), $colorspace); return $image->mapFrames(function ($frame) use ($background_color, $border_color) { $drawing = new ImagickDraw(); $drawing->setFillColor($background_color); if ($this->ellipse()->hasBorder()) { $drawing->setStrokeWidth($this->ellipse()->getBorderSize()); $drawing->setStrokeColor($border_color); } $drawing->ellipse( $this->position->x(), $this->position->y(), $this->ellipse()->getWidth() / 2, $this->ellipse()->getHeight() / 2, 0, 360 ); $frame->core()->drawImage($drawing); }); } } src/Drivers/Imagick/Modifiers/DrawLineModifier.php000064400000002061150731645660016147 0ustar00setStrokeWidth($this->line()->getWidth()); $drawing->setStrokeColor( $this->colorToPixel($this->getBackgroundColor(), $image->colorspace()) ); $drawing->line( $this->line()->getStart()->x(), $this->line()->getStart()->y(), $this->line()->getEnd()->x(), $this->line()->getEnd()->y(), ); return $image->mapFrames(function ($frame) use ($drawing) { $frame->core()->drawImage($drawing); }); } } src/Drivers/Imagick/Modifiers/DrawPixelModifier.php000064400000002241150731645660016341 0ustar00failIfNotInstance( $this->handleInput($this->color), ColorInterface::class ); $pixel = new ImagickDraw(); $pixel->setFillColor($this->colorToPixel($color, $image->colorspace())); $pixel->point($this->position->x(), $this->position->y()); return $image->mapFrames(function ($frame) use ($pixel) { $frame->core()->drawImage($pixel); }); } } src/Drivers/Imagick/Modifiers/DrawPolygonModifier.php000064400000003126150731645660016712 0ustar00colorspace(); $background_color = $this->colorToPixel($this->getBackgroundColor(), $colorspace); $border_color = $this->colorToPixel($this->getBorderColor(), $colorspace); if ($this->polygon()->hasBackgroundColor()) { $drawing->setFillColor($background_color); } if ($this->polygon()->hasBorder()) { $drawing->setStrokeColor($border_color); $drawing->setStrokeWidth($this->polygon()->getBorderSize()); } $drawing->polygon($this->points()); return $image->mapFrames(function ($frame) use ($drawing) { $frame->core()->drawImage($drawing); }); } private function points(): array { $points = []; foreach ($this->polygon() as $point) { $points[] = ['x' => $point->x(), 'y' => $point->y()]; } return $points; } } src/Drivers/Imagick/Modifiers/DrawRectangleModifier.php000064400000002627150731645660017174 0ustar00colorspace(); $background_color = $this->colorToPixel($this->getBackgroundColor(), $colorspace); $border_color = $this->colorToPixel($this->getBorderColor(), $colorspace); $drawing->setFillColor($background_color); if ($this->rectangle()->hasBorder()) { $drawing->setStrokeColor($border_color); $drawing->setStrokeWidth($this->rectangle()->getBorderSize()); } // build rectangle $drawing->rectangle( $this->position->x(), $this->position->y(), $this->position->x() + $this->rectangle()->bottomRightPoint()->x(), $this->position->y() + $this->rectangle()->bottomRightPoint()->y() ); return $image->mapFrames(function ($frame) use ($drawing) { $frame->core()->drawImage($drawing); }); } } src/Drivers/Imagick/Modifiers/FillModifier.php000064400000003664150731645660015342 0ustar00colorToPixel($this->color, $image->colorspace()); foreach ($image as $frame) { if ($this->hasPosition()) { $this->floodFillWithColor($frame, $pixel); } else { $this->fillAllWithColor($frame, $pixel); } } return $image; } protected function floodFillWithColor(Frame $frame, ImagickPixel $pixel): void { $target = $frame->core()->getImagePixelColor( $this->position->x(), $this->position->y() ); $frame->core()->floodfillPaintImage( $pixel, 100, $target, $this->position->x(), $this->position->y(), false, Imagick::CHANNEL_ALL ); } protected function fillAllWithColor(Frame $frame, ImagickPixel $pixel): void { $draw = new ImagickDraw(); $draw->setFillColor($pixel); $draw->rectangle( 0, 0, $frame->core()->getImageWidth(), $frame->core()->getImageHeight() ); $frame->core()->drawImage($draw); } protected function hasPosition(): bool { return !empty($this->position); } } src/Drivers/Imagick/Modifiers/FitDownModifier.php000064400000000463150731645660016020 0ustar00scaleDown($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/FitModifier.php000064400000001554150731645660015172 0ustar00getCropSize($image); $resize = $this->getResizeSize($crop); foreach ($image as $frame) { $frame->core()->extentImage( $crop->width(), $crop->height(), $crop->pivot()->x(), $crop->pivot()->y() ); $frame->core()->scaleImage( $resize->width(), $resize->height() ); } return $image; } } src/Drivers/Imagick/Modifiers/FlipModifier.php000064400000000631150731645660015335 0ustar00core()->flipImage(); } return $image; } } src/Drivers/Imagick/Modifiers/FlopModifier.php000064400000000631150731645660015343 0ustar00core()->flopImage(); } return $image; } } src/Drivers/Imagick/Modifiers/GammaModifier.php000064400000000767150731645660015477 0ustar00core()->gammaImage($this->gamma); } return $image; } } src/Drivers/Imagick/Modifiers/GreyscaleModifier.php000064400000000655150731645660016367 0ustar00core()->modulateImage(100, 0, 100); } return $image; } } src/Drivers/Imagick/Modifiers/InvertModifier.php000064400000000642150731645660015714 0ustar00core()->negateImage(false); } return $image; } } src/Drivers/Imagick/Modifiers/LimitColorsModifier.php000064400000002123150731645660016701 0ustar00limit === 0) { return $image; } // limit is over threshold: no reduction if ($this->limit > $this->threshold) { return $image; } $image = $this->failIfNotClass($image, Image::class); foreach ($image->getImagick() as $core) { $core->quantizeImage( $this->limit, $core->getImageColorspace(), 0, false, false ); } return $image; } } src/Drivers/Imagick/Modifiers/PadDownModifier.php000064400000001347150731645660016004 0ustar00getResizeSize($image); return $image->size() ->contain($resize->width(), $resize->height()) ->alignPivotTo($resize, $this->position); } protected function getResizeSize(ImageInterface $image): SizeInterface { return (new Rectangle($this->width, $this->height)) ->resizeDown($image->width(), $image->height()); } } src/Drivers/Imagick/Modifiers/PadModifier.php000064400000005170150731645660015152 0ustar00failIfNotClass($image, Image::class); $resize = $this->getResizeSize($image); $crop = $this->getCropSize($image); $background = $this->handleInput($this->background); $imagick = new Imagick(); foreach ($image as $frame) { // resize current core $frame->core()->scaleImage( $crop->width(), $crop->height() ); // create new canvas, to get newly emerged background color $canvas = $this->buildBaseCanvas($crop, $resize, $background); // place current core onto canvas $canvas->compositeImage( $frame->core(), Imagick::COMPOSITE_DEFAULT, $crop->pivot()->x(), $crop->pivot()->y() ); $imagick->addImage($canvas); } return $image->setImagick($imagick); } protected function buildBaseCanvas(SizeInterface $crop, SizeInterface $resize, Color $background): Imagick { // build base canvas in target size $canvas = $this->imageFactory()->newCore( $resize->width(), $resize->height(), $background ); // make area where image is placed transparent to keep // transparency even if background-color is set $draw = new ImagickDraw(); $fill = $background->toHex('#') == '#ff0000' ? '#00ff00' : '#ff0000'; $draw->setFillColor(new ImagickPixel($fill)); $draw->rectangle( $crop->pivot()->x(), $crop->pivot()->y(), $crop->pivot()->x() + $crop->width() - 1, $crop->pivot()->y() + $crop->height() - 1 ); $canvas->drawImage($draw); $canvas->transparentPaintImage($fill, 0, 0, false); return $canvas; } } src/Drivers/Imagick/Modifiers/PixelateModifier.php000064400000001553150731645660016222 0ustar00pixelateFrame($frame); } return $image; } protected function pixelateFrame(Frame $frame): void { $size = $frame->size(); $frame->core()->scaleImage( round(max(1, ($size->width() / $this->size))), round(max(1, ($size->height() / $this->size))) ); $frame->core()->scaleImage($size->width(), $size->height()); } } src/Drivers/Imagick/Modifiers/PlaceModifier.php000064400000002603150731645660015470 0ustar00handleInput($this->element); $position = $this->getPosition($image, $watermark); foreach ($image as $frame) { $frame->core()->compositeImage( $watermark->frame()->core(), Imagick::COMPOSITE_DEFAULT, $position->x(), $position->y() ); } return $image; } protected function getPosition(ImageInterface $image, Image $watermark): PointInterface { $image_size = $image->size()->movePivot($this->position, $this->offset_x, $this->offset_y); $watermark_size = $watermark->size()->movePivot($this->position); return $image_size->relativePositionTo($watermark_size); } } src/Drivers/Imagick/Modifiers/ProfileModifier.php000064400000001563150731645660016050 0ustar00failIfNotClass($image, Image::class)->getImagick(); $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.php000064400000001360150731645660017371 0ustar00failIfNotClass($image, Image::class)->getImagick(); $result = $imagick->profileImage('icc', null); if ($result === false) { throw new ColorException('ICC color profile could not be removed.'); } return $image; } } src/Drivers/Imagick/Modifiers/RemoveAnimationModifier.php000064400000001556150731645660017547 0ustar00failIfNotClass($image, Image::class); // create new imagick with just one image $imagick = new Imagick(); $frame = $this->chosenFrame($image, $this->position); $imagick->addImage($frame->core()->getImage()); return $image->setImagick($imagick); } } src/Drivers/Imagick/Modifiers/ResizeDownModifier.php000064400000000571150731645660016537 0ustar00size()->resizeDown($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/ResizeModifier.php000064400000001552150731645660015707 0ustar00getAdjustedSize($image); foreach ($image as $frame) { $frame->core()->scaleImage( $resizeTo->width(), $resizeTo->height() ); } return $image; } protected function getAdjustedSize(ImageInterface $image): SizeInterface { return $image->size()->resize($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/ResolutionModifier.php000064400000001240150731645660016603 0ustar00failIfNotClass($image, Image::class)->getImagick(); $imagick->setImageResolution($this->x, $this->y); return $image; } } src/Drivers/Imagick/Modifiers/RotateModifier.php000064400000001520150731645660015677 0ustar00core()->rotateImage( $this->colorToPixel($this->backgroundColor(), $image->colorspace()), $this->rotationAngle() ); } return $image; } protected function rotationAngle(): float { return parent::rotationAngle() * -1; } } src/Drivers/Imagick/Modifiers/ScaleDownModifier.php000064400000000567150731645660016332 0ustar00size()->scaleDown($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/ScaleModifier.php000064400000000557150731645660015501 0ustar00size()->scale($this->width, $this->height); } } src/Drivers/Imagick/Modifiers/SharpenModifier.php000064400000001017150731645660016042 0ustar00core()->unsharpMaskImage(1, 1, $this->amount / 6.25, 0); } return $image; } } src/Drivers/Imagick/Modifiers/TextWriter.php000064400000001555150731645660015113 0ustar00getAlignedTextBlock(); $font = $this->failIfNotClass($this->getFont(), Font::class); foreach ($image as $frame) { foreach ($lines as $line) { $frame->core()->annotateImage( $font->toImagickDraw($image->colorspace()), $line->getPosition()->x(), $line->getPosition()->y(), $font->getAngle(), $line ); } } return $image; } } src/Drivers/Imagick/Traits/CanHandleColors.php000064400000003754150731645660015321 0ustar00 $colorspace->colorFromNormalized([ $pixel->getColorValue(Imagick::COLOR_CYAN), $pixel->getColorValue(Imagick::COLOR_MAGENTA), $pixel->getColorValue(Imagick::COLOR_YELLOW), $pixel->getColorValue(Imagick::COLOR_BLACK), ]), default => $colorspace->colorFromNormalized([ $pixel->getColorValue(Imagick::COLOR_RED), $pixel->getColorValue(Imagick::COLOR_GREEN), $pixel->getColorValue(Imagick::COLOR_BLUE), $pixel->getColorValue(Imagick::COLOR_ALPHA), ]), }; } /** * Transforms given color to the corresponding ImagickPixel in given colorspace * * Colorspaces of color might be different from given colorspace. You might * have a CMYK Image but give an rgb color to a method. This makes sure * that the color is converted to the given colorspace. In this case * the image colorspace which is passed to this method. * * @param ColorInterface $color * @param ColorspaceInterface $colorspace * @return ImagickPixel */ public function colorToPixel(ColorInterface $color, ColorspaceInterface $colorspace): ImagickPixel { return new ImagickPixel( (string) $color->convertTo($colorspace) ); } } src/EncodedImage.php000064400000001532150731645660010366 0ustar00mimetype; } /** * Transform encoded image data into an data uri string * * @return string */ public function toDataUri(): string { return sprintf('data:%s;base64,%s', $this->mimetype, base64_encode($this->data)); } } src/Exceptions/AnimationException.php000064400000000157150731645660014003 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.php000064400000001737150731645660011065 0ustar00setWidth($diameter); $this->setHeight($diameter); $this->pivot = $pivot ? $pivot : new Point(); } public function diameter(int $diameter): self { return $this->setDiameter($diameter); } public function setDiameter(int $diameter): self { $this->setWidth($diameter); $this->setHeight($diameter); return $this; } public function getDiameter(): int { return $this->diameter; } public function radius(int $radius): self { return $this->setRadius($radius); } public function setRadius(int $radius): self { return $this->diameter(intval($radius * 2)); } public function getRadius(): int { return intval($this->diameter / 2); } } src/Geometry/Ellipse.php000064400000002171150731645660011252 0ustar00pivot = $pivot ? $pivot : new Point(); } public function size(int $width, int $height): self { return $this->setSize($width, $height); } public function setSize(int $width, int $height): self { return $this->setWidth($width)->setHeight($height); } public function setWidth(int $width): self { $this->width = $width; return $this; } public function setHeight(int $height): self { $this->height = $height; return $this; } public function getWidth(): int { return $this->width; } public function getHeight(): int { return $this->height; } } src/Geometry/Line.php000064400000003017150731645660010544 0ustar00width; } public function setWidth(int $width): self { $this->width = $width; return $this; } public function width(int $width): self { return $this->setWidth($width); } public function color($color): self { $this->setBackgroundColor($color); return $this; } public function getStart(): Point { return $this->start; } public function getEnd(): Point { return $this->end; } public function setStart(Point $start): self { $this->start = $start; return $this; } public function from(int $x, int $y): self { $this->getStart()->setX($x); $this->getStart()->setY($y); return $this; } public function to(int $x, int $y): self { $this->getEnd()->setX($x); $this->getEnd()->setY($y); return $this; } public function setEnd(Point $end): self { $this->end = $end; return $this; } } src/Geometry/Pixel.php000064400000001015150731645660010732 0ustar00background = $background; return $this; } public function background(): ColorInterface { return $this->background; } } src/Geometry/Point.php000064400000004136150731645660010751 0ustar00x = $x; return $this; } /** * Get X coordinate * * @return integer */ public function x(): int { return $this->x; } /** * Sets Y coordinate * * @param integer $y */ public function setY(int $y): self { $this->y = $y; return $this; } /** * Get Y coordinate * * @return integer */ public function y(): int { return $this->y; } /** * Move X coordinate * * @param integer $value */ public function moveX(int $value): self { $this->x += $value; return $this; } /** * Move Y coordinate * * @param integer $value */ public function moveY(int $value): self { $this->y += $value; return $this; } public function move(int $x, int $y): self { return $this->moveX($x)->moveY($y); } /** * Sets both X and Y coordinate * * @param integer $x * @param integer $y * @return Point */ public function setPosition(int $x, int $y): self { $this->setX($x); $this->setY($y); return $this; } /** * Rotate point ccw around pivot * * @param float $angle * @param Point $pivot * @return Point */ public function rotate(float $angle, Point $pivot): self { $sin = round(sin(deg2rad($angle)), 6); $cos = round(cos(deg2rad($angle)), 6); return $this->setPosition( intval($cos * ($this->x - $pivot->x) - $sin * ($this->y - $pivot->y) + $pivot->x), intval($sin * ($this->x - $pivot->x) + $cos * ($this->y - $pivot->y) + $pivot->y) ); } } src/Geometry/Polygon.php000064400000022272150731645660011310 0ustar00pivot = $pivot ? $pivot : new Point(); } public function getIterator(): Traversable { return new ArrayIterator($this->points); } /** * Return current pivot point * * @return Point */ public function getPivot(): Point { 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; } public function point(int $x, int $y): self { $this->addPoint(new Point($x, $y)); return $this; } /** * Calculate total horizontal span of polygon * * @return int */ public function width(): int { return abs($this->getMostLeftPoint()->x() - $this->getMostRightPoint()->x()); } /** * Calculate total vertical span of polygon * * @return int */ public function height(): int { return abs($this->getMostBottomPoint()->y() - $this->getMostTopPoint()->y()); } /** * Return most left point of all points in polygon * * @return Point */ public function getMostLeftPoint(): 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 getMostRightPoint(): 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 getMostTopPoint(): 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 getMostBottomPoint(): 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]; } /** * Create and return point in absolute center of the polygon * * @return Point */ public function getCenterPoint(): Point { return new Point( $this->getMostRightPoint()->x() - (intval(round($this->width() / 2))), $this->getMostTopPoint()->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->getCenterPoint()->x() - $this->getPivot()->x()); break; case 'right': $diff = ($this->getMostRightPoint()->x() - $this->getPivot()->x()); break; default: case 'left': $diff = ($this->getMostLeftPoint()->x() - $this->getPivot()->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->getCenterPoint()->y() - $this->getPivot()->y()); break; case 'top': $diff = ($this->getMostTopPoint()->y() - $this->getPivot()->y()) - $this->height(); break; default: case 'bottom': $diff = ($this->getMostBottomPoint()->y() - $this->getPivot()->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->getPivot()->x()), ); $point->setY( intval($point->y() - $this->getPivot()->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->getPivot()->x()), ); $point->setY( intval($y + $this->getPivot()->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.php000064400000016445150731645660011572 0ustar00pivot = $pivot ? $pivot : new Point(); $this->addPoint(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 the rectangle dimensions to given width & height * * @param int $width * @param int $height * @return Rectangle */ public function setSize(int $width, int $height): self { return $this->setWidth($width)->setHeight($height); } /** * Alias of self::setSize() */ public function size(int $width, int $height): self { return $this->setSize($width, $height); } public function setWidth(int $width): self { $this[1]->setX($this[0]->x() + $width); $this[2]->setX($this[3]->x() + $width); return $this; } public function setHeight(int $height): self { $this[2]->setY($this[1]->y() + $height); $this[3]->setY($this[0]->y() + $height); return $this; } public function pivot(): Point { return $this->pivot; } public function setPivot(PointInterface $pivot): self { $this->pivot = $pivot; return $this; } /** * Move current pivot of current rectangle to given position * and moves point automatically by offset. * * @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($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($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($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($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($this->width() / 2) + $offset_x; $y = intval($this->height() / 2) + $offset_y; break; default: case 'top-left': case 'left-top': $x = 0 + $offset_x; $y = 0 + $offset_y; break; } $this->pivot->setPosition($x, $y); return $this; } public function alignPivotTo(SizeInterface $size, string $position): self { $reference = new self($size->width(), $size->height()); $reference->movePivot($position); $this->movePivot($position)->setPivot( $reference->relativePositionTo($this) ); return $this; } /** * Calculate the relative position to another Size * based on the pivot point settings of both sizes. * * @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() ); } public function aspectRatio(): float { return $this->width() / $this->height(); } public function fitsInto(SizeInterface $size): bool { if ($this->width() > $size->width()) { return false; } if ($this->height() > $size->height()) { return false; } return true; } /** * Determine if size is landscape format * * @return boolean */ public function isLandscape(): bool { return $this->width() > $this->height(); } /** * Determine if size is portrait format * * @return boolean */ public function isPortrait(): bool { return $this->width() < $this->height(); } public function topLeftPoint(): PointInterface { return $this->points[0]; } public function bottomRightPoint(): PointInterface { return $this->points[2]; } protected function getResizer(?int $width = null, ?int $height = null): RectangleResizer { return new RectangleResizer($width, $height); } public function resize(?int $width = null, ?int $height = null): SizeInterface { return $this->getResizer($width, $height)->resize($this); } public function resizeDown(?int $width = null, ?int $height = null): SizeInterface { return $this->getResizer($width, $height)->resizeDown($this); } public function scale(?int $width = null, ?int $height = null): SizeInterface { return $this->getResizer($width, $height)->scale($this); } public function scaleDown(?int $width = null, ?int $height = null): SizeInterface { return $this->getResizer($width, $height)->scaleDown($this); } public function cover(int $width, int $height): SizeInterface { return $this->getResizer($width, $height)->cover($this); } public function contain(int $width, int $height): SizeInterface { return $this->getResizer($width, $height)->contain($this); } } src/Geometry/Tools/RectangleResizer.php000064400000015271150731645660014232 0ustar00width); } protected function getTargetWidth(): ?int { return $this->hasTargetWidth() ? $this->width : null; } protected function hasTargetHeight(): bool { return is_integer($this->height); } protected function getTargetHeight(): ?int { return $this->hasTargetHeight() ? $this->height : null; } protected function getTargetSize(): SizeInterface { if (!$this->hasTargetWidth() || !$this->hasTargetHeight()) { throw new GeometryException('Target size needs width and height.'); } return new Rectangle($this->width, $this->height); } public function toWidth(int $width): self { $this->width = $width; return $this; } public function toHeight(int $height): self { $this->height = $height; return $this; } public function toSize(SizeInterface $size): self { $this->width = $size->width(); $this->height = $size->height(); return $this; } protected function getProportionalWidth(SizeInterface $size): int { if (! $this->hasTargetHeight()) { return $size->width(); } return (int) round($this->height * $size->aspectRatio()); } protected function getProportionalHeight(SizeInterface $size): int { if (! $this->hasTargetWidth()) { return $size->height(); } return (int) round($this->width / $size->aspectRatio()); } public function resize(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($width = $this->getTargetWidth()) { $resized->setWidth($width); } if ($height = $this->getTargetHeight()) { $resized->setHeight($height); } return $resized; } public function resizeDown(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($width = $this->getTargetWidth()) { $resized->setWidth( min($width, $size->width()) ); } if ($height = $this->getTargetHeight()) { $resized->setHeight( min($height, $size->height()) ); } return $resized; } public function scale(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($this->hasTargetWidth() && $this->hasTargetHeight()) { $resized->setWidth(min( $this->getProportionalWidth($size), $this->getTargetWidth() )); $resized->setHeight(min( $this->getProportionalHeight($size), $this->getTargetHeight() )); } elseif ($this->hasTargetWidth()) { $resized->setWidth($this->getTargetWidth()); $resized->setHeight($this->getProportionalHeight($size)); } elseif ($this->hasTargetHeight()) { $resized->setWidth($this->getProportionalWidth($size)); $resized->setHeight($this->getTargetHeight()); } return $resized; } public function scaleDown(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); if ($this->hasTargetWidth() && $this->hasTargetHeight()) { $resized->setWidth(min( $this->getProportionalWidth($size), $this->getTargetWidth(), $size->width() )); $resized->setHeight(min( $this->getProportionalHeight($size), $this->getTargetHeight(), $size->height() )); } elseif ($this->hasTargetWidth()) { $resized->setWidth(min( $this->getTargetWidth(), $size->width() )); $resized->setHeight(min( $this->getProportionalHeight($size), $size->height() )); } elseif ($this->hasTargetHeight()) { $resized->setWidth(min( $this->getProportionalWidth($size), $size->width() )); $resized->setHeight(min( $this->getTargetHeight(), $size->height() )); } return $resized; } /** * Scale given size to cover target size * * @param SizeInterface $size Size to be resized * @return SizeInterface */ public function cover(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); // auto height $resized->setWidth($this->getTargetWidth()); $resized->setHeight($this->getProportionalHeight($size)); if ($resized->fitsInto($this->getTargetSize())) { // auto width $resized->setWidth($this->getProportionalWidth($size)); $resized->setHeight($this->getTargetHeight()); } return $resized; } /** * Scale given size to contain target size * * @param SizeInterface $size Size to be resized * @return SizeInterface */ public function contain(SizeInterface $size): SizeInterface { $resized = new Rectangle($size->width(), $size->height()); // auto height $resized->setWidth($this->getTargetWidth()); $resized->setHeight($this->getProportionalHeight($size)); if (!$resized->fitsInto($this->getTargetSize())) { // auto width $resized->setWidth($this->getProportionalWidth($size)); $resized->setHeight($this->getTargetHeight()); } return $resized; } /** * 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.php000064400000001062150731645660014633 0ustar00setBackgroundColor($color); } public function setBackgroundColor($color): self { $this->backgroundColor = $color; return $this; } public function getBackgroundColor() { return $this->backgroundColor; } public function hasBackgroundColor(): bool { return !is_null($this->backgroundColor); } } src/Geometry/Traits/HasBorder.php000064400000001457150731645660013002 0ustar00setBorderSize($size)->setBorderColor($color); } public function setBorderSize(int $size): self { $this->borderSize = $size; return $this; } public function getBorderSize(): int { return $this->borderSize; } public function setBorderColor($color): self { $this->borderColor = $color; return $this; } public function getBorderColor() { return $this->borderColor; } public function hasBorder(): bool { return $this->borderSize > 0 && !is_null($this->borderColor); } } src/ImageManager.php000064400000004630150731645660010401 0ustar00resolveDriverClass('Factory')->newImage($width, $height); } /** * Create new animated image from sources * * @param callable $callback * @return ImageInterface */ public function animate(callable $callback): ImageInterface { return $this->resolveDriverClass('Factory')->newAnimation($callback); } /** * Create new image instance from source * * @param mixed $source * @return ImageInterface */ public function read($source): ImageInterface { return $this->resolveDriverClass('InputHandler')->handle($source); } /** * Return id of current driver * * @return string */ protected function getCurrentDriver(): string { return strtolower($this->driver); } } src/Interfaces/CollectionInterface.php000064400000000505150731645660014060 0ustar00modifiers as $modifier) { $modifier->apply($image); } return $image; } public function push(ModifierInterface $modifier): self { $this->modifiers[] = $modifier; return $this; } } src/Resolution.php000064400000005371150731645660010232 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.php000064400000000414150731645660013150 0ustar00resolveDriverClass('Factory'); } } src/Traits/CanCheckType.php000064400000001214150731645660011626 0ustar00resolveDriverClass('InputHandler')->handle($input); } } src/Traits/CanResolveDriverClass.php000064400000002656150731645660013543 0ustar00getCurrentDriver(); $classname = sprintf( "Intervention\\Image\\Drivers\\%s\\%s", ucfirst($driver_id), $classname ); try { $reflection = new ReflectionClass($classname); } catch (ReflectionException $e) { throw new MissingDriverComponentException( 'Class (' . $classname . ') could not be resolved with driver ' . ucfirst($driver_id) . '.' ); } return $reflection->newInstanceArgs($arguments); } protected function getCurrentDriver(): string { $pattern = '/Intervention\\\Image\\\Drivers\\\(?P[A-Za-z]+)/'; preg_match($pattern, get_class($this), $matches); if (! array_key_exists('driver', $matches)) { throw new RuntimeException('Current driver could not be resolved.'); } return strtolower($matches['driver']); } } src/Typography/Line.php000064400000001277150731645660011125 0ustar00position = new Point(); } public function getPosition(): Point { return $this->position; } public function setPosition(Point $point): self { $this->position = $point; return $this; } public function widthInFont(FontInterface $font): int { return $font->getBoxSize($this->text)->width(); } public function __toString(): string { return $this->text; } } src/Typography/TextBlock.php000064400000003372150731645660012133 0ustar00push(new Line($line)); } } public function getBoundingBox(FontInterface $font, Point $pivot = null): Polygon { $pivot = $pivot ? $pivot : new Point(); // bounding box $box = (new Rectangle( $this->longestLine()->widthInFont($font), $font->leadingInPixels() * ($this->count() - 1) + $font->capHeight() )); // set pivot $box->setPivot($pivot); // align $box->align($font->getAlign()); $box->valign($font->getValign()); $box->rotate($font->getAngle()); return $box; } /** * Return array of lines in text block * * @return array */ public function lines(): array { return $this->items; } public function getLine($key): ?Line { if (!array_key_exists($key, $this->lines())) { return null; } return $this->lines()[$key]; } /** * Return line with most characters of text block * * @return Line */ public function longestLine(): Line { $lines = $this->lines(); usort($lines, function ($a, $b) { if (mb_strlen($a) === mb_strlen($b)) { return 0; } return (mb_strlen($a) > mb_strlen($b)) ? -1 : 1; }); return $lines[0]; } }