├── Tests
├── Resources
│ ├── outputs
│ │ └── .gitkeep
│ ├── empty_reference.yml
│ ├── convert
│ ├── dabug.png
│ └── moon_180.jpg
├── AbstractTestCase.php
├── bootstrap.php
├── References
│ ├── RotationTest.php
│ ├── BlurTest.php
│ ├── ColorspaceValuesTest.php
│ ├── PageTest.php
│ ├── ThresholdTest.php
│ ├── InterlaceTypesTest.php
│ ├── ColorsTest.php
│ ├── GravityTest.php
│ └── GeometryTest.php
└── CommandTest.php
├── .github
├── FUNDING.yml
└── workflows
│ └── php.yml
├── docker_entrypoint.sh
├── .coveralls.yml
├── .gitattributes
├── .gitignore
├── phpunit.xml.dist
├── src
├── MagickBinaryNotFoundException.php
├── ReferenceClasses
│ ├── Gravity.php
│ └── Geometry.php
├── CommandResponse.php
├── References.php
└── Command.php
├── .travis.yml
├── composer.json
├── Resources
├── meta
│ └── LICENSE
└── references.php
├── Makefile
├── .php_cs
├── README.md
└── CHANGELOG.md
/Tests/Resources/outputs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: pierstoval
2 |
--------------------------------------------------------------------------------
/Tests/Resources/empty_reference.yml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docker_entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | "$@"
3 |
--------------------------------------------------------------------------------
/Tests/Resources/convert:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | exit 1
3 |
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-ci
2 | coverage_clover: build/logs/clover.xml
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 | *.php text eol=auto
4 |
--------------------------------------------------------------------------------
/Tests/Resources/dabug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Orbitale/ImageMagickPHP/HEAD/Tests/Resources/dabug.png
--------------------------------------------------------------------------------
/Tests/Resources/moon_180.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Orbitale/ImageMagickPHP/HEAD/Tests/Resources/moon_180.jpg
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | composer.phar
2 | composer.lock
3 | vendor/
4 | build/
5 |
6 | Tests/Resources/outputs/*
7 | !Tests/Resources/outputs/.gitkeep
8 |
9 | phpunit.xml
10 | .phpunit.result.cache
11 | .phpunit.cache
12 |
13 | ImageMagick*.tar.gz
14 | /ImageMagick-*/
15 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 | Tests/
13 |
14 |
15 |
16 |
17 |
18 | ./src/
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/MagickBinaryNotFoundException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick;
15 |
16 | use Exception;
17 |
18 | class MagickBinaryNotFoundException extends Exception
19 | {
20 | public function __construct(string $magickBinaryPath)
21 | {
22 | parent::__construct(\sprintf(
23 | 'The specified path ("%s") is not a file.'."\n".
24 | 'You must set the "magickBinaryPath" parameter as the main "magick" binary installed by ImageMagick.',
25 | $magickBinaryPath
26 | ));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | sudo: required
4 |
5 | branches:
6 | only:
7 | - main
8 |
9 | cache:
10 | directories:
11 | - '$HOME/.composer/cache'
12 |
13 | addons:
14 | apt:
15 | packages:
16 | - libjpeg-dev
17 | - libpng-dev
18 | - libgif-dev
19 | php:
20 | - '7.2'
21 | - '7.3'
22 | - '7.4'
23 | - '8.0'
24 |
25 | before_install: |
26 | composer self-update
27 | curl -L "https://imagemagick.org/download/ImageMagick.tar.gz" | tar xz
28 | cd ImageMagick-*
29 | ./configure
30 | make
31 | sudo make install
32 | sudo ldconfig /usr/local/lib
33 | which magick
34 | find /usr/local -iname "*magick*"
35 | cd ..
36 | mkdir -p build/logs
37 |
38 | install:
39 | - composer install --no-interaction
40 | - composer require --dev satooshi/php-coveralls:~0.6
41 |
42 | script:
43 | - vendor/bin/phpunit --coverage-text --coverage-clover build/logs/clover.xml
44 |
45 | after_success:
46 | - travis_retry php vendor/bin/coveralls -v
47 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "orbitale/imagemagick-php",
3 | "description": "A system that allows creating commands to send to the exec() function to use ImageMagick's powerful features.",
4 | "keywords": ["imagemagick", "imagick", "gd", "image", "converter"],
5 | "homepage": "https://github.com/Orbitale/ImageMagickPHP",
6 | "type": "library",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Alex Rock Ancelet",
11 | "email": "alex@orbitale.io",
12 | "homepage": "http://www.orbitale.io/"
13 | }
14 | ],
15 | "require": {
16 | "php": "^7.2|^8.0",
17 | "ext-mbstring": "*",
18 | "symfony/process": "^5.3|^6.0|^7.0",
19 | "symfony/polyfill-php80": "^1.10"
20 | },
21 | "require-dev": {
22 | "phpunit/phpunit": "^8.5|^9.5"
23 | },
24 | "minimum-stability": "stable",
25 | "autoload": {
26 | "psr-4": {
27 | "Orbitale\\Component\\ImageMagick\\": "src/",
28 | "Orbitale\\Component\\ImageMagick\\Tests\\": "Tests/"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Resources/meta/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Alexandre Rock Ancelet
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Tests/AbstractTestCase.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\Tests;
15 |
16 | use PHPUnit\Framework\TestCase;
17 |
18 | class AbstractTestCase extends TestCase
19 | {
20 | protected $resourcesDir;
21 |
22 | public function __construct($name = null, array $data = [], $dataName = '')
23 | {
24 | parent::__construct($name, $data, $dataName);
25 | if (!\defined('IMAGEMAGICK_DIR') || !\defined('TEST_RESOURCES_DIR')) {
26 | throw new \RuntimeException("The \"IMAGEMAGICK_DIR\" constant is not defined.\n".'The bootstrap must be correctly included before executing test suite.');
27 | }
28 | $this->resourcesDir = TEST_RESOURCES_DIR;
29 | }
30 |
31 | protected function setUp(): void
32 | {
33 | $dir = TEST_RESOURCES_DIR.'/outputs';
34 | foreach (\scandir($dir, \SCANDIR_SORT_NONE) as $file) {
35 | if ('.' !== $file && '..' !== $file && '.gitkeep' !== $file) {
36 | \unlink($dir.'/'.$file);
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/ReferenceClasses/Gravity.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\ReferenceClasses;
15 |
16 | /**
17 | * Represents an ImageMagick gravity parameter.
18 | *
19 | * @see https://www.imagemagick.org/script/command-line-options.php#gravity
20 | */
21 | class Gravity
22 | {
23 | private static $validGravity = [
24 | 'NorthWest',
25 | 'North',
26 | 'NorthEast',
27 | 'West',
28 | 'Center',
29 | 'East',
30 | 'SouthWest',
31 | 'South',
32 | 'SouthEast',
33 | ];
34 |
35 | /**
36 | * @var string
37 | */
38 | private $value;
39 |
40 | public function __construct(string $gravity)
41 | {
42 | $this->value = $gravity;
43 | }
44 |
45 | public function __toString(): string
46 | {
47 | return $this->value;
48 | }
49 |
50 | public function validate(): string
51 | {
52 | if (!\in_array($this->value, self::$validGravity, true)) {
53 | throw new \InvalidArgumentException(\sprintf("Invalid gravity option, \"%s\" given.\nAvailable: %s", $this->value, \implode(', ', self::$validGravity)));
54 | }
55 |
56 | return $this->value;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/CommandResponse.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick;
15 |
16 | use Symfony\Component\Process\Process;
17 |
18 | class CommandResponse
19 | {
20 | /**
21 | * @var Process
22 | */
23 | private $process;
24 |
25 | /**
26 | * @var int
27 | */
28 | protected $code;
29 |
30 | /**
31 | * @var string
32 | */
33 | private $output;
34 |
35 | /**
36 | * @var string
37 | */
38 | private $error;
39 |
40 | public function __construct(Process $process, int $code, string $output, string $error)
41 | {
42 | $this->code = $code;
43 | $this->output = $output;
44 | $this->error = $error;
45 | $this->process = $process;
46 | }
47 |
48 | public function isSuccessful(): bool
49 | {
50 | return 0 === $this->code;
51 | }
52 |
53 | public function hasFailed(): bool
54 | {
55 | return 0 !== $this->code;
56 | }
57 |
58 | public function getProcess(): Process
59 | {
60 | return $this->process;
61 | }
62 |
63 | public function getCode(): int
64 | {
65 | return $this->code;
66 | }
67 |
68 | public function getOutput(): string
69 | {
70 | return $this->output;
71 | }
72 |
73 | public function getError(): string
74 | {
75 | return $this->error;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL := /bin/bash
2 |
3 | IMAGEMAGICK_DOCKER_IMAGE = orbitale-imphp
4 | PHP_BIN = php
5 |
6 | # Helper vars
7 | _TITLE := "\033[32m[%s]\033[0m %s\n"
8 | _ERROR := "\033[31m[%s]\033[0m %s\n"
9 |
10 | .DEFAULT_GOAL := help
11 | help: ## Show this help.
12 | @printf "\n Available commands:\n\n"
13 | @grep -E '(^[a-zA-Z_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-25s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m## */[33m/'
14 | .PHONY: help
15 |
16 | install: vendor imagemagick-docker ## Install composer dependencies and Docker image for testing
17 | .PHONY: install
18 |
19 | start: ## Start Docker image for testing
20 | @docker start $(IMAGEMAGICK_DOCKER_IMAGE)
21 | .PHONY: start
22 |
23 | stop: ## Stop testing Docker image
24 | @docker stop $(IMAGEMAGICK_DOCKER_IMAGE)
25 | .PHONY: stop
26 |
27 | test: start ## Start Docker image for testing
28 | export IMAGEMAGICK_PATH="docker exec $(IMAGEMAGICK_DOCKER_IMAGE) `pwd`/docker_entrypoint.sh magick" && \
29 | $(PHP_BIN) vendor/bin/phpunit
30 | $(MAKE) stop
31 | .PHONY: test
32 |
33 | vendor:
34 | @printf $(_TITLE) "Install" "Installing Composer dependencies"
35 | @composer update
36 | .PHONY: vendor
37 |
38 | imagemagick-docker:
39 | @printf $(_TITLE) "Install" "Removing existing Docker image"
40 | @docker rm --force --volumes $(IMAGEMAGICK_DOCKER_IMAGE)
41 | @printf $(_TITLE) "Install" "Creating ImageMagick Docker image for development"
42 | @docker create \
43 | --name=$(IMAGEMAGICK_DOCKER_IMAGE) \
44 | --volume `pwd`:`pwd` \
45 | --workdir=`pwd` \
46 | --entrypoint="`pwd`/docker_entrypoint.sh" \
47 | dpokidov/imagemagick:latest \
48 | sleep 9999999 \
49 | >/dev/null
50 | .PHONY: imagemagick-docker
51 |
--------------------------------------------------------------------------------
/Tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | use Orbitale\Component\ImageMagick\Command;
13 | use Orbitale\Component\ImageMagick\MagickBinaryNotFoundException;
14 |
15 | $file = __DIR__ . '/../vendor/autoload.php';
16 | if (!file_exists($file)) {
17 | throw new RuntimeException('Install dependencies to run test suite.');
18 | }
19 | $autoload = require $file;
20 |
21 | define('TEST_RESOURCES_DIR', __DIR__.'/Resources');
22 |
23 | // Remove potential older output files
24 | foreach (glob(TEST_RESOURCES_DIR.'/outputs/*', GLOB_NOSORT) as $file) {
25 | unlink($file);
26 | }
27 |
28 | // Check if ImageMagick is installed. Instead, we cannot run tests suite.
29 | $possibleDirectories = [
30 | getenv('IMAGEMAGICK_PATH') ?: null,
31 | null,// In the PATH variable, default behavior
32 | '/usr/bin/magick',
33 | '/usr/local/bin/magick',
34 | ];
35 | foreach ($possibleDirectories as $path) {
36 | echo 'Check for ImageMagick with path "'.$path."\"\n";
37 | try {
38 | $path = Command::findMagickBinaryPath($path);
39 | exec($path.' -version', $o, $code);
40 | if (0 === $code) {
41 | define('IMAGEMAGICK_DIR', $path);
42 | break;
43 | }
44 | } catch (MagickBinaryNotFoundException $e) {
45 | echo 'Did not find ImageMagick with path "'.$path.'". '.$e->getMessage()."\n";
46 | }
47 | }
48 |
49 | if (!defined('IMAGEMAGICK_DIR')) {
50 | throw new RuntimeException(
51 | "Couldn't locate ImageMagick.\n" .
52 | "Please check that ImageMagick is installed and that it is located\n" .
53 | 'in the global PATH variable, or that it is accessible in /usr/bin'
54 | );
55 | }
56 |
57 | echo 'ImageMagick resolved to: "'.IMAGEMAGICK_DIR."\"\n";
58 | system(IMAGEMAGICK_DIR.' -version');
59 |
--------------------------------------------------------------------------------
/Tests/References/RotationTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\Tests\References;
15 |
16 | use Orbitale\Component\ImageMagick\References;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class RotationTest extends TestCase
20 | {
21 | /**
22 | * @dataProvider provideValidRotationValues
23 | */
24 | public function testValidRotation(string $value): void
25 | {
26 | static::assertSame(\trim($value), (new References())->rotation($value));
27 | }
28 |
29 | public function provideValidRotationValues(): \Generator
30 | {
31 | yield ['1'];
32 | yield ['-1'];
33 | yield [' 360'];
34 | yield ['-360 '];
35 | yield ['1>'];
36 | yield ['-1>'];
37 | yield ['360.5>'];
38 | yield ['-360.5>'];
39 | yield ['1<'];
40 | yield ['-1<'];
41 | yield [' 360<'];
42 | yield ['-360< '];
43 | }
44 |
45 | /**
46 | * @dataProvider provideInvalidRotationValues
47 | */
48 | public function testInvalidRotation(string $value): void
49 | {
50 | $this->expectException(\InvalidArgumentException::class);
51 | $this->expectExceptionMessage(\sprintf('The specified rotate parameter (%s) is invalid.'."\n".'Please refer to ImageMagick command line documentation about the "-rotate" option:'."\n%s", \trim($value), 'http://www.imagemagick.org/script/command-line-options.php#rotate'));
52 |
53 | static::assertSame($value, (new References())->rotation($value));
54 | }
55 |
56 | public function provideInvalidRotationValues(): \Generator
57 | {
58 | yield ['a'];
59 | yield ['-1a'];
60 | yield [' abc '];
61 | yield ['0,5'];
62 | yield [''];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Tests/References/BlurTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\Tests\References;
15 |
16 | use Orbitale\Component\ImageMagick\References;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class BlurTest extends TestCase
20 | {
21 | /**
22 | * @dataProvider provideInvalidOptions
23 | */
24 | public function testInvalidBlurOptions($invalidOption): void
25 | {
26 | $this->expectException(\InvalidArgumentException::class);
27 | $this->expectExceptionMessage('Gaussian blur must respect formats "{radius}" or "{radius}x{sigma}".'."\n"
28 | .'Please refer to ImageMagick command line documentation about the "-gaussian-blur" and "-blur" options:'."\n"
29 | .'http://www.imagemagick.org/script/command-line-options.php#blur'."\n"
30 | .'http://www.imagemagick.org/script/command-line-options.php#gaussian-blur');
31 | $this->getReferences()->blur($invalidOption);
32 | }
33 |
34 | /**
35 | * @dataProvider provideValidOptions
36 | */
37 | public function testValidBlurOptions($validOption, $expectedReturn): void
38 | {
39 | static::assertSame($expectedReturn, $this->getReferences()->blur($validOption));
40 | }
41 |
42 | public function getReferences(): References
43 | {
44 | return new References();
45 | }
46 |
47 | public function provideInvalidOptions(): \Generator
48 | {
49 | yield ['invalid'];
50 | yield ['axb'];
51 | yield [' a x b'];
52 | yield ['1x2x3'];
53 | yield ['1 x2'];
54 | yield ['1x 2'];
55 | }
56 |
57 | public function provideValidOptions(): \Generator
58 | {
59 | yield [1, '1'];
60 | yield [-1, '-1'];
61 | yield ['1', '1'];
62 | yield ['-1', '-1'];
63 | yield ['1x2', '1x2'];
64 | yield [' 1x2 ', '1x2'];
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Tests/References/ColorspaceValuesTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\Tests\References;
15 |
16 | use Orbitale\Component\ImageMagick\References;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class ColorspaceValuesTest extends TestCase
20 | {
21 | /**
22 | * @var References
23 | */
24 | private $ref;
25 |
26 | public function __construct($name = null, array $data = [], $dataName = '')
27 | {
28 | parent::__construct($name, $data, $dataName);
29 | $this->ref = new References();
30 | }
31 |
32 | /**
33 | * @param string $colorspaceValue
34 | * @param string $expected
35 | *
36 | * @dataProvider provideValidColorspaceValues
37 | */
38 | public function testValidColorspaceValue($colorspaceValue, $expected): void
39 | {
40 | $validatedType = $this->ref->colorspace($colorspaceValue);
41 |
42 | static::assertSame($expected, $validatedType);
43 | }
44 |
45 | public function provideValidColorspaceValues(): ?\Generator
46 | {
47 | yield ['CMY', 'CMY'];
48 | yield ['CMYK', 'CMYK'];
49 | yield ['Gray', 'Gray'];
50 | yield ['HCL', 'HCL'];
51 | yield ['HCLp', 'HCLp'];
52 | yield ['HSB', 'HSB'];
53 | yield ['HSI', 'HSI'];
54 | }
55 |
56 | /**
57 | * @dataProvider provideInvalidColorspaceValues
58 | */
59 | public function testInvalidColorspaceValues($colorspaceValue): void
60 | {
61 | $colorspaceValue = (string) $colorspaceValue;
62 |
63 | $msg = '';
64 | try {
65 | $this->ref->colorspace($colorspaceValue);
66 | } catch (\InvalidArgumentException $e) {
67 | $msg = $e->getMessage();
68 | }
69 | static::assertStringContainsString(
70 | \sprintf('The specified colorspace value (%s) is invalid', \trim($colorspaceValue)),
71 | $msg
72 | );
73 | }
74 |
75 | public function provideInvalidColorspaceValues(): ?\Generator
76 | {
77 | yield [1];
78 | yield ['2'];
79 | yield ['wow'];
80 | yield ['WoW'];
81 | yield [''];
82 | yield [' '];
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Tests/References/PageTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\Tests\References;
15 |
16 | use Orbitale\Component\ImageMagick\ReferenceClasses\Geometry;
17 | use Orbitale\Component\ImageMagick\References;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class PageTest extends TestCase
21 | {
22 | /**
23 | * @param string|Geometry $pageValue
24 | *
25 | * @dataProvider provideValidPageValues
26 | */
27 | public function testValidPageValue($pageValue, string $expectedValue): void
28 | {
29 | self::assertSame($expectedValue, (new References())->page($pageValue));
30 | }
31 |
32 | public function provideValidPageValues(): \Generator
33 | {
34 | $done = [];
35 |
36 | yield 'Geometry' => [new Geometry(1, 2, 3, 4, '^'), '1x2^+3+4'];
37 |
38 | foreach ((new References())->getPaperSizes() as $paperSize) {
39 | foreach (['', '+1', '-1'] as $offsetX) {
40 | foreach (['', '+1', '-1'] as $offsetY) {
41 | foreach (['', '^', '!', '<', '>', '{', '}'] as $aspectRatio) {
42 | $pageString = $paperSize.$offsetX.$offsetY.$aspectRatio;
43 | if (isset($done[$pageString])) {
44 | continue;
45 | }
46 |
47 | $done[$pageString] = true;
48 |
49 | yield $pageString => [$pageString, $pageString];
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
56 | /**
57 | * @dataProvider provideInvalidPageValues
58 | */
59 | public function testInvalidPageValue(string $pageString): void
60 | {
61 | $this->expectException(\InvalidArgumentException::class);
62 | $this->expectExceptionMessage("Page option is invalid.\nIt must be either a Geometry value, or match the \"media[offset][{^!<>}]\" expression.\nPlease refer to ImageMagick command line documentation:\nhttps://imagemagick.org/script/command-line-options.php#page");
63 |
64 | (new References())->page($pageString);
65 | }
66 |
67 | public function provideInvalidPageValues(): \Generator
68 | {
69 | yield '+1' => ['+1'];
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Tests/References/ThresholdTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\Tests\References;
15 |
16 | use Orbitale\Component\ImageMagick\References;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class ThresholdTest extends TestCase
20 | {
21 | /**
22 | * @var References
23 | */
24 | private $ref;
25 |
26 | public function __construct($name = null, array $data = [], $dataName = '')
27 | {
28 | parent::__construct($name, $data, $dataName);
29 | $this->ref = new References();
30 | }
31 |
32 | /**
33 | * @dataProvider provideValidThresholdValues
34 | */
35 | public function testValidThreshold(string $value): void
36 | {
37 | $threshold = $this->ref->threshold($value);
38 |
39 | self::assertSame($threshold, $value);
40 | }
41 |
42 | public function provideValidThresholdValues(): \Generator
43 | {
44 | yield '100' => ['100'];
45 | yield '-100' => ['-100'];
46 | yield '0' => ['0'];
47 | yield '100%' => ['100%'];
48 | yield '-100%' => ['-100%'];
49 | yield '0%' => ['0%'];
50 | }
51 |
52 | /**
53 | * @dataProvider provideInvalidThresholdValues
54 | */
55 | public function testInvalidThreshold(string $value): void
56 | {
57 | $this->expectException(\InvalidArgumentException::class);
58 | $this->expectExceptionMessage(\sprintf(
59 | 'The specified threshold parameter (%s) is invalid.'."\n".'The value must be an integer or a percentage value'."\n".'Please refer to ImageMagick command line documentation:'."\n%s",
60 | $value,
61 | 'http://www.imagemagick.org/script/command-line-options.php#threshold'
62 | ));
63 |
64 | $this->ref->threshold($value);
65 | }
66 |
67 | public function provideInvalidThresholdValues(): \Generator
68 | {
69 | yield 'Empty string' => [''];
70 | yield 'a' => ['a'];
71 | yield '-' => ['-'];
72 | yield '.' => ['.'];
73 | yield '1_00' => ['1_00'];
74 | yield '%' => ['%'];
75 | yield 'a%' => ['a%'];
76 | yield '-%' => ['-%'];
77 | yield '.%' => ['.%'];
78 | yield '1_00%' => ['1_00%'];
79 | }
80 | }
--------------------------------------------------------------------------------
/.php_cs:
--------------------------------------------------------------------------------
1 |
7 |
8 | For the full copyright and license information, please view the LICENSE
9 | file that was distributed with this source code.
10 | HEADER;
11 |
12 | $finder = PhpCsFixer\Finder::create()
13 | ->exclude([
14 | 'vendor',
15 | 'build',
16 | '.idea',
17 | ])
18 | ->notName('bootstrap.php')
19 | ->in([
20 | __DIR__.'/',
21 | ])
22 | ;
23 |
24 | return PhpCsFixer\Config::create()
25 | ->setRules([
26 | 'header_comment' => [
27 | 'header' => $header,
28 | ],
29 | // Enabled rules
30 | '@Symfony' => true,
31 | '@Symfony:risky' => true,
32 | '@PHP56Migration' => true,
33 | '@PHP70Migration' => true,
34 | '@PHP70Migration:risky' => true,
35 | '@PHP71Migration' => true,
36 | '@PHP71Migration:risky' => true,
37 | 'compact_nullable_typehint' => true,
38 | 'fully_qualified_strict_types' => true,
39 | 'heredoc_to_nowdoc' => true,
40 | 'linebreak_after_opening_tag' => true,
41 | 'logical_operators' => true,
42 | 'native_function_invocation' => true,
43 | 'no_null_property_initialization' => true,
44 | 'no_php4_constructor' => true,
45 | 'no_short_echo_tag' => true,
46 | 'no_superfluous_phpdoc_tags' => true,
47 | 'no_useless_else' => true,
48 | 'no_useless_return' => true,
49 | 'ordered_imports' => true,
50 | 'simplified_null_return' => true,
51 | 'strict_param' => true,
52 | 'php_unit_test_case_static_method_calls' => [
53 | 'call_type' => 'static',
54 | ],
55 | 'array_syntax' => [
56 | 'syntax' => 'short',
57 | ],
58 | 'multiline_whitespace_before_semicolons' => [
59 | 'strategy' => 'new_line_for_chained_calls',
60 | ],
61 |
62 | // Disabled rules
63 | 'increment_style' => false, // Because "++$i" is not always necessary…
64 | 'non_printable_character' => false, // Because I love using non breakable spaces in test methods ♥
65 | ])
66 | ->setRiskyAllowed(true)
67 | ->setIndent(' ')
68 | ->setLineEnding("\n")
69 | ->setUsingCache(true)
70 | ->setFinder($finder)
71 | ;
72 |
--------------------------------------------------------------------------------
/Tests/References/InterlaceTypesTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\Tests\References;
15 |
16 | use Orbitale\Component\ImageMagick\References;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class InterlaceTypesTest extends TestCase
20 | {
21 | /**
22 | * @var References
23 | */
24 | private $ref;
25 |
26 | public function __construct($name = null, array $data = [], $dataName = '')
27 | {
28 | parent::__construct($name, $data, $dataName);
29 | $this->ref = new References();
30 | }
31 |
32 | /**
33 | * @param string $interlaceType
34 | * @param string $expected
35 | *
36 | * @dataProvider provideValidInterlaceTypes
37 | */
38 | public function testValidInterlaceTypes($interlaceType, $expected): void
39 | {
40 | $validatedType = $this->ref->interlace($interlaceType);
41 |
42 | static::assertSame($expected, $validatedType);
43 | }
44 |
45 | public function provideValidInterlaceTypes(): ?\Generator
46 | {
47 | yield ['none', 'none'];
48 | yield ['line', 'line'];
49 | yield ['plane', 'plane'];
50 | yield ['partition', 'partition'];
51 | yield ['jpeg', 'jpeg'];
52 | yield ['gif', 'gif'];
53 | yield ['png', 'png'];
54 | yield ['PNG', 'png'];
55 | yield [' none ', 'none'];
56 | yield ['NONE', 'none'];
57 | yield ['NONE', 'none'];
58 | }
59 |
60 | /**
61 | * @dataProvider provideInvalidInterlaceTypes
62 | */
63 | public function testInvalidInterlaceTypes($interlaceType): void
64 | {
65 | $interlaceType = (string) $interlaceType;
66 |
67 | $msg = '';
68 | try {
69 | $this->ref->interlace($interlaceType);
70 | } catch (\InvalidArgumentException $e) {
71 | $msg = $e->getMessage();
72 | }
73 | static::assertStringContainsString(
74 | \sprintf('The specified interlace type (%s) is invalid', \strtolower(\trim($interlaceType))),
75 | $msg
76 | );
77 | }
78 |
79 | public function provideInvalidInterlaceTypes(): ?\Generator
80 | {
81 | yield [1];
82 | yield ['2'];
83 | yield ['wow'];
84 | yield ['WoW'];
85 | yield [''];
86 | yield [' '];
87 | yield [0x00];
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: PHP CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build-test:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | php-version:
12 | - '7.2'
13 | - '7.3'
14 | - '7.4'
15 | - '8.0'
16 | - '8.1'
17 | - '8.2'
18 | - '8.3'
19 |
20 | symfony-version:
21 | - '5.4'
22 | - '6.4'
23 | - '7.0'
24 | - '7.1'
25 |
26 | exclude:
27 | - { php-version: 7.2, symfony-version: 6.4 }
28 | - { php-version: 7.2, symfony-version: 7.0 }
29 | - { php-version: 7.2, symfony-version: 7.1 }
30 |
31 | - { php-version: 7.3, symfony-version: 6.4 }
32 | - { php-version: 7.3, symfony-version: 7.0 }
33 | - { php-version: 7.3, symfony-version: 7.1 }
34 |
35 | - { php-version: 7.4, symfony-version: 6.4 }
36 | - { php-version: 7.4, symfony-version: 7.0 }
37 | - { php-version: 7.4, symfony-version: 7.1 }
38 |
39 | - { php-version: 8.0, symfony-version: 6.4 }
40 | - { php-version: 8.0, symfony-version: 7.0 }
41 | - { php-version: 8.0, symfony-version: 7.1 }
42 |
43 | - { php-version: 8.0, symfony-version: 6.4 }
44 | - { php-version: 8.1, symfony-version: 7.0 }
45 | - { php-version: 8.1, symfony-version: 7.1 }
46 |
47 | name: PHP ${{ matrix.php-version }} and Symfony ${{ matrix.symfony-version }}
48 | steps:
49 | - uses: actions/checkout@v4
50 |
51 | - uses: shivammathur/setup-php@v2
52 | with:
53 | php-version: "${{ matrix.php-version }}"
54 | extensions: gd, zip
55 | tools: flex
56 |
57 | - run: composer validate
58 |
59 | - id: composer-cache
60 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
61 |
62 | - uses: actions/cache@v3
63 | with:
64 | path: ${{ steps.composer-cache.outputs.dir }}
65 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
66 | restore-keys: ${{ runner.os }}-composer-
67 |
68 | - name: 🚧 Setup
69 | run: composer config extra.symfony.require ^${{ matrix.symfony-version }}
70 |
71 | - name: 🛠 Install
72 | run: make install
73 |
74 | - name: 🧪 Test
75 | run: make test
76 |
--------------------------------------------------------------------------------
/Tests/References/ColorsTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\Tests\References;
15 |
16 | use Orbitale\Component\ImageMagick\References;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class ColorsTest extends TestCase
20 | {
21 | /**
22 | * @var References
23 | */
24 | private $ref;
25 |
26 | public function __construct($name = null, array $data = [], $dataName = '')
27 | {
28 | parent::__construct($name, $data, $dataName);
29 | $this->ref = new References();
30 | }
31 |
32 | /**
33 | * @dataProvider provideCorrectColors
34 | *
35 | * @param string $color
36 | */
37 | public function testCorrectColors($color): void
38 | {
39 | $exception = false;
40 | try {
41 | $checked = $this->ref->color($color);
42 | static::assertEquals($checked, \trim($color));
43 | } catch (\InvalidArgumentException $e) {
44 | $exception = true;
45 | }
46 | static::assertFalse($exception, \sprintf('Failed in checking valid color (%s)', $color));
47 | }
48 |
49 | public function provideCorrectColors(): ?\Generator
50 | {
51 | yield ['#000'];
52 | yield [' #000 '];
53 | yield [' #000000 '];
54 | yield [' #000000000000 '];
55 | yield [' #0000000000000000 '];
56 | yield ['black'];
57 | yield ['RosyBrown1'];
58 | yield ['LavenderBlush2'];
59 | yield ['rgb(0,0,0)'];
60 | yield ['rgb(0%,0%,0%)'];
61 | yield ['rgb(0, 0, 0)'];
62 | yield ['rgb(0.0,0.0,0.0)'];
63 | yield ['rgb(0.0%,0.0%,0.0%)'];
64 | yield ['rgba(0,0,0,0)'];
65 | yield ['rgba(0%,0%,0%,0.0)'];
66 | yield ['rgba(0, 0, 0, 0)'];
67 | yield ['rgba(0.0,0.0,0.0,1)'];
68 | yield ['rgba(0.0%,0.0%,0.0%,0.5)'];
69 | }
70 |
71 | /**
72 | * @dataProvider provideIncorrectColors
73 | *
74 | * @param string $color
75 | */
76 | public function testIncorrectColors($color): void
77 | {
78 | $msg = '';
79 | try {
80 | $this->ref->color($color);
81 | } catch (\InvalidArgumentException $e) {
82 | $msg = $e->getMessage();
83 | }
84 | static::assertStringContainsString(
85 | \sprintf('The specified color (%s) is invalid', $color),
86 | $msg
87 | );
88 | }
89 |
90 | public function provideIncorrectColors(): ?\Generator
91 | {
92 | yield ['invalidColorName'];
93 | yield ['#0000'];
94 | yield ['rgb(0,0,0,0)'];
95 | yield ['rgb(0)'];
96 | yield ['rgb()'];
97 | yield ['rgba(0,0,0)'];
98 | yield ['rgba(0)'];
99 | yield ['rgba()'];
100 | yield ['rgba(0,0,0,2)'];
101 | yield ['rgba(0,0,0,1%)'];
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/Tests/References/GravityTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\Tests\References;
15 |
16 | use Orbitale\Component\ImageMagick\Command;
17 | use Orbitale\Component\ImageMagick\ReferenceClasses\Gravity;
18 | use Orbitale\Component\ImageMagick\Tests\AbstractTestCase;
19 |
20 | class GravityTest extends AbstractTestCase
21 | {
22 | /**
23 | * @param string $gravity
24 | *
25 | * @dataProvider provideValidGravities
26 | */
27 | public function testGravity($gravity): void
28 | {
29 | $gravity = new Gravity($gravity);
30 |
31 | $validatedGravity = $gravity->validate();
32 |
33 | static::assertNotEmpty($validatedGravity);
34 |
35 | $command = new Command(IMAGEMAGICK_DIR);
36 |
37 | $outputFile = $this->resourcesDir.'/outputs/moon_180_test_gravity_'.\md5($gravity.'test_geo').'.jpg';
38 |
39 | if (\file_exists($outputFile)) {
40 | \unlink($outputFile);
41 | }
42 |
43 | $command
44 | ->convert($this->resourcesDir.'/moon_180.jpg')
45 | ->gravity($gravity)
46 | ->resize('100x100')
47 | ->file($outputFile, false)
48 | ;
49 |
50 | $response = $command->run();
51 |
52 | static::assertTrue($response->isSuccessful(), \sprintf(
53 | "Gravity fixture \"%s\" returned an ImageMagick error.\nFull command: %s\nErrors:\n%s\n%s",
54 | $gravity->validate(),
55 | $command->getCommand(),
56 | $response->getOutput(),
57 | $response->getError()
58 | ));
59 | static::assertFileExists($outputFile);
60 | }
61 |
62 | public function provideValidGravities(): ?\Generator
63 | {
64 | yield 0 => ['NorthWest'];
65 | yield 1 => ['North'];
66 | yield 2 => ['NorthEast'];
67 | yield 3 => ['West'];
68 | yield 4 => ['Center'];
69 | yield 5 => ['East'];
70 | yield 6 => ['SouthWest'];
71 | yield 7 => ['South'];
72 | yield 8 => ['SouthEast'];
73 | }
74 |
75 | /**
76 | * @param string $gravity
77 | *
78 | * @dataProvider provideWrongGravities
79 | */
80 | public function testWrongGravities($gravity): void
81 | {
82 | $this->expectException(\InvalidArgumentException::class);
83 | $this->expectExceptionMessage('Invalid gravity option, "'.$gravity."\" given.\nAvailable: NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast");
84 |
85 | $testGravity = new Gravity($gravity);
86 | $testGravity->validate();
87 | }
88 |
89 | public function provideWrongGravities(): ?\Generator
90 | {
91 | yield 0 => ['Northwest'];
92 | yield 1 => ['northwest'];
93 | yield 2 => ['north'];
94 | yield 3 => ['northEast'];
95 | yield 4 => ['Northeast'];
96 | yield 5 => ['west'];
97 | yield 6 => ['center'];
98 | yield 7 => ['east'];
99 | yield 8 => ['southwest'];
100 | yield 9 => ['south'];
101 | yield 10 => ['southeast'];
102 | yield 11 => ['Middle'];
103 | yield 12 => [''];
104 | yield 13 => [' '];
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/ReferenceClasses/Geometry.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\ReferenceClasses;
15 |
16 | /**
17 | * Represents an ImageMagick geometry parameter.
18 | * Each value is optional so a big regex is used to parse it.
19 | *
20 | * How to build the monstruous regexp? Check the gist link.
21 | *
22 | * @see http://www.orbitale.io/2016/03/25/gist-regular-expression-for-imagemagick-geometry.html
23 | *
24 | * @see http://www.imagemagick.org/script/command-line-processing.php#geometry
25 | */
26 | class Geometry
27 | {
28 | private const REGEX_VALIDATE = '~(?(?(?:\d*(?:\.\d+)?)?%?)?(?:(?x)(?(?:\d*(?:\.\d+)?)?%?))?)(?[!><@^])?(?(?[+-]\d*(?:\.\d+)?)?(?[+-]\d*(?:\.\d+)?)?)~';
29 |
30 | public const RATIO_NONE = null;
31 | public const RATIO_MIN = '^';
32 | public const RATIO_IGNORE = '!';
33 | public const RATIO_SHRINK = '>';
34 | public const RATIO_ENLARGE = '<';
35 |
36 | private static $validRatios = [self::RATIO_ENLARGE, self::RATIO_IGNORE, self::RATIO_MIN, self::RATIO_SHRINK];
37 |
38 | /**
39 | * @var string
40 | */
41 | private $value;
42 |
43 | /**
44 | * @param string|int|null $width
45 | */
46 | public static function createFromParameters(
47 | $width = null,
48 | ?int $height = null,
49 | ?int $x = null,
50 | ?int $y = null,
51 | ?string $aspectRatio = self::RATIO_NONE
52 | ): string {
53 | $geometry = $width;
54 |
55 | // If we have a height
56 | // Or if we have width, no height and an offset
57 | // If width is 100, it will result in 100x{offset}
58 | // else, 100{offset} is incorrect
59 | if (null !== $height || ($width && !$height && (null !== $x || null !== $y))) {
60 | $geometry .= 'x';
61 | }
62 |
63 | if (null !== $height) {
64 | $geometry .= $height;
65 | }
66 |
67 | if ($aspectRatio && !\in_array($aspectRatio, self::$validRatios, true)) {
68 | throw new \InvalidArgumentException(\sprintf("Invalid aspect ratio value to generate geometry, \"%s\" given.\nAvailable: %s", $aspectRatio, \implode(', ', self::$validRatios)));
69 | }
70 | $geometry .= $aspectRatio;
71 |
72 | if (null !== $x) {
73 | $geometry .= ($x >= 0 ? '+' : '-').\abs($x);
74 | if (null !== $y) {
75 | $geometry .= ($y >= 0 ? '+' : '-').\abs($y);
76 | }
77 | } elseif (null !== $y) {
78 | $geometry .= '+0'.($y >= 0 ? '+' : '-').\abs($y);
79 | }
80 |
81 | return $geometry;
82 | }
83 |
84 | /**
85 | * @param string|int|null $width
86 | */
87 | public function __construct(
88 | $width = null,
89 | ?int $height = null,
90 | ?int $x = null,
91 | ?int $y = null,
92 | ?string $aspectRatio = self::RATIO_NONE
93 | ) {
94 | $args = \func_get_args();
95 |
96 | $geometry = $width;
97 |
98 | if (\count(\array_map(null, $args)) > 1) {
99 | $geometry = \call_user_func_array([$this, 'createFromParameters'], $args);
100 | }
101 |
102 | $this->value = $geometry;
103 | }
104 |
105 | public function validate(): string
106 | {
107 | $errors = [];
108 |
109 | \preg_match(static::REGEX_VALIDATE, $this->value, $matches);
110 |
111 | $w = isset($matches['w']) && '' !== $matches['w'] ? $matches['w'] : null;
112 | $h = isset($matches['h']) && '' !== $matches['h'] ? $matches['h'] : null;
113 | $x = isset($matches['x']) && '' !== $matches['x'] ? $matches['x'] : null;
114 | $y = isset($matches['y']) && '' !== $matches['y'] ? $matches['y'] : null;
115 | $offset = isset($matches['offset']) && '' !== $matches['offset'] ? $matches['offset'] : null;
116 | $whseparator = $matches['whseparator'];
117 | $aspect = $matches['aspect'];
118 |
119 | // The next checks will perform post-regexp matching that is impossible with preg_match natively
120 |
121 | if ('0' === $w || '0' === $h) {
122 | $errors[] = 'Cannot specify zero width nor height.';
123 | }
124 | if ($aspect && !$w && !$h) {
125 | $errors[] = 'Aspect can be used only with width and/or height.';
126 | }
127 |
128 | if ($w && !$h && ($x || $y) && !$whseparator) {
129 | $errors[] = 'When using offsets and only width, you must specify the "x" separator like this: '.$w.'x'.$offset;
130 | }
131 |
132 | if (\count($errors)) {
133 | throw new \InvalidArgumentException(\sprintf("The specified geometry (%s) is invalid.\n%s\n"."Please refer to ImageMagick command line documentation about geometry:\n%s\n", $this->value, \implode("\n", $errors), 'http://www.imagemagick.org/script/command-line-processing.php#geometry'));
134 | }
135 |
136 | $this->value = \trim($this->value);
137 |
138 | return $this->value;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ImageMagickPHP
2 | ===============
3 |
4 | An ImageMagick "exec" component for PHP apps.
5 |
6 | Installation
7 | ===============
8 |
9 | Install with [Composer](https://getcomposer.org/), it's the best packages manager you can have :
10 |
11 | ```shell
12 | composer require orbitale/imagemagick-php
13 | ```
14 |
15 | Requirements
16 | ===============
17 |
18 | * PHP 7.2 or higher
19 | * [ImageMagick 7](https://www.imagemagick.org/) has to be installed on your server, and the binaries must be executable by the user running the PHP process.
20 |
21 | Settings
22 | ===============
23 |
24 | There are not many settings, but when you instantiate a new `Command` object, you may specify ImageMagick's executable directory directly in the constructor, for example :
25 |
26 | ```php
27 | use Orbitale\Component\ImageMagick\Command;
28 |
29 | // Default directory for many Linux distributions
30 | $command = new Command('/usr/bin/magick');
31 |
32 | // Or in Windows, depending of the install directory
33 | $command = new Command('C:\ImageMagick\magick.exe');
34 |
35 | // Will try to automatically discover the path of ImageMagick in your system
36 | // Note: it uses Symfony's ExecutableFinder to find it in $PATH
37 | $command = new Command();
38 | ```
39 |
40 | The constructor will automatically search for the `magick` executable, test it, and throw an exception if it's not available.
41 |
42 | ⚠️ Make sure your ImageMagick binary is executable.
43 |
44 | Usage
45 | ===============
46 |
47 | First, we recommend you to note all possible scripts that you can use with ImageMagick in the [official docs](https://imagemagick.org/script/command-line-tools.php):
48 |
49 | * [animate](https://imagemagick.org/script/animate.php)
50 | * [compare](https://imagemagick.org/script/compare.php)
51 | * [composite](https://imagemagick.org/script/composite.php)
52 | * [conjure](https://imagemagick.org/script/conjure.php)
53 | * [convert](https://imagemagick.org/script/convert.php)
54 | * [display](https://imagemagick.org/script/display.php)
55 | * [identify](https://imagemagick.org/script/identify.php)
56 | * [import](https://imagemagick.org/script/import.php)
57 | * [mogrify](https://imagemagick.org/script/mogrify.php)
58 | * [montage](https://imagemagick.org/script/montage.php)
59 | * [stream](https://imagemagick.org/script/stream.php)
60 |
61 | These correspond to the "legacy binaries", and you can use them if you are familiar or comfortable with them.
62 |
63 | As of ImageMagick 7, these are not mandatory, but this package is compatible with them.
64 |
65 | ### Basic image type converter with ImageMagick's basic logo
66 |
67 | Read the comments :
68 |
69 | ```php
70 | require_once 'vendor/autoload.php';
71 |
72 | use Orbitale\Component\ImageMagick\Command;
73 |
74 | // Create a new command
75 | $command = new Command();
76 |
77 | $response = $command
78 | // The command will search for the "logo.png" file. If it does not exist, it will throw an exception.
79 | // If it does, it will create a new command with this source image.
80 | ->convert('logo.png')
81 |
82 | // The "output()" method will append "logo.gif" at the end of the command-line instruction as a filename.
83 | // This way, we can continue writing our command without appending "logo.gif" ourselves.
84 | ->output('logo.gif')
85 |
86 | // At this time, the command shall look like this :
87 | // $ "{ImageMagickPath}convert" "logo.png" "logo.gif"
88 |
89 | // Then we run the command by using "exec()" to get the CommandResponse
90 | ->run()
91 | ;
92 |
93 | // Check if the command failed and get the error if needed
94 | if ($response->hasFailed()) {
95 | throw new Exception('An error occurred:'.$response->getError());
96 | } else {
97 | // If it has not failed, then we simply send it to the buffer
98 | header('Content-type: image/gif');
99 | echo file_get_contents('logo.gif');
100 | }
101 | ```
102 |
103 | ### Resizing an image
104 |
105 | ```php
106 | require_once 'vendor/autoload.php';
107 |
108 | use Orbitale\Component\ImageMagick\Command;
109 |
110 | // Create a new command
111 | $command = new Command();
112 |
113 | $response = $command
114 |
115 | ->convert('background.jpeg')
116 |
117 | // We'll use the same output as the input, therefore will overwrite the source file after resizing it.
118 | ->output('background.jpeg')
119 |
120 | // The "resize" method allows you to add a "Geometry" operation.
121 | // It must fit to the "Geometry" parameters in the ImageMagick official documentation (see links below & phpdoc)
122 | ->resize('50x50')
123 |
124 | ->run()
125 | ;
126 |
127 | // Check if the command failed and get the error if needed
128 | if ($response->hasFailed()) {
129 | throw new Exception('An error occurred:'.$response->getError());
130 | } else {
131 | // If it has not failed, then we simply send it to the buffer
132 | header('Content-type: image/gif');
133 | echo file_get_contents('logo.gif');
134 | }
135 | ```
136 |
137 | ### Currently supported options:
138 |
139 | There are **a lot** of command-line options, and each have its own validation system.
140 |
141 | This is why a "few" ones are implemented now, to make sure validation is possible for each of them.
142 |
143 | **Note:** If an option is not implemented in the `Command` class, you can create an issue or make a Pull Request that implements the new option!
144 |
145 | * [`-annotate`](http://www.imagemagick.org/script/command-line-options.php#annotate)
146 | * [`-background`](http://www.imagemagick.org/script/command-line-options.php#background)
147 | * [`-blur`](http://www.imagemagick.org/script/command-line-options.php#blur)
148 | * [`-colorspace`](http://www.imagemagick.org/script/command-line-options.php#colorspace)
149 | * [`-crop`](http://www.imagemagick.org/script/command-line-options.php#crop)
150 | * [`-depth`](http://www.imagemagick.org/script/command-line-options.php#depth)
151 | * [`-draw`](http://www.imagemagick.org/script/command-line-options.php#draw)
152 | * [`-extent`](http://www.imagemagick.org/script/command-line-options.php#extent)
153 | * [`-fill`](http://www.imagemagick.org/script/command-line-options.php#fill)
154 | * [`-flatten`](http://www.imagemagick.org/script/command-line-options.php#flatten)
155 | * [`-font`](http://www.imagemagick.org/script/command-line-options.php#font)
156 | * [`-gaussian-blur`](http://www.imagemagick.org/script/command-line-options.php#gaussian-blur)
157 | * [`-gravity`](http://www.imagemagick.org/script/command-line-options.php#gravity)
158 | * [`-interlace`](http://www.imagemagick.org/script/command-line-options.php#interlace)
159 | * [`-monochrome`](http://www.imagemagick.org/script/command-line-options.php#monochrome)
160 | * [`-pointsize`](http://www.imagemagick.org/script/command-line-options.php#pointsize)
161 | * [`-quality`](http://www.imagemagick.org/script/command-line-options.php#quality)
162 | * [`-resize`](http://www.imagemagick.org/script/command-line-options.php#resize)
163 | * [`-rotate`](http://www.imagemagick.org/script/command-line-options.php#rotate)
164 | * [`-size`](http://www.imagemagick.org/script/command-line-options.php#size)
165 | * [`-strip`](http://www.imagemagick.org/script/command-line-options.php#strip)
166 | * [`-stroke`](http://www.imagemagick.org/script/command-line-options.php#stroke)
167 | * [`-thumbnail`](http://www.imagemagick.org/script/command-line-options.php#thumbnail)
168 | * [`-transpose`](http://www.imagemagick.org/script/command-line-options.php#transpose)
169 | * [`-transverse`](http://www.imagemagick.org/script/command-line-options.php#transverse)
170 | * [`xc:`](http://www.imagemagick.org/Usage/canvas/)
171 |
172 | Feel free to ask/create an issue if you need more!
173 |
174 | ### Some aliases that do magic for you:
175 |
176 | * `$command->text()`:
177 | This method uses multiple options added to the `-annotate` one to generate a text block.
178 | You must specify its position and size, but you can specify color and the font file used.
179 |
180 | * `$command->ellipse()`: (check source code for the heavy prototype!)
181 | This method uses the `-stroke`, `-fill` and `-draw` options to create an ellipse/circle/disc on your picture.
182 | **Note:** I recommend to check both the source code and the documentation to be sure of what you are doing.
183 |
184 | Useful links
185 | ===============
186 |
187 | * ImageMagick official website: http://www.imagemagick.org
188 | * ImageMagick documentation:
189 | * [Installation of the binaries](https://www.imagemagick.org/script/download.php) (depending on your OS and/or distribution)
190 | * [Geometry option](https://www.imagemagick.org/script/command-line-processing.php#geometry) (to resize or place text)
191 | * [All command-line options](https://imagemagick.org/script/command-line-options.php) ; they're not all available in this tool for now, so feel free to make a PR ! ;)
192 |
--------------------------------------------------------------------------------
/src/References.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick;
15 |
16 | use Orbitale\Component\ImageMagick\ReferenceClasses\Geometry;
17 | use Orbitale\Component\ImageMagick\ReferenceClasses\Gravity;
18 |
19 | /**
20 | * This class is here to add some validation processes when using options in the Command class.
21 | */
22 | final class References
23 | {
24 | /**
25 | * @var array Configuration from the references.php file
26 | */
27 | private $config = [];
28 |
29 | public function __construct()
30 | {
31 | $referenceFile = \dirname(__DIR__).'/Resources/references.php';
32 |
33 | if (!\is_file($referenceFile)) {
34 | throw new \RuntimeException(\sprintf('File %s for ImageMagick references does not exist.'."\n".'Check that the file exists and that it is readable.', $referenceFile));
35 | }
36 |
37 | $config = require $referenceFile;
38 |
39 | $keysToCheck = [
40 | 'colors',
41 | 'colorspace_values',
42 | 'interlace_types',
43 | 'paper_sizes',
44 | ];
45 | $keysExist = true;
46 |
47 | foreach ($keysToCheck as $key) {
48 | if (!\array_key_exists($key, $config)) {
49 | $keysExist = false;
50 | break;
51 | }
52 | }
53 |
54 | if (\is_array($config) && $keysExist) {
55 | $this->config = $config;
56 | } else {
57 | throw new \InvalidArgumentException(\sprintf('ImageMagick references file "%s" seems to be empty or invalid.', $referenceFile));
58 | }
59 | }
60 |
61 | /**
62 | * @return string[]
63 | */
64 | public function getColorspaceValuesReference(): array
65 | {
66 | return $this->config['colorspace_values'];
67 | }
68 |
69 | /**
70 | * @return string[]
71 | */
72 | public function getColorsReference(): array
73 | {
74 | return $this->config['colors'];
75 | }
76 |
77 | /**
78 | * @return string[]
79 | */
80 | public function getInterlaceTypesReference(): array
81 | {
82 | return $this->config['interlace_types'];
83 | }
84 |
85 | public function getPaperSizes()
86 | {
87 | return $this->config['paper_sizes'];
88 | }
89 |
90 | /**
91 | * @param string|Geometry $geometry
92 | */
93 | public function geometry($geometry): string
94 | {
95 | if (!$geometry instanceof Geometry) {
96 | $geometry = new Geometry(\trim($geometry));
97 | }
98 |
99 | return $geometry->validate();
100 | }
101 |
102 | /**
103 | * @param string|Gravity $gravity
104 | */
105 | public function gravity($gravity): string
106 | {
107 | if (!$gravity instanceof Gravity) {
108 | $gravity = new Gravity(\trim($gravity));
109 | }
110 |
111 | return $gravity->validate();
112 | }
113 |
114 | /**
115 | * Checks that a color is correct according to ImageMagick command line reference.
116 | *
117 | * @see http://www.imagemagick.org/script/color.php
118 | */
119 | public function color(string $color): string
120 | {
121 | $color = \trim($color);
122 | if (
123 | // Check "hex"
124 | \preg_match('~^#(?:[a-f0-9]{3}|[a-f0-9]{6}|[a-f0-9]{12})$~i', $color)
125 |
126 | // Check "hexa"
127 | || \preg_match('~^#([a-f0-9]{8}|[a-f0-9]{16})$~i', $color)
128 | || \preg_match('~^rgb\(\d{1,3}(\.\d{1,2})?%?, ?\d{1,3}(\.\d{1,2})?%?, ?\d{1,3}(\.\d{1,2})?%?\)$~', $color)
129 |
130 | // Check "rgb"
131 | || \preg_match('~^rgba\(\d{1,3}(\.\d{1,2})?%?, ?\d{1,3}(\.\d{1,2})?%?, ?\d{1,3}(\.\d{1,2})?%?, ?[01](\.\d{1,6})?\)$~', $color)
132 |
133 | // Check "rgba"
134 | || \in_array($color, $this->getColorsReference(), true)// And check the dirty one : all the color names supported by ImageMagick
135 | ) {
136 | return $color;
137 | }
138 |
139 | throw new \InvalidArgumentException(\sprintf('The specified color (%s) is invalid.'."\n".'Please refer to ImageMagick command line documentation about colors:'."\n%s", $color, 'http://www.imagemagick.org/script/color.php'));
140 | }
141 |
142 | /**
143 | * Checks that colorspace value is valid in the references.
144 | */
145 | public function colorspace(string $colorspace): string
146 | {
147 | $colorspace = \trim($colorspace);
148 |
149 | $references = $this->getColorspaceValuesReference();
150 |
151 | if (\in_array($colorspace, $references, true)) {
152 | return $colorspace;
153 | }
154 |
155 | throw new \InvalidArgumentException(\sprintf('The specified colorspace value (%s) is invalid.'."\n".'The available values are:'."\n%s\n".'Please refer to ImageMagick command line documentation:'."\n%s", $colorspace, \implode(', ', $references), 'http://www.imagemagick.org/script/command-line-options.php#colorspace'));
156 | }
157 |
158 | /**
159 | * Checks that a rotation option is correct according to ImageMagick command line reference.
160 | *
161 | * @see http://www.imagemagick.org/script/command-line-options.php#rotate
162 | */
163 | public function rotation(string $rotation): string
164 | {
165 | $rotation = \trim($rotation);
166 |
167 | if (\preg_match('~^-?\d+(\.\d+)?[<>]?$~u', $rotation)) {
168 | return $rotation;
169 | }
170 |
171 | throw new \InvalidArgumentException(\sprintf('The specified rotate parameter (%s) is invalid.'."\n".'Please refer to ImageMagick command line documentation about the "-rotate" option:'."\n%s", $rotation, 'http://www.imagemagick.org/script/command-line-options.php#rotate'));
172 | }
173 |
174 | public function blur($blur): string
175 | {
176 | if (\is_string($blur)) {
177 | $blur = \trim($blur);
178 | }
179 |
180 | if (\is_numeric($blur)) {
181 | return (string) (float) $blur;
182 | }
183 |
184 | if (\preg_match('~^\d+(?:\.\d+)?(?:x\d+(?:\.\d+)?)?$~', $blur)) {
185 | return (string) $blur;
186 | }
187 |
188 | throw new \InvalidArgumentException(\sprintf('Gaussian blur must respect formats "%s" or "%s".'."\n".'Please refer to ImageMagick command line documentation about the "-gaussian-blur" and "-blur" options:'."\n%s\n%s", '{radius}', '{radius}x{sigma}', 'http://www.imagemagick.org/script/command-line-options.php#blur', 'http://www.imagemagick.org/script/command-line-options.php#gaussian-blur'));
189 | }
190 |
191 | /**
192 | * Checks that interlace type is valid in the references.
193 | */
194 | public function interlace(string $interlaceType): string
195 | {
196 | $interlaceType = \strtolower(\trim($interlaceType));
197 |
198 | $references = $this->getInterlaceTypesReference();
199 |
200 | if (\in_array($interlaceType, $references, true)) {
201 | return $interlaceType;
202 | }
203 |
204 | throw new \InvalidArgumentException(\sprintf('The specified interlace type (%s) is invalid.'."\n".'The available values are:'."\n%s\n".'Please refer to ImageMagick command line documentation:'."\n%s", $interlaceType, \implode(', ', $references), 'http://www.imagemagick.org/script/command-line-options.php#interlace'));
205 | }
206 |
207 | /**
208 | * Checks that threshold value is valid according to ImageMagick command line reference.
209 | */
210 | public function threshold(string $threshold): string
211 | {
212 | $threshold = \trim($threshold);
213 |
214 | if (\is_numeric($threshold)) {
215 | return $threshold;
216 | }
217 |
218 | if (\str_ends_with($threshold, '%')) {
219 | $percentNumber = \substr($threshold, 0, -1);
220 | if (\is_numeric($percentNumber)) {
221 | return $threshold;
222 | }
223 | }
224 |
225 | throw new \InvalidArgumentException(\sprintf('The specified threshold parameter (%s) is invalid.'."\n".'The value must be an integer or a percentage value'."\n".'Please refer to ImageMagick command line documentation:'."\n%s", $threshold, 'http://www.imagemagick.org/script/command-line-options.php#threshold'));
226 | }
227 |
228 | /**
229 | * @param string|Geometry $page
230 | */
231 | public function page($page): string
232 | {
233 | if ($page instanceof Geometry) {
234 | return $page->validate();
235 | }
236 |
237 | $paperSizesRegex = '(?'.\implode('|', $this->getPaperSizes()).')';
238 | $offsetRegex = '[-+]\d+';
239 | $offsetsRegex = \sprintf('(?%s)?(?%s)?', $offsetRegex, $offsetRegex);
240 | $ratioRegex = '(?[^!<>])?';
241 |
242 | $pageOptionRegex = '~'.$paperSizesRegex.$offsetsRegex.$ratioRegex.'~i';
243 |
244 | if (!\preg_match($pageOptionRegex, $page)) {
245 | throw new \InvalidArgumentException(\sprintf('Page option is invalid.'."\n".'It must be either a Geometry value, or match the "%s" expression.'."\n".'Please refer to ImageMagick command line documentation:'."\n%s", 'media[offset][{^!<>}]', 'https://imagemagick.org/script/command-line-options.php#page'));
246 | }
247 |
248 | return $page;
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # [v3.3.4](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.3.4) - 9 Dec 2024
2 |
3 | * Fix: use `escapeshellarg()` on paths to support names containing spaces
4 |
5 | # [v3.3.3](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.3.3) - 10 Jun 2024
6 |
7 | * Make sure latest versions of Symfony and PHP are compatible
8 |
9 | # [v3.3.2](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.3.2) - 21 Dec 2023
10 |
11 | * Don't call trigger_error with E_STRICT ([@koenig-k](https://github.com/koenig-k))
12 |
13 | # [v3.3.1](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.3.1) - 18 Dec 2023
14 |
15 | * Add support for Symfony 7, PHP 8.2 and 8.3 ([@derrabus](https://github.com/derrabus))
16 |
17 | # [v3.3.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.3.0) - 15 Dec 2021
18 |
19 | * Drop support for Symfony 4 (Please upgrade 😉)
20 | * Change minimum required Symfony version to 5.3 instead of 4.0|5.0
21 | * Test the package with more Symfony & PHP versions
22 |
23 | # [v3.2.1](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.2.1) - 02 Jul 2021
24 |
25 | * Add tests for the `-threshold` option.
26 | * Removed useless method `__toString()` in the Geometry class.
27 | * Remove a check to the regex in the Geometry class that was always returning false.
28 |
29 | # [v3.2.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.2.0) - 08 Apr 2021
30 |
31 | * Implement the `-page` option ([@pbories](https://github.com/pbories))
32 | * Refactor the CI setup to use ImageMagick Docker image
33 | * Don't make `Command` be so strict about ImageMagick binaries when resolving the `convert` binary.
34 | This is important because it means that now you can use a compound binary, such as using `docker run ...` or `docker exec ...`.
35 |
36 | # [v3.1.1](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.1.1) - 01 Feb 2021
37 |
38 | ## Fixes
39 |
40 | * Fix an issue with `References::rotation()` not taking decimal nor single values in account.
41 | * Fix issues when ImageMagick path is empty when creating a new `Command`.
42 |
43 | # [v3.1.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.1.0) - 13 Jan 2021
44 |
45 | ## New features
46 |
47 | Allow multiple sources as first parameter of `convert()` method (#32 by @pbories).
48 |
49 | This means you can use the method like this: `->convert(['file1.jpg', 'file2.jpg'])`, for convenience, when converting multiple source files.
50 |
51 | ## Fixes
52 |
53 | * Automatically find `magick` binary (#31 by @pbories).
54 | * Fix issue making path unresolvable if null.
55 | * Fix blur type issues, and add test for it. (#23 by @AMK9978, [83914560](https://github.com/Orbitale/ImageMagickPHP/commit/83914560495b0e911ea3d040d663e32c633868a2) by @pierstoval)
56 |
57 | ## Miscellaneous
58 |
59 | * Move source code to "src/" directory instead of project root, easier for conciseness and code coverage.
60 | * Refactor test setup with latest PHPUnit version.
61 | * Global CS fix.
62 |
63 | # [v3.0.14](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.14) - 13 Dec 2020
64 |
65 | Added PHP 8 support (#28 by @VincentLanglet)
66 |
67 | # [v3.0.13](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.13) - 31 Jul 2020
68 |
69 | Add support for the `-threshold` option (@fariasmaiquita)
70 |
71 | # [v3.0.12](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.12) - 01 Jan 2020
72 |
73 | Added support for `-transpose`, `-transverse` and `-monochrome` commands.
74 |
75 | # [v3.0.11](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.11) - 27 Dec 2019
76 |
77 | Allow Symfony 5.0
78 |
79 | # [v3.0.10](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.10) - 12 Dec 2019
80 |
81 | Added support for `-gravity` option (@JeffAlyanak)
82 |
83 | # [v3.0.9](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.9) - 26 Aug 2019
84 |
85 | * Added `-depth` `-flatten` and `-colorspace` options (@70mmy)
86 |
87 | # [v3.0.8](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.8) - 23 May 2019
88 |
89 | ### Changelog for v3.0.8
90 |
91 | * Added support for ImageMagick's `-auto-orient` option
92 |
93 | # [v3.0.6](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.6) - 08 Mar 2019
94 |
95 | Changelog for v3.0.6:
96 |
97 | * Fix bug in `Command::text()`
98 |
99 | # [v3.0.4](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.4) - 07 Mar 2019
100 |
101 | Changelog for v3.0.4:
102 |
103 | * Add support for `-geometry`
104 | * Implement `Command::rawCommand()` for edge cases that are not implemented yet. ÔÜá´©Å Use at your own risk! It is way better to create an issue or a pull-request if you need an unimplemented feature ƒÿë
105 |
106 | # [v3.0.3](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.3) - 07 Mar 2019
107 |
108 | Changelog for v3.0.1:
109 |
110 | * Force adding `"` for colors when possible, to allow colors like `rgba()` or other values with parentheses
111 |
112 | # [v3.0.2](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.2) - 07 Mar 2019
113 |
114 | Changelog for v3.0.2:
115 |
116 | * Added support for `-strokewidth` option
117 |
118 | # [v3.0.1](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.1) - 07 Mar 2019
119 |
120 | Changelog for v3.0.1:
121 |
122 | * Added support for `-draw "polyline ..."` raw without validation (more will come about drawing with validation)
123 |
124 | # [v3.0.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v3.0.0) - 05 Mar 2019
125 |
126 | Changelog for v3.0.0:
127 |
128 | * **Drop support for ImageMagick 6, this means that we support only the `magick` binary (or `magick.exe` on Windows)**
129 | * Add preliminary support for all other legacy commands (composite, animation, etc.)
130 | * Require PHP 7.2 and `symfony/process` 4.* only
131 | * Removed `Command::escape()` method
132 | * Use php-cs-fixer to uniformize code style
133 | * Use `declare(strict_types=1);` everywhere
134 | * Use `ExecutableFinder` by default to determine ImageMagick's path, making it easier to discover default `magick` binary
135 | * Command line instructions are now arrays instead of strings (easier handling)
136 |
137 | # [v2.0.2](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v2.0.2) - 12 Feb 2019
138 |
139 | # Changelog for v2.0.2
140 |
141 | ## Internal breaks
142 |
143 | * Remove internal class `CommandOptions` and put all options in the main class.
144 | * Remove `symfony/yaml` dependency and make references an array for better performances.
145 | * Remove `symfony/filesystem` dependency and use native PHP functions.
146 | * ÔÜá´©Å BC BREAK: Refactor `Command::text()` with array options instead of arguments:
147 | * `$options['text']`
148 | * `$options['textSize']`
149 | * `$options['geometry']`
150 | * `$options['font']`
151 | * `$options['checkFont']`
152 | * `$options['textColor']`
153 |
154 | ## Features
155 |
156 | * Add `Command::create($binary)` for simpler fluent interfaces.
157 | * Add support for more ImageMagick options:
158 | * `-stroke`
159 | * `-fill`
160 | * `-size`
161 | * `-pointsize`
162 | * Add `$checkFontFileExists` argument to `Command::font()` for native fonts
163 | * Add `isSuccessful()` to `CommandResponse`, more convenient than `hasFailed()`
164 |
165 | ## Dev/test
166 |
167 | * Update Travis-CI build to find a proper way to download ImageMagick for tests (even though it's not 100% ok)
168 |
169 | # [v2.0.1](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v2.0.1) - 09 Feb 2018
170 |
171 | Changelog for v2.0.1
172 |
173 | * Add [`-size`](http://www.imagemagick.org/script/command-line-options.php#size) option
174 | * Add `output()` alias to append file name to the end of the command
175 | * Add `xc` option to allow creating images with "X window color", like `xc:none` by default for transparent images
176 |
177 | # [v2.0.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v2.0.0) - 20 Jan 2018
178 |
179 | New of the v2.0!
180 | ==========
181 |
182 | * Now PHP 7.1 is required.
183 | * Symfony 3.0+ or 4.0+ required.
184 | * Make use of PHP 7 typehints when it's possible.
185 | * Tests on Travis-CI now build ImageMagick 6 and 7 to check both versions.
186 |
187 | Just a major release to advance in time.
188 |
189 | If new ImageMagick options are added, they may be backported to v1, as of the new 1.x branch.
190 |
191 | # [v1.6.1](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v1.6.1) - 19 Jan 2018
192 |
193 | ### Fixes
194 |
195 | * Escape background option
196 |
197 | # [v1.6.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v1.6.0) - 10 Feb 2017
198 |
199 | ### New features
200 |
201 | Add support for `-interlace`, `-blur` and `-gaussian-blur` options.
202 |
203 | Added needed references and tests
204 |
205 | # [v1.5.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v1.5.0) - 02 Feb 2017
206 |
207 | ### New features
208 | - Use Symfony Process to exec command, and take care of IM6 and IM7 **(Possible BC breaks, but there should be none actually)**
209 | - Add `__toString()` to Geometry
210 |
211 | ### Adjustments
212 | - Always use the bundled reference file instead of passing it as argument
213 | - Improve constructor docs for Geometry
214 |
215 | ### Fixes
216 | - Add spaces after some command options
217 | - Trim Geometry value when constructing it
218 |
219 | Adapted tests to last updates, and remove useless ones
220 |
221 | # [v1.4.2](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v1.4.2) - 01 Feb 2017
222 |
223 | ### New option
224 |
225 | [4e99158] Added `-strip` option
226 |
227 | Docs of this option => http://www.imagemagick.org/script/command-line-options.php#strip
228 |
229 | # [v1.4.1](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v1.4.1) - 15 Apr 2016
230 |
231 | # New option!
232 |
233 | [a897116] Add the `-rotate` command-line option (closes #2 )
234 |
235 | # [v1.4.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v1.4.0) - 08 Mar 2016
236 |
237 | Add SF3 compatibility (still need review)
238 |
239 | # [v1.3.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v1.3.0) - 03 Sep 2015
240 |
241 | This new release comes with a great `Geometry` validation inside the new `Geometry` class.
242 |
243 | Geometry is really hard to validate in ImageMagick because it can have multiple forms, according to [ImageMagick's documentation about Geometry](http://www.imagemagick.org/script/command-line-processing.php#geometry).
244 |
245 | Then, all current methods requiring a geometry parameter (`resize`, `crop`, etc.) now allow using a `Geometry` instance to reinforce validation before executing the command (and catching the exceptions with a verbose explanation of what happened).
246 | Also, the `Geometry` class has an easy constructor with `Geometry::createFromParameters` that allow specifying all parameters we need: width, height, X and Y offsets, and the aspect ratio option.
247 |
248 | More than 700 tests were generated to be sure that the validation system is the same than the one present in ImageMagick itself.
249 |
250 | Also, a big new feature about `::run()` method was introduced: you can now execute commands in three ways:
251 | - Normally (nothing to do)
252 | - In background (will append `> /dev/null 2>&1` to the command)
253 | - In debug mode (will append STDERR to STDOUT to retrieve the errors in the `CommandResponse::getContent()` method)
254 |
255 | Just take a look at the `RUN_*` constants in the `Command` class.
256 |
257 | ### New features
258 |
259 | [ef52968] Start refactoring the Geometry validation
260 | [044f25d] Many updates on the command class.
261 | - Escape the geometry parameters (to avoid problems with "!" or "^" flags)
262 | - Added "setEnv" for env vars, also added other IM commands: background, crop, extent.
263 | - When using `run`, one can retrieve stderr in the output to watch for errors.
264 | - Removed many spaces form most commands.
265 | [4b46ff0] Add new Geometry class to handle geometry use
266 | [f1636aa] Created new `RUN_` constants for running mode.
267 | ### Tests & CI
268 | [a46c25d] Install ImageMagick manually to have latest version
269 | [1f440a4] Show ImageMagick version in tests
270 | [aad6c2b] Finalize geometry testing
271 | [08576eb] Remove useless dev deps, optimize travis-ci
272 | [510c3f5] Fixed tests
273 |
274 | ### Code style
275 |
276 | [738047a] Reorder methods to fit PSR style
277 |
278 | # [v1.2.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v1.2.0) - 07 Apr 2015
279 |
280 | Transferred ownership.
281 |
282 | # [v1.1.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v1.1.0) - 03 Mar 2015
283 |
284 | **New features:**
285 | dd2991b Added "quality" and "thumbnail" command line options
286 | afac867 Made the "escape" function public.
287 |
288 | **Other big changes:**
289 | e9cad7d Changed composer namespace
290 |
291 | Also added many MANY tests.
292 |
293 | # [v1.0.0](https://github.com/Orbitale/ImageMagickPHP/releases/tag/v1.0.0) - 26 Feb 2015
294 |
295 | First release
296 |
--------------------------------------------------------------------------------
/Tests/CommandTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\Tests;
15 |
16 | use Orbitale\Component\ImageMagick\Command;
17 | use Orbitale\Component\ImageMagick\MagickBinaryNotFoundException;
18 |
19 | class CommandTest extends AbstractTestCase
20 | {
21 | /**
22 | * @dataProvider provideWrongConvertDirs
23 | */
24 | public function testWrongConvertDirs($path, $expectedMessage, $expectedException): void
25 | {
26 | $path = \str_replace('\\', '/', $path);
27 | $expectedMessage = \str_replace('\\', '/', $expectedMessage);
28 | $this->expectExceptionMessage($expectedMessage);
29 | $this->expectException($expectedException);
30 |
31 | new Command($path);
32 | }
33 |
34 | public function provideWrongConvertDirs(): ?\Generator
35 | {
36 | yield ['/this/is/a/dummy/dir', "ImageMagick does not seem to work well, the test command resulted in an error.\nExecution returned message: \"Command not found\"\nTo solve this issue, please run this command and check your error messages to see if ImageMagick was correctly installed:\n/this/is/a/dummy/dir -version", MagickBinaryNotFoundException::class];
37 | yield ['./', "The specified path (\"ImageMagick does not seem to work well, the test command resulted in an error.\nExecution returned message: \"Misuse of shell builtins\"\nTo solve this issue, please run this command and check your error messages to see if ImageMagick was correctly installed:\n. -version\") is not a file.\nYou must set the \"magickBinaryPath\" parameter as the main \"magick\" binary installed by ImageMagick.", MagickBinaryNotFoundException::class];
38 | }
39 |
40 | /**
41 | * @param $fileSources
42 | *
43 | * @dataProvider convertDataProvider
44 | */
45 | public function testConvert($fileSources, string $fileOutput): void
46 | {
47 | $command = new Command(IMAGEMAGICK_DIR);
48 |
49 | $response = $command
50 | ->convert($fileSources)
51 | ->output($fileOutput)
52 | ->run()
53 | ;
54 |
55 | static::assertFileExists($fileOutput);
56 |
57 | static::assertFalse($response->hasFailed());
58 | }
59 |
60 | public function convertDataProvider(): array
61 | {
62 | $singleSource = $this->resourcesDir.'/moon_180.jpg';
63 | $multipleSources = [
64 | $this->resourcesDir.'/moon_180.jpg',
65 | $this->resourcesDir.'/dabug.png',
66 | ];
67 |
68 | return [
69 | [$singleSource, $this->resourcesDir.'/outputs/output1.pdf'],
70 | [$multipleSources, $this->resourcesDir.'/outputs/output2.pdf'],
71 | ];
72 | }
73 |
74 | public function testResizeImage(): void
75 | {
76 | $command = new Command(IMAGEMAGICK_DIR);
77 |
78 | $imageToResize = $this->resourcesDir.'/moon_180.jpg';
79 | $imageOutput = $this->resourcesDir.'/outputs/moon.jpg';
80 | static::assertFileExists($imageToResize);
81 |
82 | $response = $command
83 | ->convert($imageToResize)
84 | ->resize('100x100')
85 | ->file($imageOutput, false)
86 | ->run()
87 | ;
88 |
89 | static::assertFalse($response->hasFailed(), "Errors when testing:\n".$response->getProcess()->getOutput()."\t".$response->getProcess()->getErrorOutput());
90 |
91 | static::assertFileExists($this->resourcesDir.'/outputs/moon.jpg');
92 |
93 | static::assertFalse($response->hasFailed());
94 |
95 | $this->testConvertIdentifyImage($imageOutput, 'JPEG', '100x94+0+0', '8-bit');
96 | }
97 |
98 | public function testDepthImage(): void
99 | {
100 | $command = new Command(IMAGEMAGICK_DIR);
101 |
102 | $imageToResize = $this->resourcesDir.'/moon_180.jpg';
103 | $imageOutput = $this->resourcesDir.'/outputs/moon.jpg';
104 | static::assertFileExists($imageToResize);
105 |
106 | $response = $command
107 | ->convert($imageToResize)
108 | ->depth(1)
109 | ->file($imageOutput, false)
110 | ->run()
111 | ;
112 |
113 | static::assertFalse($response->hasFailed(), "Errors when testing:\n".$response->getProcess()->getOutput()."\t".$response->getProcess()->getErrorOutput());
114 |
115 | static::assertFileExists($this->resourcesDir.'/outputs/moon.jpg');
116 |
117 | static::assertFalse($response->hasFailed());
118 |
119 | $this->testConvertIdentifyImage($imageOutput, 'JPEG', '180x170+0+0', '8-bit');
120 | }
121 |
122 | public function testFlattenImage(): void
123 | {
124 | $command = new Command(IMAGEMAGICK_DIR);
125 |
126 | $imageToResize = $this->resourcesDir.'/moon_180.jpg';
127 | $imageOutput = $this->resourcesDir.'/outputs/moon.jpg';
128 | static::assertFileExists($imageToResize);
129 |
130 | $response = $command
131 | ->convert($imageToResize)
132 | ->flatten()
133 | ->file($imageOutput, false)
134 | ->run()
135 | ;
136 |
137 | static::assertFalse($response->hasFailed(), "Errors when testing:\n".$response->getProcess()->getOutput()."\t".$response->getProcess()->getErrorOutput());
138 |
139 | static::assertFileExists($this->resourcesDir.'/outputs/moon.jpg');
140 |
141 | static::assertFalse($response->hasFailed());
142 |
143 | $this->testConvertIdentifyImage($imageOutput, 'JPEG', '180x170+0+0', '8-bit');
144 | }
145 |
146 | public function testTranspose(): void
147 | {
148 | $command = new Command(IMAGEMAGICK_DIR);
149 |
150 | $imageSource = $this->resourcesDir.'/moon_180.jpg';
151 | $imageOutput = $this->resourcesDir.'/outputs/moon_transpose.jpg';
152 | static::assertFileExists($imageSource);
153 |
154 | $response = $command
155 | ->convert($imageSource)
156 | ->transpose()
157 | ->file($imageOutput, false)
158 | ->run()
159 | ;
160 |
161 | static::assertFalse($response->hasFailed());
162 | static::assertFileExists($this->resourcesDir.'/outputs/moon_transpose.jpg');
163 | }
164 |
165 | public function testTransverse(): void
166 | {
167 | $command = new Command(IMAGEMAGICK_DIR);
168 |
169 | $imageSource = $this->resourcesDir.'/moon_180.jpg';
170 | $imageOutput = $this->resourcesDir.'/outputs/moon_transverse.jpg';
171 | static::assertFileExists($imageSource);
172 |
173 | $response = $command
174 | ->convert($imageSource)
175 | ->transverse()
176 | ->file($imageOutput, false)
177 | ->run()
178 | ;
179 |
180 | static::assertFalse($response->hasFailed());
181 | static::assertFileExists($this->resourcesDir.'/outputs/moon_transverse.jpg');
182 | }
183 |
184 | public function testColorspaceImage(): void
185 | {
186 | $command = new Command(IMAGEMAGICK_DIR);
187 |
188 | $imageToResize = $this->resourcesDir.'/moon_180.jpg';
189 | $imageOutput = $this->resourcesDir.'/outputs/moon.jpg';
190 | static::assertFileExists($imageToResize);
191 |
192 | $response = $command
193 | ->convert($imageToResize)
194 | ->colorspace('Gray')
195 | ->file($imageOutput, false)
196 | ->run()
197 | ;
198 |
199 | static::assertFalse($response->hasFailed(), "Errors when testing:\n".$response->getProcess()->getOutput()."\t".$response->getProcess()->getErrorOutput());
200 |
201 | static::assertFileExists($this->resourcesDir.'/outputs/moon.jpg');
202 |
203 | static::assertFalse($response->hasFailed());
204 |
205 | $this->testConvertIdentifyImage($imageOutput, 'JPEG', '180x170+0+0', '8-bit');
206 | }
207 |
208 | public function testMonochrome(): void
209 | {
210 | $command = new Command(IMAGEMAGICK_DIR);
211 |
212 | $imageSource = $this->resourcesDir.'/moon_180.jpg';
213 | $imageOutput = $this->resourcesDir.'/outputs/moon_monochrome.jpg';
214 | static::assertFileExists($imageSource);
215 |
216 | $response = $command
217 | ->convert($imageSource)
218 | ->monochrome()
219 | ->file($imageOutput, false)
220 | ->run()
221 | ;
222 |
223 | static::assertFalse($response->hasFailed());
224 | static::assertFileExists($this->resourcesDir.'/outputs/moon_monochrome.jpg');
225 | }
226 |
227 | public function testGravityCommand(): void
228 | {
229 | $command = new Command(IMAGEMAGICK_DIR);
230 |
231 | $imageToResize = $this->resourcesDir.'/moon_180.jpg';
232 | $imageOutput = $this->resourcesDir.'/outputs/moon.jpg';
233 | static::assertFileExists($imageToResize);
234 |
235 | $response = $command
236 | ->convert($imageToResize)
237 | ->gravity('Center')
238 | ->extent('100x100')
239 | ->file($imageOutput, false)
240 | ->run()
241 | ;
242 |
243 | static::assertFalse($response->hasFailed(), "Errors when testing:\n".$response->getProcess()->getOutput()."\t".$response->getProcess()->getErrorOutput());
244 |
245 | static::assertFileExists($this->resourcesDir.'/outputs/moon.jpg');
246 |
247 | $this->testConvertIdentifyImage($imageOutput, 'JPEG', '100x100+0+0', '8-bit');
248 | }
249 |
250 | /**
251 | * @dataProvider provideImagesToIdentify
252 | */
253 | public function testConvertIdentifyImage($imageToIdentify, $expectedFormat, $expectedGeometry, $expectedResolution): void
254 | {
255 | $command = new Command(IMAGEMAGICK_DIR);
256 |
257 | // ImageMagick normalizes paths with "/" as directory separator
258 | $imageToIdentify = \str_replace('\\', '/', $imageToIdentify);
259 |
260 | $response = $command->identify($imageToIdentify)->run();
261 |
262 | static::assertFalse($response->hasFailed());
263 |
264 | $content = $response->getOutput();
265 |
266 | static::assertStringContainsString(\sprintf(
267 | '%s %s %s %s %s',
268 | $imageToIdentify,
269 | $expectedFormat,
270 | \preg_replace('~\+.*$~', '', $expectedGeometry),
271 | $expectedGeometry,
272 | $expectedResolution
273 | ), $content);
274 | }
275 |
276 | public function provideImagesToIdentify(): ?\Generator
277 | {
278 | yield [$this->resourcesDir.'/moon_180.jpg', 'JPEG', '180x170+0+0', '8-bit'];
279 | }
280 |
281 | public function testMogrifyResizeImage(): void
282 | {
283 | $command = new Command(IMAGEMAGICK_DIR);
284 |
285 | $sourceImage = $this->resourcesDir.'/moon_180.jpg';
286 | $imageOutput = $this->resourcesDir.'/outputs/moon_mogrify.jpg';
287 | static::assertFileExists($sourceImage);
288 |
289 | $baseSize = \filesize($sourceImage);
290 |
291 | if (\file_exists($imageOutput)) {
292 | \unlink($imageOutput);
293 | }
294 |
295 | \copy($sourceImage, $imageOutput);
296 |
297 | \clearstatcache(true, $imageOutput);
298 |
299 | if (!\file_exists($imageOutput)) {
300 | static::fail('File could not be copied from resources dir to output dir.');
301 | }
302 |
303 | $response = $command
304 | ->mogrify($imageOutput)
305 | ->background('#000000')
306 | ->extent('5000x5000')
307 | ->run()
308 | ;
309 |
310 | static::assertFileExists($imageOutput);
311 |
312 | static::assertTrue($response->isSuccessful(), "Command returned an error when testing mogrify resize:\n".$response->getOutput()."\n".$response->getError());
313 |
314 | static::assertGreaterThan($baseSize, \filesize($imageOutput));
315 | }
316 |
317 | /**
318 | * @dataProvider provideTestCommandString
319 | */
320 | public function testCommandString($source, $output, $geometry, $quality, $format): void
321 | {
322 | $command = new Command(IMAGEMAGICK_DIR);
323 |
324 | $commandString = $command
325 | ->convert($source)
326 | ->thumbnail($geometry)
327 | ->quality($quality)
328 | ->page($format)
329 | ->file($output, false)
330 | ->getCommand()
331 | ;
332 |
333 | $expected = \implode(' ', $command->getExecutable('convert')).
334 | ' \''.$source.'\''.
335 | ' -thumbnail "'.$geometry.'"'.
336 | ' -quality '.$quality.
337 | ' -page "'.$format.'"'.
338 | ' \''.$output.'\'';
339 |
340 | $expected = \str_replace('\\', '/', $expected);
341 |
342 | static::assertEquals($expected, $commandString);
343 | }
344 |
345 | public function provideTestCommandString(): ?\Generator
346 | {
347 | yield 0 => [$this->resourcesDir.'/moon_180.jpg', $this->resourcesDir.'/outputs/moon_10_forced.jpg', '10x10!', 10, 'a4'];
348 | yield 1 => [$this->resourcesDir.'/moon_180.jpg', $this->resourcesDir.'/outputs/moon_1000.jpg', '1000x1000', 100, '9x11'];
349 | yield 2 => [$this->resourcesDir.'/moon_180.jpg', $this->resourcesDir.'/outputs/moon_half.jpg', '50%', 50, 'halfletter'];
350 | yield 3 => [$this->resourcesDir.'/moon_180.jpg', $this->resourcesDir.'/outputs/moon_geometry.jpg', '30x30+20+20', 50, 'Letter+43+43'];
351 | }
352 |
353 | public function testWrongExecutable(): void
354 | {
355 | $this->expectException(\InvalidArgumentException::class);
356 |
357 | $command = new Command(IMAGEMAGICK_DIR);
358 | $command->getExecutable('this_executable_might_not_exist');
359 | }
360 |
361 | public function testInexistingFiles(): void
362 | {
363 | $command = new Command(IMAGEMAGICK_DIR);
364 |
365 | $exception = '';
366 | $file = __DIR__.'/this/file/does/not/exist';
367 | try {
368 | $command->file($file, true, true);
369 | } catch (\Exception $e) {
370 | $exception = $e->getMessage();
371 | }
372 | static::assertStringContainsString(\sprintf('The file "%s" is not found.', $file), $exception);
373 | }
374 | }
375 |
--------------------------------------------------------------------------------
/Resources/references.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | return [
15 | 'interlace_types' => [
16 | 'none',
17 | 'line',
18 | 'plane',
19 | 'partition',
20 | 'jpeg',
21 | 'gif',
22 | 'png',
23 | ],
24 | 'colorspace_values' => [
25 | 'CMY',
26 | 'CMYK',
27 | 'Gray',
28 | 'HCL',
29 | 'HCLp',
30 | 'HSB',
31 | 'HSI',
32 | 'HSL',
33 | 'HSV',
34 | 'HWB',
35 | 'Lab',
36 | 'LCHab',
37 | 'LCHuv',
38 | 'LMS',
39 | 'Log',
40 | 'Luv',
41 | 'OHTA',
42 | 'Rec601YCbCr',
43 | 'Rec709YCbCr',
44 | 'RGB',
45 | 'scRGB',
46 | 'sRGB',
47 | 'Transparent',
48 | 'xyY',
49 | 'XYZ',
50 | 'YCbCr',
51 | 'YCC',
52 | 'YDbDr',
53 | 'YIQ',
54 | 'YPbPr',
55 | 'YUV',
56 | ],
57 | 'colors' => [
58 | 'snow',
59 | 'snow1',
60 | 'snow2',
61 | 'RosyBrown1',
62 | 'RosyBrown2',
63 | 'snow3',
64 | 'LightCoral',
65 | 'IndianRed1',
66 | 'RosyBrown3',
67 | 'IndianRed2',
68 | 'RosyBrown',
69 | 'brown1',
70 | 'firebrick1',
71 | 'brown2',
72 | 'IndianRed',
73 | 'IndianRed3',
74 | 'firebrick2',
75 | 'snow4',
76 | 'brown3',
77 | 'red',
78 | 'red1',
79 | 'RosyBrown4',
80 | 'firebrick3',
81 | 'red2',
82 | 'firebrick',
83 | 'brown',
84 | 'red3',
85 | 'IndianRed4',
86 | 'brown4',
87 | 'firebrick4',
88 | 'DarkRed',
89 | 'red4',
90 | 'maroon',
91 | 'LightPink1',
92 | 'LightPink3',
93 | 'LightPink4',
94 | 'LightPink2',
95 | 'LightPink',
96 | 'pink',
97 | 'crimson',
98 | 'pink1',
99 | 'pink2',
100 | 'pink3',
101 | 'pink4',
102 | 'PaleVioletRed4',
103 | 'PaleVioletRed',
104 | 'PaleVioletRed2',
105 | 'PaleVioletRed1',
106 | 'PaleVioletRed3',
107 | 'LavenderBlush',
108 | 'LavenderBlush1',
109 | 'LavenderBlush3',
110 | 'LavenderBlush2',
111 | 'LavenderBlush4',
112 | 'maroon',
113 | 'HotPink3',
114 | 'VioletRed3',
115 | 'VioletRed1',
116 | 'VioletRed2',
117 | 'VioletRed4',
118 | 'HotPink2',
119 | 'HotPink1',
120 | 'HotPink4',
121 | 'HotPink',
122 | 'DeepPink',
123 | 'DeepPink1',
124 | 'DeepPink2',
125 | 'DeepPink3',
126 | 'DeepPink4',
127 | 'maroon1',
128 | 'maroon2',
129 | 'maroon3',
130 | 'maroon4',
131 | 'MediumVioletRed',
132 | 'VioletRed',
133 | 'orchid2',
134 | 'orchid',
135 | 'orchid1',
136 | 'orchid3',
137 | 'orchid4',
138 | 'thistle1',
139 | 'thistle2',
140 | 'plum1',
141 | 'plum2',
142 | 'thistle',
143 | 'thistle3',
144 | 'plum',
145 | 'violet',
146 | 'plum3',
147 | 'thistle4',
148 | 'fuchsia',
149 | 'magenta',
150 | 'magenta1',
151 | 'plum4',
152 | 'magenta2',
153 | 'magenta3',
154 | 'DarkMagenta',
155 | 'magenta4',
156 | 'purple',
157 | 'MediumOrchid',
158 | 'MediumOrchid1',
159 | 'MediumOrchid2',
160 | 'MediumOrchid3',
161 | 'MediumOrchid4',
162 | 'DarkViolet',
163 | 'DarkOrchid',
164 | 'DarkOrchid1',
165 | 'DarkOrchid3',
166 | 'DarkOrchid2',
167 | 'DarkOrchid4',
168 | 'purple',
169 | 'indigo',
170 | 'BlueViolet',
171 | 'purple2',
172 | 'purple3',
173 | 'purple4',
174 | 'purple1',
175 | 'MediumPurple',
176 | 'MediumPurple1',
177 | 'MediumPurple2',
178 | 'MediumPurple3',
179 | 'MediumPurple4',
180 | 'DarkSlateBlue',
181 | 'LightSlateBlue',
182 | 'MediumSlateBlue',
183 | 'SlateBlue',
184 | 'SlateBlue1',
185 | 'SlateBlue2',
186 | 'SlateBlue3',
187 | 'SlateBlue4',
188 | 'GhostWhite',
189 | 'lavender',
190 | 'blue',
191 | 'blue1',
192 | 'blue2',
193 | 'blue3',
194 | 'MediumBlue',
195 | 'blue4',
196 | 'DarkBlue',
197 | 'MidnightBlue',
198 | 'navy',
199 | 'NavyBlue',
200 | 'RoyalBlue',
201 | 'RoyalBlue1',
202 | 'RoyalBlue2',
203 | 'RoyalBlue3',
204 | 'RoyalBlue4',
205 | 'CornflowerBlue',
206 | 'LightSteelBlue',
207 | 'LightSteelBlue1',
208 | 'LightSteelBlue2',
209 | 'LightSteelBlue3',
210 | 'LightSteelBlue4',
211 | 'SlateGray4',
212 | 'SlateGray1',
213 | 'SlateGray2',
214 | 'SlateGray3',
215 | 'LightSlateGray',
216 | 'LightSlateGrey',
217 | 'SlateGray',
218 | 'SlateGrey',
219 | 'DodgerBlue',
220 | 'DodgerBlue1',
221 | 'DodgerBlue2',
222 | 'DodgerBlue4',
223 | 'DodgerBlue3',
224 | 'AliceBlue',
225 | 'SteelBlue4',
226 | 'SteelBlue',
227 | 'SteelBlue1',
228 | 'SteelBlue2',
229 | 'SteelBlue3',
230 | 'SkyBlue4',
231 | 'SkyBlue1',
232 | 'SkyBlue2',
233 | 'SkyBlue3',
234 | 'LightSkyBlue',
235 | 'LightSkyBlue4',
236 | 'LightSkyBlue1',
237 | 'LightSkyBlue2',
238 | 'LightSkyBlue3',
239 | 'SkyBlue',
240 | 'LightBlue3',
241 | 'DeepSkyBlue',
242 | 'DeepSkyBlue1',
243 | 'DeepSkyBlue2',
244 | 'DeepSkyBlue4',
245 | 'DeepSkyBlue3',
246 | 'LightBlue1',
247 | 'LightBlue2',
248 | 'LightBlue',
249 | 'LightBlue4',
250 | 'PowderBlue',
251 | 'CadetBlue1',
252 | 'CadetBlue2',
253 | 'CadetBlue3',
254 | 'CadetBlue4',
255 | 'turquoise1',
256 | 'turquoise2',
257 | 'turquoise3',
258 | 'turquoise4',
259 | 'cadet',
260 | 'CadetBlue',
261 | 'DarkTurquoise',
262 | 'azure',
263 | 'azure1',
264 | 'LightCyan',
265 | 'LightCyan1',
266 | 'azure2',
267 | 'LightCyan2',
268 | 'PaleTurquoise1',
269 | 'PaleTurquoise',
270 | 'PaleTurquoise2',
271 | 'DarkSlateGray1',
272 | 'azure3',
273 | 'LightCyan3',
274 | 'DarkSlateGray2',
275 | 'PaleTurquoise3',
276 | 'DarkSlateGray3',
277 | 'azure4',
278 | 'LightCyan4',
279 | 'aqua',
280 | 'cyan',
281 | 'cyan1',
282 | 'PaleTurquoise4',
283 | 'cyan2',
284 | 'DarkSlateGray4',
285 | 'cyan3',
286 | 'cyan4',
287 | 'DarkCyan',
288 | 'teal',
289 | 'DarkSlateGray',
290 | 'DarkSlateGrey',
291 | 'MediumTurquoise',
292 | 'LightSeaGreen',
293 | 'turquoise',
294 | 'aquamarine4',
295 | 'aquamarine',
296 | 'aquamarine1',
297 | 'aquamarine2',
298 | 'aquamarine3',
299 | 'MediumAquamarine',
300 | 'MediumSpringGreen',
301 | 'MintCream',
302 | 'SpringGreen',
303 | 'SpringGreen1',
304 | 'SpringGreen2',
305 | 'SpringGreen3',
306 | 'SpringGreen4',
307 | 'MediumSeaGreen',
308 | 'SeaGreen',
309 | 'SeaGreen3',
310 | 'SeaGreen1',
311 | 'SeaGreen4',
312 | 'SeaGreen2',
313 | 'MediumForestGreen',
314 | 'honeydew',
315 | 'honeydew1',
316 | 'honeydew2',
317 | 'DarkSeaGreen1',
318 | 'DarkSeaGreen2',
319 | 'PaleGreen1',
320 | 'PaleGreen',
321 | 'honeydew3',
322 | 'LightGreen',
323 | 'PaleGreen2',
324 | 'DarkSeaGreen3',
325 | 'DarkSeaGreen',
326 | 'PaleGreen3',
327 | 'honeydew4',
328 | 'green1',
329 | 'lime',
330 | 'LimeGreen',
331 | 'DarkSeaGreen4',
332 | 'green2',
333 | 'PaleGreen4',
334 | 'green3',
335 | 'ForestGreen',
336 | 'green4',
337 | 'green',
338 | 'DarkGreen',
339 | 'LawnGreen',
340 | 'chartreuse',
341 | 'chartreuse1',
342 | 'chartreuse2',
343 | 'chartreuse3',
344 | 'chartreuse4',
345 | 'GreenYellow',
346 | 'DarkOliveGreen3',
347 | 'DarkOliveGreen1',
348 | 'DarkOliveGreen2',
349 | 'DarkOliveGreen4',
350 | 'DarkOliveGreen',
351 | 'OliveDrab',
352 | 'OliveDrab1',
353 | 'OliveDrab2',
354 | 'OliveDrab3',
355 | 'YellowGreen',
356 | 'OliveDrab4',
357 | 'ivory',
358 | 'ivory1',
359 | 'LightYellow',
360 | 'LightYellow1',
361 | 'beige',
362 | 'ivory2',
363 | 'LightGoldenrodYellow',
364 | 'LightYellow2',
365 | 'ivory3',
366 | 'LightYellow3',
367 | 'ivory4',
368 | 'LightYellow4',
369 | 'yellow',
370 | 'yellow1',
371 | 'yellow2',
372 | 'yellow3',
373 | 'yellow4',
374 | 'olive',
375 | 'DarkKhaki',
376 | 'khaki2',
377 | 'LemonChiffon4',
378 | 'khaki1',
379 | 'khaki3',
380 | 'khaki4',
381 | 'PaleGoldenrod',
382 | 'LemonChiffon',
383 | 'LemonChiffon1',
384 | 'khaki',
385 | 'LemonChiffon3',
386 | 'LemonChiffon2',
387 | 'MediumGoldenRod',
388 | 'cornsilk4',
389 | 'gold',
390 | 'gold1',
391 | 'gold2',
392 | 'gold3',
393 | 'gold4',
394 | 'LightGoldenrod',
395 | 'LightGoldenrod4',
396 | 'LightGoldenrod1',
397 | 'LightGoldenrod3',
398 | 'LightGoldenrod2',
399 | 'cornsilk3',
400 | 'cornsilk2',
401 | 'cornsilk',
402 | 'cornsilk1',
403 | 'goldenrod',
404 | 'goldenrod1',
405 | 'goldenrod2',
406 | 'goldenrod3',
407 | 'goldenrod4',
408 | 'DarkGoldenrod',
409 | 'DarkGoldenrod1',
410 | 'DarkGoldenrod2',
411 | 'DarkGoldenrod3',
412 | 'DarkGoldenrod4',
413 | 'FloralWhite',
414 | 'wheat2',
415 | 'OldLace',
416 | 'wheat',
417 | 'wheat1',
418 | 'wheat3',
419 | 'orange',
420 | 'orange1',
421 | 'orange2',
422 | 'orange3',
423 | 'orange4',
424 | 'wheat4',
425 | 'moccasin',
426 | 'PapayaWhip',
427 | 'NavajoWhite3',
428 | 'BlanchedAlmond',
429 | 'NavajoWhite',
430 | 'NavajoWhite1',
431 | 'NavajoWhite2',
432 | 'NavajoWhite4',
433 | 'AntiqueWhite4',
434 | 'AntiqueWhite',
435 | 'tan',
436 | 'bisque4',
437 | 'burlywood',
438 | 'AntiqueWhite2',
439 | 'burlywood1',
440 | 'burlywood3',
441 | 'burlywood2',
442 | 'AntiqueWhite1',
443 | 'burlywood4',
444 | 'AntiqueWhite3',
445 | 'DarkOrange',
446 | 'bisque2',
447 | 'bisque',
448 | 'bisque1',
449 | 'bisque3',
450 | 'DarkOrange1',
451 | 'linen',
452 | 'DarkOrange2',
453 | 'DarkOrange3',
454 | 'DarkOrange4',
455 | 'peru',
456 | 'tan1',
457 | 'tan2',
458 | 'tan3',
459 | 'tan4',
460 | 'PeachPuff',
461 | 'PeachPuff1',
462 | 'PeachPuff4',
463 | 'PeachPuff2',
464 | 'PeachPuff3',
465 | 'SandyBrown',
466 | 'seashell4',
467 | 'seashell2',
468 | 'seashell3',
469 | 'chocolate',
470 | 'chocolate1',
471 | 'chocolate2',
472 | 'chocolate3',
473 | 'chocolate4',
474 | 'SaddleBrown',
475 | 'seashell',
476 | 'seashell1',
477 | 'sienna4',
478 | 'sienna',
479 | 'sienna1',
480 | 'sienna2',
481 | 'sienna3',
482 | 'LightSalmon3',
483 | 'LightSalmon',
484 | 'LightSalmon1',
485 | 'LightSalmon4',
486 | 'LightSalmon2',
487 | 'coral',
488 | 'OrangeRed',
489 | 'OrangeRed1',
490 | 'OrangeRed2',
491 | 'OrangeRed3',
492 | 'OrangeRed4',
493 | 'DarkSalmon',
494 | 'salmon1',
495 | 'salmon2',
496 | 'salmon3',
497 | 'salmon4',
498 | 'coral1',
499 | 'coral2',
500 | 'coral3',
501 | 'coral4',
502 | 'tomato4',
503 | 'tomato',
504 | 'tomato1',
505 | 'tomato2',
506 | 'tomato3',
507 | 'MistyRose4',
508 | 'MistyRose2',
509 | 'MistyRose',
510 | 'MistyRose1',
511 | 'salmon',
512 | 'MistyRose3',
513 | 'white',
514 | 'gray100',
515 | 'grey100',
516 | 'grey100',
517 | 'gray99',
518 | 'grey99',
519 | 'gray98',
520 | 'grey98',
521 | 'gray97',
522 | 'grey97',
523 | 'gray96',
524 | 'grey96',
525 | 'WhiteSmoke',
526 | 'gray95',
527 | 'grey95',
528 | 'gray94',
529 | 'grey94',
530 | 'gray93',
531 | 'grey93',
532 | 'gray92',
533 | 'grey92',
534 | 'gray91',
535 | 'grey91',
536 | 'gray90',
537 | 'grey90',
538 | 'gray89',
539 | 'grey89',
540 | 'gray88',
541 | 'grey88',
542 | 'gray87',
543 | 'grey87',
544 | 'gainsboro',
545 | 'gray86',
546 | 'grey86',
547 | 'gray85',
548 | 'grey85',
549 | 'gray84',
550 | 'grey84',
551 | 'gray83',
552 | 'grey83',
553 | 'LightGray',
554 | 'LightGrey',
555 | 'gray82',
556 | 'grey82',
557 | 'gray81',
558 | 'grey81',
559 | 'gray80',
560 | 'grey80',
561 | 'gray79',
562 | 'grey79',
563 | 'gray78',
564 | 'grey78',
565 | 'gray77',
566 | 'grey77',
567 | 'gray76',
568 | 'grey76',
569 | 'silver',
570 | 'gray75',
571 | 'grey75',
572 | 'gray74',
573 | 'grey74',
574 | 'gray73',
575 | 'grey73',
576 | 'gray72',
577 | 'grey72',
578 | 'gray71',
579 | 'grey71',
580 | 'gray70',
581 | 'grey70',
582 | 'gray69',
583 | 'grey69',
584 | 'gray68',
585 | 'grey68',
586 | 'gray67',
587 | 'grey67',
588 | 'DarkGray',
589 | 'DarkGrey',
590 | 'gray66',
591 | 'grey66',
592 | 'gray65',
593 | 'grey65',
594 | 'gray64',
595 | 'grey64',
596 | 'gray63',
597 | 'grey63',
598 | 'gray62',
599 | 'grey62',
600 | 'gray61',
601 | 'grey61',
602 | 'gray60',
603 | 'grey60',
604 | 'gray59',
605 | 'grey59',
606 | 'gray58',
607 | 'grey58',
608 | 'gray57',
609 | 'grey57',
610 | 'gray56',
611 | 'grey56',
612 | 'gray55',
613 | 'grey55',
614 | 'gray54',
615 | 'grey54',
616 | 'gray53',
617 | 'grey53',
618 | 'gray52',
619 | 'grey52',
620 | 'gray51',
621 | 'grey51',
622 | 'fractal',
623 | 'gray50',
624 | 'grey50',
625 | 'gray',
626 | 'gray49',
627 | 'grey49',
628 | 'gray48',
629 | 'grey48',
630 | 'gray47',
631 | 'grey47',
632 | 'gray46',
633 | 'grey46',
634 | 'gray45',
635 | 'grey45',
636 | 'gray44',
637 | 'grey44',
638 | 'gray43',
639 | 'grey43',
640 | 'gray42',
641 | 'grey42',
642 | 'DimGray',
643 | 'DimGrey',
644 | 'gray41',
645 | 'grey41',
646 | 'gray40',
647 | 'grey40',
648 | 'gray39',
649 | 'grey39',
650 | 'gray38',
651 | 'grey38',
652 | 'gray37',
653 | 'grey37',
654 | 'gray36',
655 | 'grey36',
656 | 'gray35',
657 | 'grey35',
658 | 'gray34',
659 | 'grey34',
660 | 'gray33',
661 | 'grey33',
662 | 'gray32',
663 | 'grey32',
664 | 'gray31',
665 | 'grey31',
666 | 'gray30',
667 | 'grey30',
668 | 'gray29',
669 | 'grey29',
670 | 'gray28',
671 | 'grey28',
672 | 'gray27',
673 | 'grey27',
674 | 'gray26',
675 | 'grey26',
676 | 'gray25',
677 | 'grey25',
678 | 'gray24',
679 | 'grey24',
680 | 'gray23',
681 | 'grey23',
682 | 'gray22',
683 | 'grey22',
684 | 'gray21',
685 | 'grey21',
686 | 'gray20',
687 | 'grey20',
688 | 'gray19',
689 | 'grey19',
690 | 'gray18',
691 | 'grey18',
692 | 'gray17',
693 | 'grey17',
694 | 'gray16',
695 | 'grey16',
696 | 'gray15',
697 | 'grey15',
698 | 'gray14',
699 | 'grey14',
700 | 'gray13',
701 | 'grey13',
702 | 'gray12',
703 | 'grey12',
704 | 'gray11',
705 | 'grey11',
706 | 'gray10',
707 | 'grey10',
708 | 'gray9',
709 | 'grey9',
710 | 'gray8',
711 | 'grey8',
712 | 'gray7',
713 | 'grey7',
714 | 'gray6',
715 | 'grey6',
716 | 'gray5',
717 | 'grey5',
718 | 'gray4',
719 | 'grey4',
720 | 'gray3',
721 | 'grey3',
722 | 'gray2',
723 | 'grey2',
724 | 'gray1',
725 | 'grey1',
726 | 'black',
727 | 'gray0',
728 | 'grey0',
729 |
730 | // Special colors:
731 | 'opaque',
732 | 'none',
733 | 'transparent',
734 | ],
735 | 'paper_sizes' => [
736 | '2A0',
737 | '4A0',
738 | '4x6',
739 | '5x7',
740 | '7x9',
741 | '8x10',
742 | '9x11',
743 | '9x12',
744 | '10x13',
745 | '10x14',
746 | '11x17',
747 | 'a0',
748 | 'a1',
749 | 'a2',
750 | 'a3',
751 | 'a4',
752 | 'a4small',
753 | 'a5',
754 | 'a6',
755 | 'a7',
756 | 'a8',
757 | 'a9',
758 | 'a10',
759 | 'archa',
760 | 'archb',
761 | 'archC',
762 | 'archd',
763 | 'arche',
764 | 'b0',
765 | 'b1',
766 | 'b2',
767 | 'b3',
768 | 'b4',
769 | 'b5',
770 | 'b6',
771 | 'b7',
772 | 'b8',
773 | 'b9',
774 | 'b10',
775 | 'c0',
776 | 'c1',
777 | 'c2',
778 | 'c3',
779 | 'c4',
780 | 'c5',
781 | 'c6',
782 | 'c7',
783 | 'csheet',
784 | 'dsheet',
785 | 'esheet',
786 | 'executive',
787 | 'flsa',
788 | 'flse',
789 | 'folio',
790 | 'halfletter',
791 | 'isob0',
792 | 'isob1',
793 | 'isob2',
794 | 'isob3',
795 | 'isob4',
796 | 'isob5',
797 | 'isob6',
798 | 'isob7',
799 | 'isob8',
800 | 'isob9',
801 | 'isob10',
802 | 'jisb0',
803 | 'jisb1',
804 | 'jisb2',
805 | 'jisb3',
806 | 'jisb4',
807 | 'jisb5',
808 | 'jisb6',
809 | 'ledger',
810 | 'legal',
811 | 'letter',
812 | 'lettersmall',
813 | 'monarch',
814 | 'quarto',
815 | 'statement',
816 | 'tabloid',
817 | ],
818 | ];
819 |
--------------------------------------------------------------------------------
/src/Command.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick;
15 |
16 | use Orbitale\Component\ImageMagick\ReferenceClasses\Geometry;
17 | use Orbitale\Component\ImageMagick\ReferenceClasses\Gravity;
18 | use Symfony\Component\Process\ExecutableFinder;
19 | use Symfony\Component\Process\Process;
20 |
21 | /**
22 | * @author Alexandre Rock Ancelet
23 | */
24 | class Command
25 | {
26 | /**
27 | * The list of allowed ImageMagick binaries.
28 | */
29 | public const ALLOWED_EXECUTABLES = [
30 | 'animate',
31 | 'compare',
32 | 'composite',
33 | 'conjure',
34 | 'convert',
35 | 'display',
36 | 'identify',
37 | 'import',
38 | 'mogrify',
39 | 'montage',
40 | 'stream',
41 | ];
42 |
43 | /**
44 | * @var string
45 | */
46 | protected $magickBinaryPath = '';
47 |
48 | /**
49 | * Environment variables used during the execution of this command.
50 | *
51 | * @var string[]
52 | */
53 | protected $env = [];
54 |
55 | /**
56 | * @var References
57 | */
58 | protected $ref;
59 |
60 | /**
61 | * The shell command.
62 | *
63 | * @var string[]
64 | */
65 | protected $command = [];
66 |
67 | /**
68 | * Parameters to add at the end of the command.
69 | *
70 | * @var string[]
71 | */
72 | protected $commandToAppend = [];
73 |
74 | /**
75 | * @var int
76 | */
77 | protected $version;
78 |
79 | public function __construct(?string $magickBinaryPath = '')
80 | {
81 | $magickBinaryPath = self::findMagickBinaryPath($magickBinaryPath);
82 |
83 | $process = Process::fromShellCommandline($magickBinaryPath.' -version');
84 |
85 | try {
86 | $code = $process->run();
87 | } catch (\Throwable $e) {
88 | throw new MagickBinaryNotFoundException('Could not check ImageMagick version', 1, $e);
89 | }
90 |
91 | if (0 !== $code || !$process->isSuccessful()) {
92 | throw new MagickBinaryNotFoundException(\sprintf("ImageMagick does not seem to work well, the test command resulted in an error.\n"."Execution returned message: \"{$process->getExitCodeText()}\"\n"."To solve this issue, please run this command and check your error messages to see if ImageMagick was correctly installed:\n%s", $magickBinaryPath.' -version'));
93 | }
94 |
95 | $this->ref = new References();
96 |
97 | $this->magickBinaryPath = $magickBinaryPath;
98 | }
99 |
100 | public static function create(?string $magickBinaryPath = null): self
101 | {
102 | return new self($magickBinaryPath);
103 | }
104 |
105 | public static function findMagickBinaryPath(?string $magickBinaryPath): string
106 | {
107 | // Delete trimming directory separator
108 | $magickBinaryPath = self::cleanPath((string) $magickBinaryPath, true, false);
109 |
110 | if (!$magickBinaryPath) {
111 | $magickBinaryPath = (new ExecutableFinder())->find('magick');
112 | }
113 |
114 | if (!$magickBinaryPath) {
115 | throw new MagickBinaryNotFoundException((string) $magickBinaryPath);
116 | }
117 |
118 | return $magickBinaryPath;
119 | }
120 |
121 | private static function cleanPath(string $path, bool $rtrim = false, bool $escape = true): string
122 | {
123 | $path = \str_replace('\\', '/', $path);
124 |
125 | if ($rtrim) {
126 | $path = \rtrim($path, '/');
127 | }
128 |
129 | if ($escape) {
130 | $path = \escapeshellarg($path);
131 | }
132 |
133 | return $path;
134 | }
135 |
136 | /**
137 | * This command is used for the "legacy" binaries that can still be used with ImageMagick 7.
138 | *
139 | * @see https://imagemagick.org/script/command-line-tools.php
140 | */
141 | public function getExecutable(?string $binary = null): array
142 | {
143 | if (!\in_array($binary, static::ALLOWED_EXECUTABLES, true)) {
144 | throw new \InvalidArgumentException(\sprintf("The ImageMagick executable \"%s\" is not allowed.\n"."The only binaries allowed to be executed are the following:\n%s", $binary, \implode(', ', static::ALLOWED_EXECUTABLES)));
145 | }
146 |
147 | return [$this->magickBinaryPath, $binary];
148 | }
149 |
150 | /**
151 | * Entirely reset all current command statements, and start a whole new command.
152 | */
153 | public function newCommand(?string $binary = null): self
154 | {
155 | $this->env = [];
156 | $this->command = $binary ? $this->getExecutable($binary) : [];
157 | $this->commandToAppend = [];
158 |
159 | return $this;
160 | }
161 |
162 | /**
163 | * @see https://imagemagick.org/script/convert.php
164 | */
165 | public function convert($sourceFiles, bool $checkIfFileExists = true): self
166 | {
167 | if (!\is_array($sourceFiles)) {
168 | $sourceFiles = [$sourceFiles];
169 | }
170 |
171 | $this->newCommand('convert');
172 | foreach ($sourceFiles as $sourceFile) {
173 | $this->file($sourceFile, $checkIfFileExists);
174 | }
175 |
176 | return $this;
177 | }
178 |
179 | /**
180 | * @see https://imagemagick.org/script/mogrify.php
181 | */
182 | public function mogrify(?string $sourceFile = null): self
183 | {
184 | $this->newCommand('mogrify');
185 | if ($sourceFile) {
186 | $this->file($sourceFile, true, true);
187 | }
188 |
189 | return $this;
190 | }
191 |
192 | /**
193 | * @see https://imagemagick.org/script/identify.php
194 | */
195 | public function identify(string $sourceFile): self
196 | {
197 | return $this->newCommand('identify')->file($sourceFile);
198 | }
199 |
200 | /**
201 | * @see https://imagemagick.org/script/composite.php
202 | */
203 | public function composite(): self
204 | {
205 | return $this->newCommand('composite');
206 | }
207 |
208 | /**
209 | * @see https://imagemagick.org/script/animate.php
210 | */
211 | public function animate(): self
212 | {
213 | return $this->newCommand('animate');
214 | }
215 |
216 | /**
217 | * @see https://imagemagick.org/script/compare.php
218 | */
219 | public function compare(): self
220 | {
221 | return $this->newCommand('compare');
222 | }
223 |
224 | /**
225 | * @see https://imagemagick.org/script/conjure.php
226 | */
227 | public function conjure(): self
228 | {
229 | return $this->newCommand('conjure');
230 | }
231 |
232 | /**
233 | * @see https://imagemagick.org/script/display.php
234 | */
235 | public function display(): self
236 | {
237 | return $this->newCommand('display');
238 | }
239 |
240 | /**
241 | * @see https://imagemagick.org/script/import.php
242 | */
243 | public function import(): self
244 | {
245 | return $this->newCommand('import');
246 | }
247 |
248 | /**
249 | * @see https://imagemagick.org/script/montage.php
250 | */
251 | public function montage(): self
252 | {
253 | return $this->newCommand('montage');
254 | }
255 |
256 | /**
257 | * @see https://imagemagick.org/script/stream.php
258 | */
259 | public function stream(): self
260 | {
261 | return $this->newCommand('stream');
262 | }
263 |
264 | public function run(): CommandResponse
265 | {
266 | $process = Process::fromShellCommandline($this->getCommand(), null, $this->env);
267 |
268 | $output = '';
269 | $error = '';
270 |
271 | $code = $process->run(static function ($type, $buffer) use (&$output, &$error): void {
272 | if (Process::ERR === $type) {
273 | $error .= $buffer;
274 | } else {
275 | $output .= $buffer;
276 | }
277 | });
278 |
279 | if ($code !== 0) {
280 | echo "\n>>> Command-line: « {$process->getCommandLine()} » \n";
281 | echo "\n>>> Output:\n>{$output}\n";
282 | echo "\n>>> Error:\n>{$error}\n";
283 | }
284 |
285 |
286 | return new CommandResponse($process, $code, $output, $error);
287 | }
288 |
289 | /**
290 | * Adds environment variable to ImageMagick at runtime.
291 | *
292 | * @see https://imagemagick.org/script/resources.php#environment
293 | */
294 | public function setEnv(string $key, string $value): void
295 | {
296 | $this->env[$key] = $value;
297 | }
298 |
299 | /**
300 | * Get the final command that will be executed when using Command::run().
301 | */
302 | public function getCommand(): string
303 | {
304 | return \implode(' ', \array_merge($this->command, $this->commandToAppend));
305 | }
306 |
307 | /**
308 | * Add a file specification to the command, mostly for source or destination file.
309 | */
310 | public function file(string $source, bool $checkIfFileExists = true, bool $appendToCommand = false): self
311 | {
312 | $source = $checkIfFileExists ? $this->checkExistingFile($source) : self::cleanPath($source);
313 | $source = \str_replace('\\', '/', $source);
314 | if ($appendToCommand) {
315 | $this->commandToAppend[] = $source;
316 | } else {
317 | $this->command[] = $source;
318 | }
319 |
320 | return $this;
321 | }
322 |
323 | /**
324 | * Add an output file to the end of the command.
325 | */
326 | public function output(string $source): self
327 | {
328 | return $this->file($source, false, true);
329 | }
330 |
331 | /* --------------------------------- *
332 | * Start imagemagick native options. *
333 | * --------------------------------- */
334 |
335 | /**
336 | * @see http://imagemagick.org/script/command-line-options.php#background
337 | */
338 | public function background(string $color): self
339 | {
340 | $this->command[] = '-background';
341 | $this->command[] = '"'.$this->ref->color($color).'"';
342 |
343 | return $this;
344 | }
345 |
346 | /**
347 | * @see http://imagemagick.org/script/command-line-options.php#fill
348 | */
349 | public function fill(string $color): self
350 | {
351 | $this->command[] = '-fill';
352 | $this->command[] = '"'.$this->ref->color($color).'"';
353 |
354 | return $this;
355 | }
356 |
357 | /**
358 | * @param string|Geometry $geometry
359 | *
360 | * @see http://imagemagick.org/script/command-line-options.php#resize
361 | */
362 | public function resize($geometry): self
363 | {
364 | $this->command[] = '-resize';
365 | $this->command[] = '"'.$this->ref->geometry($geometry).'"';
366 |
367 | return $this;
368 | }
369 |
370 | /**
371 | * @param string|Geometry $geometry
372 | *
373 | * @see http://imagemagick.org/script/command-line-options.php#size
374 | */
375 | public function size($geometry): self
376 | {
377 | $this->command[] = '-size';
378 | $this->command[] = '"'.$this->ref->geometry($geometry).'"';
379 |
380 | return $this;
381 | }
382 |
383 | /**
384 | * Create a colored canvas.
385 | *
386 | * @see http://www.imagemagick.org/Usage/canvas/
387 | */
388 | public function xc(string $canvasColor = 'none'): self
389 | {
390 | $this->command[] = '"xc:'.$this->ref->color($canvasColor).'"';
391 |
392 | return $this;
393 | }
394 |
395 | /**
396 | * @param string|Geometry $geometry
397 | *
398 | * @see http://imagemagick.org/script/command-line-options.php#crop
399 | */
400 | public function crop($geometry): self
401 | {
402 | $this->command[] = '-crop';
403 | $this->command[] = '"'.$this->ref->geometry($geometry).'"';
404 |
405 | return $this;
406 | }
407 |
408 | /**
409 | * @param string|Geometry $geometry
410 | *
411 | * @see http://imagemagick.org/script/command-line-options.php#geometry
412 | */
413 | public function geometry($geometry): self
414 | {
415 | $this->command[] = '-geometry';
416 | $this->command[] = '"'.$this->ref->geometry($geometry).'"';
417 |
418 | return $this;
419 | }
420 |
421 | /**
422 | * @param string|Geometry $geometry
423 | *
424 | * @see http://imagemagick.org/script/command-line-options.php#extent
425 | */
426 | public function extent($geometry): self
427 | {
428 | $this->command[] = '-extent';
429 | $this->command[] = '"'.$this->ref->geometry($geometry).'"';
430 |
431 | return $this;
432 | }
433 |
434 | /**
435 | * @param string|Gravity $gravity
436 | *
437 | * @see https://www.imagemagick.org/script/command-line-options.php#gravity
438 | */
439 | public function gravity($gravity): self
440 | {
441 | $this->command[] = '-gravity';
442 | $this->command[] = '"'.$this->ref->gravity($gravity).'"';
443 |
444 | return $this;
445 | }
446 |
447 | /**
448 | * @param string|Geometry $geometry
449 | *
450 | * @see http://imagemagick.org/script/command-line-options.php#thumbnail
451 | */
452 | public function thumbnail($geometry): self
453 | {
454 | $this->command[] = '-thumbnail';
455 | $this->command[] = '"'.$this->ref->geometry($geometry).'"';
456 |
457 | return $this;
458 | }
459 |
460 | /**
461 | * @see http://imagemagick.org/script/command-line-options.php#quality
462 | */
463 | public function quality(int $quality): self
464 | {
465 | $this->command[] = '-quality';
466 | $this->command[] = (string) $quality;
467 |
468 | return $this;
469 | }
470 |
471 | /**
472 | * @param string|Geometry $page
473 | *
474 | * @see http://imagemagick.org/script/command-line-options.php#page
475 | */
476 | public function page($page): self
477 | {
478 | $this->command[] = '-page';
479 | $this->command[] = '"'.$this->ref->page($page).'"';
480 |
481 | return $this;
482 | }
483 |
484 | /**
485 | * @see http://imagemagick.org/script/command-line-options.php#rotate
486 | */
487 | public function rotate(string $rotation): self
488 | {
489 | $this->command[] = '-rotate';
490 | $this->command[] = \escapeshellarg($this->ref->rotation($rotation));
491 |
492 | return $this;
493 | }
494 |
495 | /**
496 | * @see http://imagemagick.org/script/command-line-options.php#strip
497 | */
498 | public function strip(): self
499 | {
500 | $this->command[] = '-strip';
501 |
502 | return $this;
503 | }
504 |
505 | /**
506 | * @see http://imagemagick.org/script/command-line-options.php#monochrome
507 | */
508 | public function monochrome(): self
509 | {
510 | $this->command[] = '-monochrome';
511 |
512 | return $this;
513 | }
514 |
515 | /**
516 | * @see http://imagemagick.org/script/command-line-options.php#interlace
517 | */
518 | public function interlace(string $type): self
519 | {
520 | $this->command[] = '-interlace';
521 | $this->command[] = $this->ref->interlace($type);
522 |
523 | return $this;
524 | }
525 |
526 | /**
527 | * @see http://imagemagick.org/script/command-line-options.php#gaussian-blur
528 | */
529 | public function gaussianBlur($blur): self
530 | {
531 | $this->command[] = '-gaussian-blur';
532 | $this->command[] = $this->ref->blur($blur);
533 |
534 | return $this;
535 | }
536 |
537 | /**
538 | * @see http://imagemagick.org/script/command-line-options.php#blur
539 | */
540 | public function blur($blur): self
541 | {
542 | $this->command[] = '-blur';
543 | $this->command[] = $this->ref->blur($blur);
544 |
545 | return $this;
546 | }
547 |
548 | /**
549 | * @see http://imagemagick.org/script/command-line-options.php#font
550 | */
551 | public function font(string $fontFile, bool $checkFontFileExists = false): self
552 | {
553 | $this->command[] = '-font';
554 | $this->command[] = ($checkFontFileExists ? $this->checkExistingFile($fontFile) : $fontFile);
555 |
556 | return $this;
557 | }
558 |
559 | /**
560 | * @see http://imagemagick.org/script/command-line-options.php#pointsize
561 | */
562 | public function pointsize(int $pointsize): self
563 | {
564 | $this->command[] = '-pointsize';
565 | $this->command[] = $pointsize;
566 |
567 | return $this;
568 | }
569 |
570 | /**
571 | * @see http://imagemagick.org/script/command-line-options.php#stroke
572 | */
573 | public function stroke(string $color): self
574 | {
575 | $this->command[] = '-stroke';
576 | $this->command[] = '"'.$this->ref->color($color).'"';
577 |
578 | return $this;
579 | }
580 |
581 | /**
582 | * @see http://imagemagick.org/script/command-line-options.php#strokewidth
583 | */
584 | public function strokeWidth(float $strokeWidth): self
585 | {
586 | $this->command[] = '-strokewidth';
587 | $this->command[] = (string) $strokeWidth;
588 |
589 | return $this;
590 | }
591 |
592 | /**
593 | * @see https://imagemagick.org/script/command-line-options.php#auto-orient
594 | */
595 | public function autoOrient(): self
596 | {
597 | $this->command[] = '-auto-orient';
598 |
599 | return $this;
600 | }
601 |
602 | /**
603 | * @see https://imagemagick.org/script/command-line-options.php#depth
604 | */
605 | public function depth(int $depth): self
606 | {
607 | $this->command[] = '-depth '.$depth;
608 |
609 | return $this;
610 | }
611 |
612 | /**
613 | * @see https://imagemagick.org/script/command-line-options.php#flatten
614 | */
615 | public function flatten(): self
616 | {
617 | $this->command[] = '-flatten';
618 |
619 | return $this;
620 | }
621 |
622 | /**
623 | * @see https://imagemagick.org/script/command-line-options.php#colorspace
624 | */
625 | public function colorspace(string $colorspace): self
626 | {
627 | $this->command[] = '-colorspace';
628 | $this->command[] = $this->ref->colorspace($colorspace);
629 |
630 | return $this;
631 | }
632 |
633 | /**
634 | * @see http://imagemagick.org/script/command-line-options.php#transpose
635 | */
636 | public function transpose(): self
637 | {
638 | $this->command[] = '-transpose';
639 |
640 | return $this;
641 | }
642 |
643 | /**
644 | * @see http://imagemagick.org/script/command-line-options.php#transverse
645 | */
646 | public function transverse(): self
647 | {
648 | $this->command[] = '-transverse';
649 |
650 | return $this;
651 | }
652 |
653 | /**
654 | * @see http://www.imagemagick.org/script/command-line-options.php#threshold
655 | */
656 | public function threshold(string $threshold): self
657 | {
658 | $this->command[] = '-threshold';
659 | $this->command[] = $this->ref->threshold($threshold);
660 |
661 | return $this;
662 | }
663 |
664 | /**
665 | * /!\ Append a raw command to ImageMagick.
666 | * Not safe! Use at your own risks!
667 | *
668 | * @internal
669 | */
670 | public function rawCommand(string $command, bool $append = false): self
671 | {
672 | $msg = <<<'MSG'
673 | This command is not safe and therefore should not be used, unless you need to use an option that is not supported yet.
674 | Use at your own risk!
675 | If you are certain of what you are doing, you can silence this error using the "@" sign on the instruction that executes this method.
676 | If the option you need is not supported, please open an issue or a pull-request at https://github.com/Orbitale/ImageMagickPHP in order for us to implement the option you need! 😃
677 | MSG
678 | ;
679 | @\trigger_error($msg, \E_USER_WARNING);
680 |
681 | if ($append) {
682 | $this->commandToAppend[] = $command;
683 | } else {
684 | $this->command[] = $command;
685 | }
686 |
687 | return $this;
688 | }
689 |
690 | /* ------------------------------------------ *
691 | * End of ImageMagick native options. *
692 | * Want more? Some options are missing? *
693 | * PRs are welcomed ;) *
694 | * https://github.com/Orbitale/ImageMagickPHP *
695 | * ------------------------------------------ */
696 |
697 | /* ------------------------------------------------------ *
698 | * Here are some nice aliases you might be interested in. *
699 | * They use some combinations of ImageMagick options to *
700 | * ease certain common habits. *
701 | * Feel free to contribute if you have cool aliases! *
702 | * ------------------------------------------------------ */
703 |
704 | /**
705 | * @see http://imagemagick.org/script/command-line-options.php#annotate
706 | */
707 | public function text(array $options = []): self
708 | {
709 | foreach (['text', 'geometry', 'textSize'] as $key) {
710 | if (!isset($options[$key])) {
711 | throw new \InvalidArgumentException(\sprintf('Key "%s" is missing for the %s function.', $key, __METHOD__));
712 | }
713 | }
714 |
715 | $text = $options['text'];
716 | $textSize = $options['textSize'];
717 | $geometry = $options['geometry'];
718 | $font = $options['font'] ?? null;
719 | $checkFont = $options['checkFont'] ?? false;
720 | $textColor = $options['textColor'] ?? null;
721 | $strokeWidth = $options['strokeWidth'] ?? null;
722 | $strokeColor = $options['strokeColor'] ?? 'none';
723 |
724 | if ($font) {
725 | $this->font($font, $checkFont);
726 | }
727 |
728 | $this->pointsize($textSize);
729 |
730 | if ($textColor) {
731 | $this->fill($textColor);
732 | }
733 |
734 | if (null !== $strokeWidth) {
735 | $this->strokeWidth($strokeWidth);
736 | }
737 | $this->stroke($strokeColor);
738 |
739 | $this->command[] = '-annotate';
740 | $this->command[] = '"'.$this->ref->geometry($geometry).'"';
741 | $this->command[] = \escapeshellarg($text);
742 |
743 | return $this;
744 | }
745 |
746 | /**
747 | * @see http://imagemagick.org/script/command-line-options.php#draw
748 | */
749 | public function ellipse(
750 | int $xCenter,
751 | int $yCenter,
752 | int $width,
753 | int $height,
754 | string $fillColor,
755 | string $strokeColor = '',
756 | int $startAngleInDegree = 0,
757 | int $endAngleInDegree = 360
758 | ): self {
759 | if ($strokeColor) {
760 | $this->stroke($strokeColor);
761 | }
762 |
763 | $this->fill($fillColor);
764 |
765 | $this->command[] = '-draw';
766 | $this->command[] = '"ellipse '.$xCenter.','.$yCenter.$width.','.$height.$startAngleInDegree.','.$endAngleInDegree.'"';
767 |
768 | return $this;
769 | }
770 |
771 | /**
772 | * @see http://imagemagick.org/script/command-line-options.php#draw
773 | */
774 | public function polyline(array $coordinates, string $strokeColor = ''): self
775 | {
776 | if ($strokeColor) {
777 | $this->stroke($strokeColor);
778 | }
779 |
780 | $this->fill('transparent');
781 |
782 | $this->command[] = '-draw';
783 | $this->command[] = '"polyline '.\implode(' ', $coordinates).'"';
784 |
785 | return $this;
786 | }
787 |
788 | protected function checkExistingFile(string $file): string
789 | {
790 | if (!\file_exists($file)) {
791 | throw new \InvalidArgumentException(\sprintf('The file "%s" is not found.'."\n".'If the file really exists in your filesystem, then maybe it is not readable.', $file));
792 | }
793 |
794 | return self::cleanPath($file);
795 | }
796 | }
797 |
--------------------------------------------------------------------------------
/Tests/References/GeometryTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Orbitale\Component\ImageMagick\Tests\References;
15 |
16 | use Orbitale\Component\ImageMagick\Command;
17 | use Orbitale\Component\ImageMagick\ReferenceClasses\Geometry;
18 | use Orbitale\Component\ImageMagick\Tests\AbstractTestCase;
19 |
20 | class GeometryTest extends AbstractTestCase
21 | {
22 | /**
23 | * @dataProvider provideValidGeometries
24 | */
25 | public function testGeometry(?int $width, ?int $height, ?int $x, ?int $y, ?string $aspectRatio): void
26 | {
27 | $geometry = new Geometry($width, $height, $x, $y, $aspectRatio);
28 |
29 | try {
30 | $validatedGeometry = $geometry->validate();
31 | } catch (\Exception $e) {
32 | static::fail($e->getMessage());
33 |
34 | return;
35 | }
36 |
37 | static::assertIsString($validatedGeometry);
38 | static::assertNotEmpty($validatedGeometry);
39 |
40 | if ('' === $validatedGeometry) {
41 | static::markTestSkipped('No geometry to check. ['.$validatedGeometry.'] ['.\implode(',', \func_get_args()).']');
42 | }
43 |
44 | $command = new Command(IMAGEMAGICK_DIR);
45 |
46 | $outputFile = $this->resourcesDir.'/outputs/moon_180_test_geometry_'.\md5($width.$height.$x.$y.$aspectRatio.'test_geo').'.jpg';
47 |
48 | if (\file_exists($outputFile)) {
49 | \unlink($outputFile);
50 | }
51 |
52 | $command
53 | ->convert($this->resourcesDir.'/moon_180.jpg')
54 | ->resize($geometry)
55 | ->file($outputFile, false)
56 | ;
57 |
58 | $response = $command->run();
59 |
60 | static::assertTrue($response->isSuccessful(), \sprintf(
61 | "Geometry fixture \"%s\" returned an ImageMagick error.\nFull command: %s\nErrors:\n%s\n%s",
62 | $geometry->validate(),
63 | $command->getCommand(),
64 | $response->getOutput(),
65 | $response->getError()
66 | ));
67 | static::assertFileExists($outputFile);
68 | }
69 |
70 | public function provideValidGeometries(): ?\Generator
71 | {
72 | // width, height, x, y, aspectRatio
73 | yield 0 => [null, null, null, 0, null];
74 | yield 1 => [null, null, null, -1, null];
75 | yield 2 => [null, null, null, 1, null];
76 | yield 3 => [null, null, 0, null, null];
77 | yield 4 => [null, null, 0, 0, null];
78 | yield 5 => [null, null, 0, -1, null];
79 | yield 6 => [null, null, 0, 1, null];
80 | yield 7 => [null, null, -1, null, null];
81 | yield 8 => [null, null, -1, 0, null];
82 | yield 9 => [null, null, -1, -1, null];
83 | yield 10 => [null, null, -1, 1, null];
84 | yield 11 => [null, null, 1, null, null];
85 | yield 12 => [null, null, 1, 0, null];
86 | yield 13 => [null, null, 1, -1, null];
87 | yield 14 => [null, null, 1, 1, null];
88 | yield 15 => [null, 100, null, null, null];
89 | yield 16 => [null, 100, null, null, '<'];
90 | yield 17 => [null, 100, null, null, '!'];
91 | yield 18 => [null, 100, null, null, '^'];
92 | yield 19 => [null, 100, null, null, '>'];
93 | yield 20 => [null, 100, null, 0, null];
94 | yield 21 => [null, 100, null, 0, '<'];
95 | yield 22 => [null, 100, null, 0, '!'];
96 | yield 23 => [null, 100, null, 0, '^'];
97 | yield 24 => [null, 100, null, 0, '>'];
98 | yield 25 => [null, 100, null, -1, null];
99 | yield 26 => [null, 100, null, -1, '<'];
100 | yield 27 => [null, 100, null, -1, '!'];
101 | yield 28 => [null, 100, null, -1, '^'];
102 | yield 29 => [null, 100, null, -1, '>'];
103 | yield 30 => [null, 100, null, 1, null];
104 | yield 31 => [null, 100, null, 1, '<'];
105 | yield 32 => [null, 100, null, 1, '!'];
106 | yield 33 => [null, 100, null, 1, '^'];
107 | yield 34 => [null, 100, null, 1, '>'];
108 | yield 35 => [null, 100, 0, null, null];
109 | yield 36 => [null, 100, 0, null, '<'];
110 | yield 37 => [null, 100, 0, null, '!'];
111 | yield 38 => [null, 100, 0, null, '^'];
112 | yield 39 => [null, 100, 0, null, '>'];
113 | yield 40 => [null, 100, 0, 0, null];
114 | yield 41 => [null, 100, 0, 0, '<'];
115 | yield 42 => [null, 100, 0, 0, '!'];
116 | yield 43 => [null, 100, 0, 0, '^'];
117 | yield 44 => [null, 100, 0, 0, '>'];
118 | yield 45 => [null, 100, 0, -1, null];
119 | yield 46 => [null, 100, 0, -1, '<'];
120 | yield 47 => [null, 100, 0, -1, '!'];
121 | yield 48 => [null, 100, 0, -1, '^'];
122 | yield 49 => [null, 100, 0, -1, '>'];
123 | yield 50 => [null, 100, 0, 1, null];
124 | yield 51 => [null, 100, 0, 1, '<'];
125 | yield 52 => [null, 100, 0, 1, '!'];
126 | yield 53 => [null, 100, 0, 1, '^'];
127 | yield 54 => [null, 100, 0, 1, '>'];
128 | yield 55 => [null, 100, -1, null, null];
129 | yield 56 => [null, 100, -1, null, '<'];
130 | yield 57 => [null, 100, -1, null, '!'];
131 | yield 58 => [null, 100, -1, null, '^'];
132 | yield 59 => [null, 100, -1, null, '>'];
133 | yield 60 => [null, 100, -1, 0, null];
134 | yield 61 => [null, 100, -1, 0, '<'];
135 | yield 62 => [null, 100, -1, 0, '!'];
136 | yield 63 => [null, 100, -1, 0, '^'];
137 | yield 64 => [null, 100, -1, 0, '>'];
138 | yield 65 => [null, 100, -1, -1, null];
139 | yield 66 => [null, 100, -1, -1, '<'];
140 | yield 67 => [null, 100, -1, -1, '!'];
141 | yield 68 => [null, 100, -1, -1, '^'];
142 | yield 69 => [null, 100, -1, -1, '>'];
143 | yield 70 => [null, 100, -1, 1, null];
144 | yield 71 => [null, 100, -1, 1, '<'];
145 | yield 72 => [null, 100, -1, 1, '!'];
146 | yield 73 => [null, 100, -1, 1, '^'];
147 | yield 74 => [null, 100, -1, 1, '>'];
148 | yield 75 => [null, 100, 1, null, null];
149 | yield 76 => [null, 100, 1, null, '<'];
150 | yield 77 => [null, 100, 1, null, '!'];
151 | yield 78 => [null, 100, 1, null, '^'];
152 | yield 79 => [null, 100, 1, null, '>'];
153 | yield 80 => [null, 100, 1, 0, null];
154 | yield 81 => [null, 100, 1, 0, '<'];
155 | yield 82 => [null, 100, 1, 0, '!'];
156 | yield 83 => [null, 100, 1, 0, '^'];
157 | yield 84 => [null, 100, 1, 0, '>'];
158 | yield 85 => [null, 100, 1, -1, null];
159 | yield 86 => [null, 100, 1, -1, '<'];
160 | yield 87 => [null, 100, 1, -1, '!'];
161 | yield 88 => [null, 100, 1, -1, '^'];
162 | yield 89 => [null, 100, 1, -1, '>'];
163 | yield 90 => [null, 100, 1, 1, null];
164 | yield 91 => [null, 100, 1, 1, '<'];
165 | yield 92 => [null, 100, 1, 1, '!'];
166 | yield 93 => [null, 100, 1, 1, '^'];
167 | yield 94 => [null, 100, 1, 1, '>'];
168 | yield 95 => [100, null, null, null, null];
169 | yield 96 => [100, null, null, null, '<'];
170 | yield 97 => [100, null, null, null, '!'];
171 | yield 98 => [100, null, null, null, '^'];
172 | yield 99 => [100, null, null, null, '>'];
173 | yield 100 => [100, null, null, 0, null];
174 | yield 101 => [100, null, null, 0, '<'];
175 | yield 102 => [100, null, null, 0, '!'];
176 | yield 103 => [100, null, null, 0, '^'];
177 | yield 104 => [100, null, null, 0, '>'];
178 | yield 105 => [100, null, null, -1, null];
179 | yield 106 => [100, null, null, -1, '<'];
180 | yield 107 => [100, null, null, -1, '!'];
181 | yield 108 => [100, null, null, -1, '^'];
182 | yield 109 => [100, null, null, -1, '>'];
183 | yield 110 => [100, null, null, 1, null];
184 | yield 111 => [100, null, null, 1, '<'];
185 | yield 112 => [100, null, null, 1, '!'];
186 | yield 113 => [100, null, null, 1, '^'];
187 | yield 114 => [100, null, null, 1, '>'];
188 | yield 115 => [100, null, 0, null, null];
189 | yield 116 => [100, null, 0, null, '<'];
190 | yield 117 => [100, null, 0, null, '!'];
191 | yield 118 => [100, null, 0, null, '^'];
192 | yield 119 => [100, null, 0, null, '>'];
193 | yield 120 => [100, null, 0, 0, null];
194 | yield 121 => [100, null, 0, 0, '<'];
195 | yield 122 => [100, null, 0, 0, '!'];
196 | yield 123 => [100, null, 0, 0, '^'];
197 | yield 124 => [100, null, 0, 0, '>'];
198 | yield 125 => [100, null, 0, -1, null];
199 | yield 126 => [100, null, 0, -1, '<'];
200 | yield 127 => [100, null, 0, -1, '!'];
201 | yield 128 => [100, null, 0, -1, '^'];
202 | yield 129 => [100, null, 0, -1, '>'];
203 | yield 130 => [100, null, 0, 1, null];
204 | yield 131 => [100, null, 0, 1, '<'];
205 | yield 132 => [100, null, 0, 1, '!'];
206 | yield 133 => [100, null, 0, 1, '^'];
207 | yield 134 => [100, null, 0, 1, '>'];
208 | yield 135 => [100, null, -1, null, null];
209 | yield 136 => [100, null, -1, null, '<'];
210 | yield 137 => [100, null, -1, null, '!'];
211 | yield 138 => [100, null, -1, null, '^'];
212 | yield 139 => [100, null, -1, null, '>'];
213 | yield 140 => [100, null, -1, 0, null];
214 | yield 141 => [100, null, -1, 0, '<'];
215 | yield 142 => [100, null, -1, 0, '!'];
216 | yield 143 => [100, null, -1, 0, '^'];
217 | yield 144 => [100, null, -1, 0, '>'];
218 | yield 145 => [100, null, -1, -1, null];
219 | yield 146 => [100, null, -1, -1, '<'];
220 | yield 147 => [100, null, -1, -1, '!'];
221 | yield 148 => [100, null, -1, -1, '^'];
222 | yield 149 => [100, null, -1, -1, '>'];
223 | yield 150 => [100, null, -1, 1, null];
224 | yield 151 => [100, null, -1, 1, '<'];
225 | yield 152 => [100, null, -1, 1, '!'];
226 | yield 153 => [100, null, -1, 1, '^'];
227 | yield 154 => [100, null, -1, 1, '>'];
228 | yield 155 => [100, null, 1, null, null];
229 | yield 156 => [100, null, 1, null, '<'];
230 | yield 157 => [100, null, 1, null, '!'];
231 | yield 158 => [100, null, 1, null, '^'];
232 | yield 159 => [100, null, 1, null, '>'];
233 | yield 160 => [100, null, 1, 0, null];
234 | yield 161 => [100, null, 1, 0, '<'];
235 | yield 162 => [100, null, 1, 0, '!'];
236 | yield 163 => [100, null, 1, 0, '^'];
237 | yield 164 => [100, null, 1, 0, '>'];
238 | yield 165 => [100, null, 1, -1, null];
239 | yield 166 => [100, null, 1, -1, '<'];
240 | yield 167 => [100, null, 1, -1, '!'];
241 | yield 168 => [100, null, 1, -1, '^'];
242 | yield 169 => [100, null, 1, -1, '>'];
243 | yield 170 => [100, null, 1, 1, null];
244 | yield 171 => [100, null, 1, 1, '<'];
245 | yield 172 => [100, null, 1, 1, '!'];
246 | yield 173 => [100, null, 1, 1, '^'];
247 | yield 174 => [100, null, 1, 1, '>'];
248 | yield 175 => [100, 100, null, null, null];
249 | yield 176 => [100, 100, null, null, '<'];
250 | yield 177 => [100, 100, null, null, '!'];
251 | yield 178 => [100, 100, null, null, '^'];
252 | yield 179 => [100, 100, null, null, '>'];
253 | yield 180 => [100, 100, null, 0, null];
254 | yield 181 => [100, 100, null, 0, '<'];
255 | yield 182 => [100, 100, null, 0, '!'];
256 | yield 183 => [100, 100, null, 0, '^'];
257 | yield 184 => [100, 100, null, 0, '>'];
258 | yield 185 => [100, 100, null, -1, null];
259 | yield 186 => [100, 100, null, -1, '<'];
260 | yield 187 => [100, 100, null, -1, '!'];
261 | yield 188 => [100, 100, null, -1, '^'];
262 | yield 189 => [100, 100, null, -1, '>'];
263 | yield 190 => [100, 100, null, 1, null];
264 | yield 191 => [100, 100, null, 1, '<'];
265 | yield 192 => [100, 100, null, 1, '!'];
266 | yield 193 => [100, 100, null, 1, '^'];
267 | yield 194 => [100, 100, null, 1, '>'];
268 | yield 195 => [100, 100, 0, null, null];
269 | yield 196 => [100, 100, 0, null, '<'];
270 | yield 197 => [100, 100, 0, null, '!'];
271 | yield 198 => [100, 100, 0, null, '^'];
272 | yield 199 => [100, 100, 0, null, '>'];
273 | yield 200 => [100, 100, 0, 0, null];
274 | yield 201 => [100, 100, 0, 0, '<'];
275 | yield 202 => [100, 100, 0, 0, '!'];
276 | yield 203 => [100, 100, 0, 0, '^'];
277 | yield 204 => [100, 100, 0, 0, '>'];
278 | yield 205 => [100, 100, 0, -1, null];
279 | yield 206 => [100, 100, 0, -1, '<'];
280 | yield 207 => [100, 100, 0, -1, '!'];
281 | yield 208 => [100, 100, 0, -1, '^'];
282 | yield 209 => [100, 100, 0, -1, '>'];
283 | yield 210 => [100, 100, 0, 1, null];
284 | yield 211 => [100, 100, 0, 1, '<'];
285 | yield 212 => [100, 100, 0, 1, '!'];
286 | yield 213 => [100, 100, 0, 1, '^'];
287 | yield 214 => [100, 100, 0, 1, '>'];
288 | yield 215 => [100, 100, -1, null, null];
289 | yield 216 => [100, 100, -1, null, '<'];
290 | yield 217 => [100, 100, -1, null, '!'];
291 | yield 218 => [100, 100, -1, null, '^'];
292 | yield 219 => [100, 100, -1, null, '>'];
293 | yield 220 => [100, 100, -1, 0, null];
294 | yield 221 => [100, 100, -1, 0, '<'];
295 | yield 222 => [100, 100, -1, 0, '!'];
296 | yield 223 => [100, 100, -1, 0, '^'];
297 | yield 224 => [100, 100, -1, 0, '>'];
298 | yield 225 => [100, 100, -1, -1, null];
299 | yield 226 => [100, 100, -1, -1, '<'];
300 | yield 227 => [100, 100, -1, -1, '!'];
301 | yield 228 => [100, 100, -1, -1, '^'];
302 | yield 229 => [100, 100, -1, -1, '>'];
303 | yield 230 => [100, 100, -1, 1, null];
304 | yield 231 => [100, 100, -1, 1, '<'];
305 | yield 232 => [100, 100, -1, 1, '!'];
306 | yield 233 => [100, 100, -1, 1, '^'];
307 | yield 234 => [100, 100, -1, 1, '>'];
308 | yield 235 => [100, 100, 1, null, null];
309 | yield 236 => [100, 100, 1, null, '<'];
310 | yield 237 => [100, 100, 1, null, '!'];
311 | yield 238 => [100, 100, 1, null, '^'];
312 | yield 239 => [100, 100, 1, null, '>'];
313 | yield 240 => [100, 100, 1, 0, null];
314 | yield 241 => [100, 100, 1, 0, '<'];
315 | yield 242 => [100, 100, 1, 0, '!'];
316 | yield 243 => [100, 100, 1, 0, '^'];
317 | yield 244 => [100, 100, 1, 0, '>'];
318 | yield 245 => [100, 100, 1, -1, null];
319 | yield 246 => [100, 100, 1, -1, '<'];
320 | yield 247 => [100, 100, 1, -1, '!'];
321 | yield 248 => [100, 100, 1, -1, '^'];
322 | yield 249 => [100, 100, 1, -1, '>'];
323 | yield 250 => [100, 100, 1, 1, null];
324 | yield 251 => [100, 100, 1, 1, '<'];
325 | yield 252 => [100, 100, 1, 1, '!'];
326 | yield 253 => [100, 100, 1, 1, '^'];
327 | yield 254 => [100, 100, 1, 1, '>'];
328 | }
329 |
330 | /**
331 | * @dataProvider provideWrongGeometries
332 | */
333 | public function testWrongGeometry(?int $width, ?int $height, ?int $x, ?int $y, ?string $aspectRatio): void
334 | {
335 | $geometry = new Geometry($width, $height, $x, $y, $aspectRatio);
336 |
337 | $expectedGeometry = Geometry::createFromParameters($width, $height, $x, $y, $aspectRatio);
338 |
339 | $message = null;
340 |
341 | try {
342 | $geometry->validate();
343 | } catch (\InvalidArgumentException $e) {
344 | $message = $e->getMessage();
345 | }
346 |
347 | static::assertNotNull($message, 'No exception for geometry "'.$expectedGeometry.'"');
348 | static::assertStringStartsWith('The specified geometry ('.$expectedGeometry.') is invalid.', $message, "Wrong exception message:\n$message");
349 | }
350 |
351 | public function provideWrongGeometries(): ?\Generator
352 | {
353 | yield 0 => [null, null, null, null, '<'];
354 | yield 1 => [null, null, null, null, '!'];
355 | yield 2 => [null, null, null, null, '^'];
356 | yield 3 => [null, null, null, null, '>'];
357 | yield 4 => [null, null, null, 0, '<'];
358 | yield 5 => [null, null, null, 0, '!'];
359 | yield 6 => [null, null, null, 0, '^'];
360 | yield 7 => [null, null, null, 0, '>'];
361 | yield 8 => [null, null, null, -1, '<'];
362 | yield 9 => [null, null, null, -1, '!'];
363 | yield 10 => [null, null, null, -1, '^'];
364 | yield 11 => [null, null, null, -1, '>'];
365 | yield 12 => [null, null, null, 1, '<'];
366 | yield 13 => [null, null, null, 1, '!'];
367 | yield 14 => [null, null, null, 1, '^'];
368 | yield 15 => [null, null, null, 1, '>'];
369 | yield 16 => [null, null, 0, null, '<'];
370 | yield 17 => [null, null, 0, null, '!'];
371 | yield 18 => [null, null, 0, null, '^'];
372 | yield 19 => [null, null, 0, null, '>'];
373 | yield 20 => [null, null, 0, 0, '<'];
374 | yield 21 => [null, null, 0, 0, '!'];
375 | yield 22 => [null, null, 0, 0, '^'];
376 | yield 23 => [null, null, 0, 0, '>'];
377 | yield 24 => [null, null, 0, -1, '<'];
378 | yield 25 => [null, null, 0, -1, '!'];
379 | yield 26 => [null, null, 0, -1, '^'];
380 | yield 27 => [null, null, 0, -1, '>'];
381 | yield 28 => [null, null, 0, 1, '<'];
382 | yield 29 => [null, null, 0, 1, '!'];
383 | yield 30 => [null, null, 0, 1, '^'];
384 | yield 31 => [null, null, 0, 1, '>'];
385 | yield 32 => [null, null, -1, null, '<'];
386 | yield 33 => [null, null, -1, null, '!'];
387 | yield 34 => [null, null, -1, null, '^'];
388 | yield 35 => [null, null, -1, null, '>'];
389 | yield 36 => [null, null, -1, 0, '<'];
390 | yield 37 => [null, null, -1, 0, '!'];
391 | yield 38 => [null, null, -1, 0, '^'];
392 | yield 39 => [null, null, -1, 0, '>'];
393 | yield 40 => [null, null, -1, -1, '<'];
394 | yield 41 => [null, null, -1, -1, '!'];
395 | yield 42 => [null, null, -1, -1, '^'];
396 | yield 43 => [null, null, -1, -1, '>'];
397 | yield 44 => [null, null, -1, 1, '<'];
398 | yield 45 => [null, null, -1, 1, '!'];
399 | yield 46 => [null, null, -1, 1, '^'];
400 | yield 47 => [null, null, -1, 1, '>'];
401 | yield 48 => [null, null, 1, null, '<'];
402 | yield 49 => [null, null, 1, null, '!'];
403 | yield 50 => [null, null, 1, null, '^'];
404 | yield 51 => [null, null, 1, null, '>'];
405 | yield 52 => [null, null, 1, 0, '<'];
406 | yield 53 => [null, null, 1, 0, '!'];
407 | yield 54 => [null, null, 1, 0, '^'];
408 | yield 55 => [null, null, 1, 0, '>'];
409 | yield 56 => [null, null, 1, -1, '<'];
410 | yield 57 => [null, null, 1, -1, '!'];
411 | yield 58 => [null, null, 1, -1, '^'];
412 | yield 59 => [null, null, 1, -1, '>'];
413 | yield 60 => [null, null, 1, 1, '<'];
414 | yield 61 => [null, null, 1, 1, '!'];
415 | yield 62 => [null, null, 1, 1, '^'];
416 | yield 63 => [null, null, 1, 1, '>'];
417 | yield 64 => [null, 0, null, null, null];
418 | yield 65 => [null, 0, null, null, '<'];
419 | yield 66 => [null, 0, null, null, '!'];
420 | yield 67 => [null, 0, null, null, '^'];
421 | yield 68 => [null, 0, null, null, '>'];
422 | yield 69 => [null, 0, null, 0, null];
423 | yield 70 => [null, 0, null, 0, '<'];
424 | yield 71 => [null, 0, null, 0, '!'];
425 | yield 72 => [null, 0, null, 0, '^'];
426 | yield 73 => [null, 0, null, 0, '>'];
427 | yield 74 => [null, 0, null, -1, null];
428 | yield 75 => [null, 0, null, -1, '<'];
429 | yield 76 => [null, 0, null, -1, '!'];
430 | yield 77 => [null, 0, null, -1, '^'];
431 | yield 78 => [null, 0, null, -1, '>'];
432 | yield 79 => [null, 0, null, 1, null];
433 | yield 80 => [null, 0, null, 1, '<'];
434 | yield 81 => [null, 0, null, 1, '!'];
435 | yield 82 => [null, 0, null, 1, '^'];
436 | yield 83 => [null, 0, null, 1, '>'];
437 | yield 84 => [null, 0, 0, null, null];
438 | yield 85 => [null, 0, 0, null, '<'];
439 | yield 86 => [null, 0, 0, null, '!'];
440 | yield 87 => [null, 0, 0, null, '^'];
441 | yield 88 => [null, 0, 0, null, '>'];
442 | yield 89 => [null, 0, 0, 0, null];
443 | yield 90 => [null, 0, 0, 0, '<'];
444 | yield 91 => [null, 0, 0, 0, '!'];
445 | yield 92 => [null, 0, 0, 0, '^'];
446 | yield 93 => [null, 0, 0, 0, '>'];
447 | yield 94 => [null, 0, 0, -1, null];
448 | yield 95 => [null, 0, 0, -1, '<'];
449 | yield 96 => [null, 0, 0, -1, '!'];
450 | yield 97 => [null, 0, 0, -1, '^'];
451 | yield 98 => [null, 0, 0, -1, '>'];
452 | yield 99 => [null, 0, 0, 1, null];
453 | yield 100 => [null, 0, 0, 1, '<'];
454 | yield 101 => [null, 0, 0, 1, '!'];
455 | yield 102 => [null, 0, 0, 1, '^'];
456 | yield 103 => [null, 0, 0, 1, '>'];
457 | yield 104 => [null, 0, -1, null, null];
458 | yield 105 => [null, 0, -1, null, '<'];
459 | yield 106 => [null, 0, -1, null, '!'];
460 | yield 107 => [null, 0, -1, null, '^'];
461 | yield 108 => [null, 0, -1, null, '>'];
462 | yield 109 => [null, 0, -1, 0, null];
463 | yield 110 => [null, 0, -1, 0, '<'];
464 | yield 111 => [null, 0, -1, 0, '!'];
465 | yield 112 => [null, 0, -1, 0, '^'];
466 | yield 113 => [null, 0, -1, 0, '>'];
467 | yield 114 => [null, 0, -1, -1, null];
468 | yield 115 => [null, 0, -1, -1, '<'];
469 | yield 116 => [null, 0, -1, -1, '!'];
470 | yield 117 => [null, 0, -1, -1, '^'];
471 | yield 118 => [null, 0, -1, -1, '>'];
472 | yield 119 => [null, 0, -1, 1, null];
473 | yield 120 => [null, 0, -1, 1, '<'];
474 | yield 121 => [null, 0, -1, 1, '!'];
475 | yield 122 => [null, 0, -1, 1, '^'];
476 | yield 123 => [null, 0, -1, 1, '>'];
477 | yield 124 => [null, 0, 1, null, null];
478 | yield 125 => [null, 0, 1, null, '<'];
479 | yield 126 => [null, 0, 1, null, '!'];
480 | yield 127 => [null, 0, 1, null, '^'];
481 | yield 128 => [null, 0, 1, null, '>'];
482 | yield 129 => [null, 0, 1, 0, null];
483 | yield 130 => [null, 0, 1, 0, '<'];
484 | yield 131 => [null, 0, 1, 0, '!'];
485 | yield 132 => [null, 0, 1, 0, '^'];
486 | yield 133 => [null, 0, 1, 0, '>'];
487 | yield 134 => [null, 0, 1, -1, null];
488 | yield 135 => [null, 0, 1, -1, '<'];
489 | yield 136 => [null, 0, 1, -1, '!'];
490 | yield 137 => [null, 0, 1, -1, '^'];
491 | yield 138 => [null, 0, 1, -1, '>'];
492 | yield 139 => [null, 0, 1, 1, null];
493 | yield 140 => [null, 0, 1, 1, '<'];
494 | yield 141 => [null, 0, 1, 1, '!'];
495 | yield 142 => [null, 0, 1, 1, '^'];
496 | yield 143 => [null, 0, 1, 1, '>'];
497 | yield 144 => [0, null, null, null, null];
498 | yield 145 => [0, null, null, null, '<'];
499 | yield 146 => [0, null, null, null, '!'];
500 | yield 147 => [0, null, null, null, '^'];
501 | yield 148 => [0, null, null, null, '>'];
502 | yield 149 => [0, null, null, 0, null];
503 | yield 150 => [0, null, null, 0, '<'];
504 | yield 151 => [0, null, null, 0, '!'];
505 | yield 152 => [0, null, null, 0, '^'];
506 | yield 153 => [0, null, null, 0, '>'];
507 | yield 154 => [0, null, null, -1, null];
508 | yield 155 => [0, null, null, -1, '<'];
509 | yield 156 => [0, null, null, -1, '!'];
510 | yield 157 => [0, null, null, -1, '^'];
511 | yield 158 => [0, null, null, -1, '>'];
512 | yield 159 => [0, null, null, 1, null];
513 | yield 160 => [0, null, null, 1, '<'];
514 | yield 161 => [0, null, null, 1, '!'];
515 | yield 162 => [0, null, null, 1, '^'];
516 | yield 163 => [0, null, null, 1, '>'];
517 | yield 164 => [0, null, 0, null, null];
518 | yield 165 => [0, null, 0, null, '<'];
519 | yield 166 => [0, null, 0, null, '!'];
520 | yield 167 => [0, null, 0, null, '^'];
521 | yield 168 => [0, null, 0, null, '>'];
522 | yield 169 => [0, null, 0, 0, null];
523 | yield 170 => [0, null, 0, 0, '<'];
524 | yield 171 => [0, null, 0, 0, '!'];
525 | yield 172 => [0, null, 0, 0, '^'];
526 | yield 173 => [0, null, 0, 0, '>'];
527 | yield 174 => [0, null, 0, -1, null];
528 | yield 175 => [0, null, 0, -1, '<'];
529 | yield 176 => [0, null, 0, -1, '!'];
530 | yield 177 => [0, null, 0, -1, '^'];
531 | yield 178 => [0, null, 0, -1, '>'];
532 | yield 179 => [0, null, 0, 1, null];
533 | yield 180 => [0, null, 0, 1, '<'];
534 | yield 181 => [0, null, 0, 1, '!'];
535 | yield 182 => [0, null, 0, 1, '^'];
536 | yield 183 => [0, null, 0, 1, '>'];
537 | yield 184 => [0, null, -1, null, null];
538 | yield 185 => [0, null, -1, null, '<'];
539 | yield 186 => [0, null, -1, null, '!'];
540 | yield 187 => [0, null, -1, null, '^'];
541 | yield 188 => [0, null, -1, null, '>'];
542 | yield 189 => [0, null, -1, 0, null];
543 | yield 190 => [0, null, -1, 0, '<'];
544 | yield 191 => [0, null, -1, 0, '!'];
545 | yield 192 => [0, null, -1, 0, '^'];
546 | yield 193 => [0, null, -1, 0, '>'];
547 | yield 194 => [0, null, -1, -1, null];
548 | yield 195 => [0, null, -1, -1, '<'];
549 | yield 196 => [0, null, -1, -1, '!'];
550 | yield 197 => [0, null, -1, -1, '^'];
551 | yield 198 => [0, null, -1, -1, '>'];
552 | yield 199 => [0, null, -1, 1, null];
553 | yield 200 => [0, null, -1, 1, '<'];
554 | yield 201 => [0, null, -1, 1, '!'];
555 | yield 202 => [0, null, -1, 1, '^'];
556 | yield 203 => [0, null, -1, 1, '>'];
557 | yield 204 => [0, null, 1, null, null];
558 | yield 205 => [0, null, 1, null, '<'];
559 | yield 206 => [0, null, 1, null, '!'];
560 | yield 207 => [0, null, 1, null, '^'];
561 | yield 208 => [0, null, 1, null, '>'];
562 | yield 209 => [0, null, 1, 0, null];
563 | yield 210 => [0, null, 1, 0, '<'];
564 | yield 211 => [0, null, 1, 0, '!'];
565 | yield 212 => [0, null, 1, 0, '^'];
566 | yield 213 => [0, null, 1, 0, '>'];
567 | yield 214 => [0, null, 1, -1, null];
568 | yield 215 => [0, null, 1, -1, '<'];
569 | yield 216 => [0, null, 1, -1, '!'];
570 | yield 217 => [0, null, 1, -1, '^'];
571 | yield 218 => [0, null, 1, -1, '>'];
572 | yield 219 => [0, null, 1, 1, null];
573 | yield 220 => [0, null, 1, 1, '<'];
574 | yield 221 => [0, null, 1, 1, '!'];
575 | yield 222 => [0, null, 1, 1, '^'];
576 | yield 223 => [0, null, 1, 1, '>'];
577 | yield 224 => [0, 0, null, null, null];
578 | yield 225 => [0, 0, null, null, '<'];
579 | yield 226 => [0, 0, null, null, '!'];
580 | yield 227 => [0, 0, null, null, '^'];
581 | yield 228 => [0, 0, null, null, '>'];
582 | yield 229 => [0, 0, null, 0, null];
583 | yield 230 => [0, 0, null, 0, '<'];
584 | yield 231 => [0, 0, null, 0, '!'];
585 | yield 232 => [0, 0, null, 0, '^'];
586 | yield 233 => [0, 0, null, 0, '>'];
587 | yield 234 => [0, 0, null, -1, null];
588 | yield 235 => [0, 0, null, -1, '<'];
589 | yield 236 => [0, 0, null, -1, '!'];
590 | yield 237 => [0, 0, null, -1, '^'];
591 | yield 238 => [0, 0, null, -1, '>'];
592 | yield 239 => [0, 0, null, 1, null];
593 | yield 240 => [0, 0, null, 1, '<'];
594 | yield 241 => [0, 0, null, 1, '!'];
595 | yield 242 => [0, 0, null, 1, '^'];
596 | yield 243 => [0, 0, null, 1, '>'];
597 | yield 244 => [0, 0, 0, null, null];
598 | yield 245 => [0, 0, 0, null, '<'];
599 | yield 246 => [0, 0, 0, null, '!'];
600 | yield 247 => [0, 0, 0, null, '^'];
601 | yield 248 => [0, 0, 0, null, '>'];
602 | yield 249 => [0, 0, 0, 0, null];
603 | yield 250 => [0, 0, 0, 0, '<'];
604 | yield 251 => [0, 0, 0, 0, '!'];
605 | yield 252 => [0, 0, 0, 0, '^'];
606 | yield 253 => [0, 0, 0, 0, '>'];
607 | yield 254 => [0, 0, 0, -1, null];
608 | yield 255 => [0, 0, 0, -1, '<'];
609 | yield 256 => [0, 0, 0, -1, '!'];
610 | yield 257 => [0, 0, 0, -1, '^'];
611 | yield 258 => [0, 0, 0, -1, '>'];
612 | yield 259 => [0, 0, 0, 1, null];
613 | yield 260 => [0, 0, 0, 1, '<'];
614 | yield 261 => [0, 0, 0, 1, '!'];
615 | yield 262 => [0, 0, 0, 1, '^'];
616 | yield 263 => [0, 0, 0, 1, '>'];
617 | yield 264 => [0, 0, -1, null, null];
618 | yield 265 => [0, 0, -1, null, '<'];
619 | yield 266 => [0, 0, -1, null, '!'];
620 | yield 267 => [0, 0, -1, null, '^'];
621 | yield 268 => [0, 0, -1, null, '>'];
622 | yield 269 => [0, 0, -1, 0, null];
623 | yield 270 => [0, 0, -1, 0, '<'];
624 | yield 271 => [0, 0, -1, 0, '!'];
625 | yield 272 => [0, 0, -1, 0, '^'];
626 | yield 273 => [0, 0, -1, 0, '>'];
627 | yield 274 => [0, 0, -1, -1, null];
628 | yield 275 => [0, 0, -1, -1, '<'];
629 | yield 276 => [0, 0, -1, -1, '!'];
630 | yield 277 => [0, 0, -1, -1, '^'];
631 | yield 278 => [0, 0, -1, -1, '>'];
632 | yield 279 => [0, 0, -1, 1, null];
633 | yield 280 => [0, 0, -1, 1, '<'];
634 | yield 281 => [0, 0, -1, 1, '!'];
635 | yield 282 => [0, 0, -1, 1, '^'];
636 | yield 283 => [0, 0, -1, 1, '>'];
637 | yield 284 => [0, 0, 1, null, null];
638 | yield 285 => [0, 0, 1, null, '<'];
639 | yield 286 => [0, 0, 1, null, '!'];
640 | yield 287 => [0, 0, 1, null, '^'];
641 | yield 288 => [0, 0, 1, null, '>'];
642 | yield 289 => [0, 0, 1, 0, null];
643 | yield 290 => [0, 0, 1, 0, '<'];
644 | yield 291 => [0, 0, 1, 0, '!'];
645 | yield 292 => [0, 0, 1, 0, '^'];
646 | yield 293 => [0, 0, 1, 0, '>'];
647 | yield 294 => [0, 0, 1, -1, null];
648 | yield 295 => [0, 0, 1, -1, '<'];
649 | yield 296 => [0, 0, 1, -1, '!'];
650 | yield 297 => [0, 0, 1, -1, '^'];
651 | yield 298 => [0, 0, 1, -1, '>'];
652 | yield 299 => [0, 0, 1, 1, null];
653 | yield 300 => [0, 0, 1, 1, '<'];
654 | yield 301 => [0, 0, 1, 1, '!'];
655 | yield 302 => [0, 0, 1, 1, '^'];
656 | yield 303 => [0, 0, 1, 1, '>'];
657 | yield 304 => [0, 100, null, null, null];
658 | yield 305 => [0, 100, null, null, '<'];
659 | yield 306 => [0, 100, null, null, '!'];
660 | yield 307 => [0, 100, null, null, '^'];
661 | yield 308 => [0, 100, null, null, '>'];
662 | yield 309 => [0, 100, null, 0, null];
663 | yield 310 => [0, 100, null, 0, '<'];
664 | yield 311 => [0, 100, null, 0, '!'];
665 | yield 312 => [0, 100, null, 0, '^'];
666 | yield 313 => [0, 100, null, 0, '>'];
667 | yield 314 => [0, 100, null, -1, null];
668 | yield 315 => [0, 100, null, -1, '<'];
669 | yield 316 => [0, 100, null, -1, '!'];
670 | yield 317 => [0, 100, null, -1, '^'];
671 | yield 318 => [0, 100, null, -1, '>'];
672 | yield 319 => [0, 100, null, 1, null];
673 | yield 320 => [0, 100, null, 1, '<'];
674 | yield 321 => [0, 100, null, 1, '!'];
675 | yield 322 => [0, 100, null, 1, '^'];
676 | yield 323 => [0, 100, null, 1, '>'];
677 | yield 324 => [0, 100, 0, null, null];
678 | yield 325 => [0, 100, 0, null, '<'];
679 | yield 326 => [0, 100, 0, null, '!'];
680 | yield 327 => [0, 100, 0, null, '^'];
681 | yield 328 => [0, 100, 0, null, '>'];
682 | yield 329 => [0, 100, 0, 0, null];
683 | yield 330 => [0, 100, 0, 0, '<'];
684 | yield 331 => [0, 100, 0, 0, '!'];
685 | yield 332 => [0, 100, 0, 0, '^'];
686 | yield 333 => [0, 100, 0, 0, '>'];
687 | yield 334 => [0, 100, 0, -1, null];
688 | yield 335 => [0, 100, 0, -1, '<'];
689 | yield 336 => [0, 100, 0, -1, '!'];
690 | yield 337 => [0, 100, 0, -1, '^'];
691 | yield 338 => [0, 100, 0, -1, '>'];
692 | yield 339 => [0, 100, 0, 1, null];
693 | yield 340 => [0, 100, 0, 1, '<'];
694 | yield 341 => [0, 100, 0, 1, '!'];
695 | yield 342 => [0, 100, 0, 1, '^'];
696 | yield 343 => [0, 100, 0, 1, '>'];
697 | yield 344 => [0, 100, -1, null, null];
698 | yield 345 => [0, 100, -1, null, '<'];
699 | yield 346 => [0, 100, -1, null, '!'];
700 | yield 347 => [0, 100, -1, null, '^'];
701 | yield 348 => [0, 100, -1, null, '>'];
702 | yield 349 => [0, 100, -1, 0, null];
703 | yield 350 => [0, 100, -1, 0, '<'];
704 | yield 351 => [0, 100, -1, 0, '!'];
705 | yield 352 => [0, 100, -1, 0, '^'];
706 | yield 353 => [0, 100, -1, 0, '>'];
707 | yield 354 => [0, 100, -1, -1, null];
708 | yield 355 => [0, 100, -1, -1, '<'];
709 | yield 356 => [0, 100, -1, -1, '!'];
710 | yield 357 => [0, 100, -1, -1, '^'];
711 | yield 358 => [0, 100, -1, -1, '>'];
712 | yield 359 => [0, 100, -1, 1, null];
713 | yield 360 => [0, 100, -1, 1, '<'];
714 | yield 361 => [0, 100, -1, 1, '!'];
715 | yield 362 => [0, 100, -1, 1, '^'];
716 | yield 363 => [0, 100, -1, 1, '>'];
717 | yield 364 => [0, 100, 1, null, null];
718 | yield 365 => [0, 100, 1, null, '<'];
719 | yield 366 => [0, 100, 1, null, '!'];
720 | yield 367 => [0, 100, 1, null, '^'];
721 | yield 368 => [0, 100, 1, null, '>'];
722 | yield 369 => [0, 100, 1, 0, null];
723 | yield 370 => [0, 100, 1, 0, '<'];
724 | yield 371 => [0, 100, 1, 0, '!'];
725 | yield 372 => [0, 100, 1, 0, '^'];
726 | yield 373 => [0, 100, 1, 0, '>'];
727 | yield 374 => [0, 100, 1, -1, null];
728 | yield 375 => [0, 100, 1, -1, '<'];
729 | yield 376 => [0, 100, 1, -1, '!'];
730 | yield 377 => [0, 100, 1, -1, '^'];
731 | yield 378 => [0, 100, 1, -1, '>'];
732 | yield 379 => [0, 100, 1, 1, null];
733 | yield 380 => [0, 100, 1, 1, '<'];
734 | yield 381 => [0, 100, 1, 1, '!'];
735 | yield 382 => [0, 100, 1, 1, '^'];
736 | yield 383 => [0, 100, 1, 1, '>'];
737 | yield 384 => [100, 0, null, null, null];
738 | yield 385 => [100, 0, null, null, '<'];
739 | yield 386 => [100, 0, null, null, '!'];
740 | yield 387 => [100, 0, null, null, '^'];
741 | yield 388 => [100, 0, null, null, '>'];
742 | yield 389 => [100, 0, null, 0, null];
743 | yield 390 => [100, 0, null, 0, '<'];
744 | yield 391 => [100, 0, null, 0, '!'];
745 | yield 392 => [100, 0, null, 0, '^'];
746 | yield 393 => [100, 0, null, 0, '>'];
747 | yield 394 => [100, 0, null, -1, null];
748 | yield 395 => [100, 0, null, -1, '<'];
749 | yield 396 => [100, 0, null, -1, '!'];
750 | yield 397 => [100, 0, null, -1, '^'];
751 | yield 398 => [100, 0, null, -1, '>'];
752 | yield 399 => [100, 0, null, 1, null];
753 | yield 400 => [100, 0, null, 1, '<'];
754 | yield 401 => [100, 0, null, 1, '!'];
755 | yield 402 => [100, 0, null, 1, '^'];
756 | yield 403 => [100, 0, null, 1, '>'];
757 | yield 404 => [100, 0, 0, null, null];
758 | yield 405 => [100, 0, 0, null, '<'];
759 | yield 406 => [100, 0, 0, null, '!'];
760 | yield 407 => [100, 0, 0, null, '^'];
761 | yield 408 => [100, 0, 0, null, '>'];
762 | yield 409 => [100, 0, 0, 0, null];
763 | yield 410 => [100, 0, 0, 0, '<'];
764 | yield 411 => [100, 0, 0, 0, '!'];
765 | yield 412 => [100, 0, 0, 0, '^'];
766 | yield 413 => [100, 0, 0, 0, '>'];
767 | yield 414 => [100, 0, 0, -1, null];
768 | yield 415 => [100, 0, 0, -1, '<'];
769 | yield 416 => [100, 0, 0, -1, '!'];
770 | yield 417 => [100, 0, 0, -1, '^'];
771 | yield 418 => [100, 0, 0, -1, '>'];
772 | yield 419 => [100, 0, 0, 1, null];
773 | yield 420 => [100, 0, 0, 1, '<'];
774 | yield 421 => [100, 0, 0, 1, '!'];
775 | yield 422 => [100, 0, 0, 1, '^'];
776 | yield 423 => [100, 0, 0, 1, '>'];
777 | yield 424 => [100, 0, -1, null, null];
778 | yield 425 => [100, 0, -1, null, '<'];
779 | yield 426 => [100, 0, -1, null, '!'];
780 | yield 427 => [100, 0, -1, null, '^'];
781 | yield 428 => [100, 0, -1, null, '>'];
782 | yield 429 => [100, 0, -1, 0, null];
783 | yield 430 => [100, 0, -1, 0, '<'];
784 | yield 431 => [100, 0, -1, 0, '!'];
785 | yield 432 => [100, 0, -1, 0, '^'];
786 | yield 433 => [100, 0, -1, 0, '>'];
787 | yield 434 => [100, 0, -1, -1, null];
788 | yield 435 => [100, 0, -1, -1, '<'];
789 | yield 436 => [100, 0, -1, -1, '!'];
790 | yield 437 => [100, 0, -1, -1, '^'];
791 | yield 438 => [100, 0, -1, -1, '>'];
792 | yield 439 => [100, 0, -1, 1, null];
793 | yield 440 => [100, 0, -1, 1, '<'];
794 | yield 441 => [100, 0, -1, 1, '!'];
795 | yield 442 => [100, 0, -1, 1, '^'];
796 | yield 443 => [100, 0, -1, 1, '>'];
797 | yield 444 => [100, 0, 1, null, null];
798 | yield 445 => [100, 0, 1, null, '<'];
799 | yield 446 => [100, 0, 1, null, '!'];
800 | yield 447 => [100, 0, 1, null, '^'];
801 | yield 448 => [100, 0, 1, null, '>'];
802 | yield 449 => [100, 0, 1, 0, null];
803 | yield 450 => [100, 0, 1, 0, '<'];
804 | yield 451 => [100, 0, 1, 0, '!'];
805 | yield 452 => [100, 0, 1, 0, '^'];
806 | yield 453 => [100, 0, 1, 0, '>'];
807 | yield 454 => [100, 0, 1, -1, null];
808 | yield 455 => [100, 0, 1, -1, '<'];
809 | yield 456 => [100, 0, 1, -1, '!'];
810 | yield 457 => [100, 0, 1, -1, '^'];
811 | yield 458 => [100, 0, 1, -1, '>'];
812 | yield 459 => [100, 0, 1, 1, null];
813 | yield 460 => [100, 0, 1, 1, '<'];
814 | yield 461 => [100, 0, 1, 1, '!'];
815 | yield 462 => [100, 0, 1, 1, '^'];
816 | yield 463 => [100, 0, 1, 1, '>'];
817 | }
818 |
819 | public function testInvalidAspectRatio(): void
820 | {
821 | $this->expectException(\InvalidArgumentException::class);
822 | $this->expectExceptionMessage(\sprintf("Invalid aspect ratio value to generate geometry, \"%s\" given.\nAvailable: <, !, ^, >", 'invalid_ratio'));
823 |
824 | new Geometry(null, null, null, null, 'invalid_ratio');
825 | }
826 |
827 | public function testInvalidWidthHeightSeparator(): void
828 | {
829 | $geometryString = '120+1+1';
830 |
831 | $this->expectException(\InvalidArgumentException::class);
832 | $this->expectExceptionMessage(\sprintf(
833 | "The specified geometry (%s) is invalid.\n%s\n"."Please refer to ImageMagick command line documentation about geometry:\nhttp://www.imagemagick.org/script/command-line-processing.php#geometry\n",
834 | '120+1+1',
835 | 'When using offsets and only width, you must specify the "x" separator like this: 120x+1+1'
836 | ));
837 |
838 | $geometry = new Geometry($geometryString);
839 | $geometry->validate();
840 | }
841 | }
842 |
--------------------------------------------------------------------------------