├── 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 | --------------------------------------------------------------------------------