diff --git a/.github/workflows/phpstan.yaml b/.github/workflows/phpstan.yaml new file mode 100644 index 0000000..e362fbd --- /dev/null +++ b/.github/workflows/phpstan.yaml @@ -0,0 +1,14 @@ +name: PHPStan +on: push +jobs: + phpstan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: php-actions/composer@v6 + with: + dev: yes + - uses: php-actions/phpstan@v3 + with: + path: src + level: max diff --git a/.github/workflows/phpunit.yaml b/.github/workflows/phpunit.yaml new file mode 100644 index 0000000..a93faa6 --- /dev/null +++ b/.github/workflows/phpunit.yaml @@ -0,0 +1,13 @@ +name: PHPUnit +on: push +jobs: + phpunit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: php-actions/composer@v6 + with: + dev: yes + - uses: php-actions/phpunit@v3 + with: + version: 10 diff --git a/.gitignore b/.gitignore index b9c83f1..b81d74f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ composer.phar composer.lock debug.log /examples/out/* -!.gitkeep \ No newline at end of file +!.gitkeep +.phpunit.result.cache \ No newline at end of file diff --git a/README.md b/README.md index bae7426..e3e563f 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,11 @@ This library is under active development, and until a 1.0 release is made you sh ### Current progress -| Driver | Rotate | Mirror | Resize | Crop | -| :--------- | :----: | :----: | :----: | :--: | -| GD | X | X | X | X | -| Imagick | | | | | -| Gmagick | | | | | -| ImagickCLI | X | X | X | X | +| Driver | Rotate | Mirror | Resize | Crop | +| :-------------- | :----: | :----: | :----: | :---: | +| GDDriver | X | X | X | X | +| MagickDriver | | | | | +| MagickCliDriver | X | X | X | X | ## Roadmap @@ -22,9 +21,8 @@ This library is under active development, and until a 1.0 release is made you sh A 1.0 release will not be made until the following drivers are available and solidly tested: * GD -* Imagick -* Gmagick -* GmagickCLI +* Magick (unified driver that will use either ImageMagick or Gmagick automatically depending on what is available) +* MagickCliDriver (unified driver that can be configured to use either ImageMagick or Gmagick CLI tools) ### Transforms diff --git a/composer.json b/composer.json index 2d40691..aad378d 100644 --- a/composer.json +++ b/composer.json @@ -24,14 +24,13 @@ }, "require-dev": { "ext-gmagick": "*", - "ext-gd": "*" + "ext-gd": "*", + "phpunit/phpunit": "^10.3", + "phpstan/phpstan": "^1.10" }, "autoload-dev": { "psr-4": { "ByJoby\\ImageTransform\\tests\\": "tests/" } - }, - "scripts": { - "test": "./vendor/bin/atoum -d tests" } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..478362b --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: max + paths: + - src \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..bc2f7c1 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,31 @@ + + + + + + + + + + tests/ + + + + + src + + + + + + \ No newline at end of file diff --git a/src/DefaultDriver.php b/src/DefaultDriver.php index d7c2d06..7718952 100644 --- a/src/DefaultDriver.php +++ b/src/DefaultDriver.php @@ -10,7 +10,8 @@ use ByJoby\ImageTransform\Drivers\GDDriver; */ class DefaultDriver { - protected $driver; + /** @var DriverInterface|null */ + protected static $driver; public static function get(): DriverInterface { diff --git a/src/Drivers/AbstractCliDriver.php b/src/Drivers/AbstractCliDriver.php index 8a9021f..21f9642 100644 --- a/src/Drivers/AbstractCliDriver.php +++ b/src/Drivers/AbstractCliDriver.php @@ -4,10 +4,4 @@ namespace ByJoby\ImageTransform\Drivers; abstract class AbstractCliDriver extends AbstractDriver { - public function __construct() - { - if (!function_exists('exec')) { - throw new \Exception("CLI drivers can't be used with the current configuration because exec is disabled"); - } - } } diff --git a/src/Drivers/AbstractDriver.php b/src/Drivers/AbstractDriver.php index aaa8d41..211b827 100644 --- a/src/Drivers/AbstractDriver.php +++ b/src/Drivers/AbstractDriver.php @@ -5,20 +5,25 @@ namespace ByJoby\ImageTransform\Drivers; use ByJoby\ImageTransform\DriverInterface; use ByJoby\ImageTransform\Image; use ByJoby\ImageTransform\Sizers\AbstractSizer; +use Exception; abstract class AbstractDriver implements DriverInterface { + /** @var string|null */ protected $tempDir = null; + /** @var int */ protected $chmod_dir = 0775; + /** @var int */ protected $chmod_file = 0665; - abstract protected function doSave(Image $image, string $filename); + abstract protected function doSave(Image $image, string $filename): void; public function tempDir(): string { if (!$this->tempDir) { $this->setTempDir(sys_get_temp_dir() . '/byjoby_image-transform/' . uniqid("", true)); } + // @phpstan-ignore-next-line this is actually checked return $this->tempDir; } @@ -31,7 +36,7 @@ abstract class AbstractDriver implements DriverInterface return $this; } - protected function mkdir(string $dir) + protected function mkdir(string $dir): bool { // return true if dir exists and is writeable if (is_dir($dir) && is_writeable($dir)) { @@ -74,12 +79,15 @@ abstract class AbstractDriver implements DriverInterface } touch($filename); } - $this->doSave($image, realpath($filename)); + $filename = realpath($filename); + if (!$filename) throw new Exception("Invalid filename or path"); + $this->doSave($image, $filename); chmod($filename, $this->chmod_file); return null; } else { $filename = $this->tempDir() . '/' . uniqid() . '.jpg'; $this->doSave($image, $filename); + /** @var string we can count on this being a string because we just wrote it */ $output = file_get_contents($filename); unlink($filename); return $output; diff --git a/src/Drivers/AbstractExtensionDriver.php b/src/Drivers/AbstractExtensionDriver.php index b454259..00e0765 100644 --- a/src/Drivers/AbstractExtensionDriver.php +++ b/src/Drivers/AbstractExtensionDriver.php @@ -6,14 +6,20 @@ use ByJoby\ImageTransform\Image; abstract class AbstractExtensionDriver extends AbstractDriver { + // @phpstan-ignore-next-line we specify types in subclasses abstract protected function getImageObject(Image $image); + // @phpstan-ignore-next-line we specify types in subclasses abstract protected function doResize($object, Image $image); + // @phpstan-ignore-next-line we specify types in subclasses abstract protected function doCrop($object, Image $image); + // @phpstan-ignore-next-line we specify types in subclasses abstract protected function doFlip($object, Image $image); + // @phpstan-ignore-next-line we specify types in subclasses abstract protected function doRotation($object, Image $image); - abstract protected function saveImageObject($object, string $filename); + // @phpstan-ignore-next-line we specify types in subclasses + abstract protected function saveImageObject($object, string $filename): void; - public function doSave(Image $image, string $filename) + public function doSave(Image $image, string $filename): void { $object = $this->getImageObject($image); $object = $this->doRotation($object, $image); diff --git a/src/Drivers/GDDriver.php b/src/Drivers/GDDriver.php index 36bf4b3..07bb48b 100644 --- a/src/Drivers/GDDriver.php +++ b/src/Drivers/GDDriver.php @@ -3,6 +3,7 @@ namespace ByJoby\ImageTransform\Drivers; use ByJoby\ImageTransform\Image; +use GdImage; /** * This driver uses PHP's built-in GD libary. This is by far the slowest driver, @@ -18,42 +19,62 @@ class GDDriver extends AbstractExtensionDriver } } - protected function getImageObject(Image $image) + protected function getImageObject(Image $image): GdImage { $source = $image->source(); - $extension = strtolower(preg_replace('/^.+\./', '', $source)); + /** @var string */ + $extension = preg_replace('/^.+\./', '', $source); + $extension = strtolower($extension); switch ($extension) { case 'bmp': + // @phpstan-ignore-next-line this will throw an exception, which is good return imagecreatefrombmp($source); case 'gif': + // @phpstan-ignore-next-line this will throw an exception, which is good return imagecreatefromgif($source); case 'jpg': + // @phpstan-ignore-next-line this will throw an exception, which is good return imagecreatefromjpeg($source); case 'jpeg': + // @phpstan-ignore-next-line this will throw an exception, which is good return imagecreatefromjpeg($source); case 'png': + // @phpstan-ignore-next-line this will throw an exception, which is good return imagecreatefrompng($source); case 'wbmp': + // @phpstan-ignore-next-line this will throw an exception, which is good return imagecreatefromwbmp($source); case 'webp': + // @phpstan-ignore-next-line this will throw an exception, which is good return imagecreatefromwebp($source); case 'xbm': + // @phpstan-ignore-next-line this will throw an exception, which is good return imagecreatefromxbm($source); default: throw new \Exception("Unsupported input type: " . htmlentities($source)); } } - protected function doResize($object, Image $image) + /** + * @param GdImage $object + * @param Image $image + * @return GdImage + */ + protected function doResize($object, Image $image): GdImage { $sizer = $image->sizer(); if ($sizer->resizeToHeight() && $sizer->resizeToWidth()) { // sizer is calling for a resize + /** @var GdImage */ $new = imagecreatetruecolor($sizer->resizeToWidth(), $sizer->resizeToHeight()); imagecopyresampled( - $new, $object, - 0, 0,//dst x/y - 0, 0, + $new, + $object, + 0, + 0, + //dst x/y + 0, + 0, $sizer->resizeToWidth(), $sizer->resizeToHeight(), $sizer->originalWidth(), $sizer->originalHeight() ); @@ -64,17 +85,26 @@ class GDDriver extends AbstractExtensionDriver } } - protected function doCrop($object, Image $image) + /** + * @param GdImage $object + * @param Image $image + * @return GdImage + */ + protected function doCrop($object, Image $image): GdImage { $sizer = $image->sizer(); if ($sizer->cropToHeight() && $sizer->cropToWidth()) { // sizer is calling for a crop + /** @var GdImage */ $new = imagecreatetruecolor($sizer->cropToWidth(), $sizer->cropToHeight()); imagecopyresampled( - $new, $object, - ($sizer->cropToWidth()-$sizer->resizeToWidth())/2,($sizer->cropToHeight()-$sizer->resizeToHeight())/2, - 0,0, - $sizer->resizetoWidth(),$sizer->resizeToHeight(),$sizer->resizeToWidth(),$sizer->resizeToHeight() + $new, + $object, + ($sizer->cropToWidth() - $sizer->resizeToWidth()) / 2, ($sizer->cropToHeight() - $sizer->resizeToHeight()) / 2, + 0, + 0, + // @phpstan-ignore-next-line these are definitely set + $sizer->resizetoWidth(), $sizer->resizeToHeight(), $sizer->resizeToWidth(), $sizer->resizeToHeight() ); return $new; } else { @@ -83,47 +113,65 @@ class GDDriver extends AbstractExtensionDriver } } - protected function doFlip($object, Image $image) + /** + * @param GdImage $object + * @param Image $image + * @return GdImage + */ + protected function doFlip($object, Image $image): GdImage { if ($image->getFlipH()) { - imageflip($object,IMG_FLIP_HORIZONTAL); + imageflip($object, IMG_FLIP_HORIZONTAL); } if ($image->getFlipV()) { - imageflip($object,IMG_FLIP_VERTICAL); + imageflip($object, IMG_FLIP_VERTICAL); } return $object; } - protected function doRotation($object, Image $image) + /** + * @param GdImage $object + * @param Image $image + * @return GdImage + */ + protected function doRotation($object, Image $image): GdImage { if ($rotationAmount = 360 - $image->rotation() * 90) { + // @phpstan-ignore-next-line return imagerotate($object, $rotationAmount, 0); } return $object; } - protected function saveImageObject($object, string $filename) + /** + * @param GdImage $object + * @param string $filename + * @return void + */ + protected function saveImageObject($object, string $filename): void { - $extension = strtolower(preg_replace('/^.+\./', '', $filename)); + /** @var string */ + $extension = preg_replace('/^.+\./', '', $filename); + $extension = strtolower($extension); switch ($extension) { case 'bmp': - return imagebmp($object, $filename); + imagebmp($object, $filename); case 'gif': - return imagegif($object, $filename); + imagegif($object, $filename); case 'jpg': - return imagejpeg($object, $filename); + imagejpeg($object, $filename); case 'jpeg': - return imagejpeg($object, $filename); + imagejpeg($object, $filename); case 'png': - return imagepng($object, $filename); + imagepng($object, $filename); case 'wbmp': - return imagewbmp($object, $filename); + imagewbmp($object, $filename); case 'webp': - return imagewebp($object, $filename); + imagewebp($object, $filename); case 'xbm': - return imagexbm($object, $filename); + imagexbm($object, $filename); default: throw new \Exception("Unsupported output type: " . htmlentities($filename)); } } -} +} \ No newline at end of file diff --git a/src/Drivers/MagickCliDriver.php b/src/Drivers/MagickCliDriver.php index d9af730..92fc32b 100644 --- a/src/Drivers/MagickCliDriver.php +++ b/src/Drivers/MagickCliDriver.php @@ -13,12 +13,8 @@ use ByJoby\ImageTransform\Image; */ class MagickCliDriver extends AbstractCliDriver { - protected $mogrify_executable; - - public function __construct($mogrify_executable = 'magick mogrify') + public function __construct(protected string $mogrify_executable = 'magick mogrify') { - parent::__construct(); - $this->mogrify_executable = $mogrify_executable; } protected function mogrifyExecutable(): string @@ -26,7 +22,7 @@ class MagickCliDriver extends AbstractCliDriver return $this->mogrify_executable; } - protected function doSave(Image $image, string $filename) + protected function doSave(Image $image, string $filename): void { // basics of command $command = [ diff --git a/src/Drivers/MagickDriver.php b/src/Drivers/MagickDriver.php new file mode 100644 index 0000000..d979883 --- /dev/null +++ b/src/Drivers/MagickDriver.php @@ -0,0 +1,144 @@ +use_gmagick) return new Gmagick($image->source()); + else return new Imagick($image->source()); + } + + /** + * @param Gmagick|Imagick $object + * @param Image $image + * @return Gmagick|Imagick + */ + protected function doResize($object, Image $image): Gmagick|Imagick + { + $sizer = $image->sizer(); + if ($sizer->resizeToHeight() && $sizer->resizeToWidth()) { + if ($object instanceof Gmagick) { + $object->resizeimage( + $sizer->resizeToWidth(), + $sizer->resizeToHeight(), + Gmagick::FILTER_LANCZOS, + 0 + ); + } else { + $object->resizeImage( + $sizer->resizeToWidth(), + $sizer->resizeToHeight(), + Imagick::FILTER_LANCZOS, + 0 + ); + } + } + return $object; + } + + /** + * @param Gmagick|Imagick $object + * @param Image $image + * @return Gmagick|Imagick + */ + protected function doCrop($object, Image $image): Gmagick|Imagick + { + $sizer = $image->sizer(); + $height = $sizer->cropToHeight(); + $width = $sizer->cropToWidth(); + if ($height && $width) { + $x = intval(($sizer->resizetoWidth() - $width) / 2); + $y = intval(($sizer->resizetoHeight() - $height) / 2); + if ($object instanceof Gmagick) { + $object->cropimage( + $width, + $height, + $x, + $y + ); + } else { + $object->cropImage( + $width, + $height, + $x, + $y + ); + } + } + return $object; + } + + /** + * @param Gmagick|Imagick $object + * @param Image $image + * @return Gmagick|Imagick + */ + protected function doFlip($object, Image $image): Gmagick|Imagick + { + if ($image->getFlipH()) { + if ($object instanceof Gmagick) $object->flipimage(); + else $object->flipImage(); + } + if ($image->getFlipV()) { + if ($object instanceof Gmagick) $object->flopimage(); + else $object->flopImage(); + } + return $object; + } + + /** + * @param Gmagick|Imagick $object + * @param Image $image + * @return Gmagick|Imagick + */ + protected function doRotation($object, Image $image): Gmagick|Imagick + { + if ($image->rotation()) { + if ($object instanceof Gmagick) $object->rotateimage(new GmagickPixel("#000"), $image->rotation() * 90); + else $object->rotateImage(new ImagickPixel("#000"), $image->rotation() * 90); + } + return $object; + } + + /** + * @param Gmagick|Imagick $object + * @param string $filename + * @return void + */ + protected function saveImageObject($object, string $filename): void + { + /** @var string */ + $format = preg_replace('/^.+\./', '', $filename); + $format = strtoupper($format); + if ($format == 'JPG') $format = 'JPEG'; + if ($object instanceof Gmagick) { + $object->setimageformat($format); + $object->writeimage($filename, true); + } else { + $object->setImageFormat($format); + $object->writeImage($filename); + } + } +} \ No newline at end of file diff --git a/src/Image.php b/src/Image.php index 6564b4f..9dc3306 100644 --- a/src/Image.php +++ b/src/Image.php @@ -7,13 +7,21 @@ use ByJoby\ImageTransform\Sizers\Original; class Image { + /** @var string */ protected $source; + /** @var DriverInterface|null */ protected $driver; + /** @var int */ protected $originalWidth; + /** @var int */ protected $originalHeight; + /** @var int */ protected $rotation = 0; + /** @var boolean */ protected $flipH = false; + /** @var boolean */ protected $flipV = false; + /** @var AbstractSizer */ protected $sizer = null; public function __construct(string $source, AbstractSizer|null $sizer = null) @@ -30,10 +38,11 @@ class Image public function setSource(string $source): static { // set source - $this->source = realpath($source); - if (!$this->source) { - throw new \Exception("Source image not found: " . htmlentities($source)); + $source = realpath($source); + if (!$source) { + throw new \Exception("Source image not found"); } + $this->source = $source; // validate file if (!is_file($this->source)) { throw new \Exception("Image file doesn't exist: " . htmlentities($this->source)); @@ -42,7 +51,11 @@ class Image throw new \Exception("Invalid image file: " . htmlentities($this->source)); } // get height/width - list($this->originalWidth, $this->originalHeight) = getimagesize($this->source); + $size = getimagesize($this->source); + if (!$size) { + throw new \Exception("Couldn't get image size: " . htmlentities($this->source)); + } + list($this->originalWidth, $this->originalHeight) = $size; // return self return $this; } @@ -55,7 +68,7 @@ class Image public function setSizer(AbstractSizer $sizer): static { $this->sizer = clone $sizer; - $this->sizer->image($this); + $this->sizer->setImage($this); return $this; } diff --git a/src/Sizers/AbstractSizer.php b/src/Sizers/AbstractSizer.php index 08f8834..4ca31e2 100644 --- a/src/Sizers/AbstractSizer.php +++ b/src/Sizers/AbstractSizer.php @@ -6,6 +6,7 @@ use ByJoby\ImageTransform\Image; abstract class AbstractSizer { + /** @var Image */ protected $image; abstract public function width(): int; @@ -46,8 +47,9 @@ abstract class AbstractSizer return $this->originalWidth()/$this->originalHeight(); } - public function image(Image $image) + public function setImage(Image $image): static { $this->image = $image; + return $this; } } diff --git a/src/Sizers/Cover.php b/src/Sizers/Cover.php index 8cdb921..a2823cd 100644 --- a/src/Sizers/Cover.php +++ b/src/Sizers/Cover.php @@ -4,6 +4,7 @@ namespace ByJoby\ImageTransform\Sizers; class Cover extends AbstractSizer { + /** @var int */ protected $width, $height; public function __construct(int $width, int $height) @@ -17,14 +18,17 @@ class Cover extends AbstractSizer return $this->width / $this->height; } + /** + * @return array{height:int,width:int} + */ protected function calculateSize(): array { if ($this->targetRatio() < $this->originalRatio()) { $height = $this->height; - $width = round($height * $this->originalRatio()); + $width = intval(round($height * $this->originalRatio())); } else { $width = $this->width; - $height = round($width / $this->originalRatio()); + $height = intval(round($width / $this->originalRatio())); } return [ 'height' => $height, @@ -61,4 +65,4 @@ class Cover extends AbstractSizer { return $this->height; } -} +} \ No newline at end of file diff --git a/src/Sizers/Fit.php b/src/Sizers/Fit.php index 8c46839..5fe74dd 100644 --- a/src/Sizers/Fit.php +++ b/src/Sizers/Fit.php @@ -4,6 +4,7 @@ namespace ByJoby\ImageTransform\Sizers; class Fit extends AbstractSizer { + /** @var int */ protected $width, $height; public function __construct(int $width, int $height) @@ -27,14 +28,17 @@ class Fit extends AbstractSizer return $this->width / $this->height; } + /** + * @return array{height:int,width:int} + */ protected function calculateSize(): array { if ($this->targetRatio() > $this->originalRatio()) { $height = $this->height; - $width = round($height * $this->originalRatio()); + $width = intval(round($height * $this->originalRatio())); } else { $width = $this->width; - $height = round($width / $this->originalRatio()); + $height = intval(round($width / $this->originalRatio())); } return [ 'height' => $height, diff --git a/tests/100x200.jpg b/tests/100x200.jpg deleted file mode 100644 index aa3bcc5..0000000 Binary files a/tests/100x200.jpg and /dev/null differ diff --git a/tests/200x100.jpg b/tests/200x100.jpg deleted file mode 100644 index cb9088b..0000000 Binary files a/tests/200x100.jpg and /dev/null differ diff --git a/tests/200x200.jpg b/tests/200x200.jpg deleted file mode 100644 index 6a4cf3e..0000000 Binary files a/tests/200x200.jpg and /dev/null differ diff --git a/tests/Sizers/CoverTest.php b/tests/Sizers/CoverTest.php new file mode 100644 index 0000000..276a47e --- /dev/null +++ b/tests/Sizers/CoverTest.php @@ -0,0 +1,109 @@ +createMock(Image::class); + $image->method("originalWidth")->willReturn(200); + $image->method("originalHeight")->willReturn(200); + // spin up a 50x50 sizer + $sizer = new Cover(50, 50); + $sizer->setImage($image); + // resized to cover + $this->assertEquals(50, $sizer->resizeToWidth()); + $this->assertEquals(50, $sizer->resizeToHeight()); + // cropped to size + $this->assertEquals(50, $sizer->cropToWidth()); + $this->assertEquals(50, $sizer->cropToHeight()); + } + + public function testSquareImageSizedLandscape(): void + { + // mock 200x200 image + $image = $this->createMock(Image::class); + $image->method("originalWidth")->willReturn(200); + $image->method("originalHeight")->willReturn(200); + // spin up a 100x50 sizer + $sizer = new Cover(100, 50); + $sizer->setImage($image); + // resized to cover + $this->assertEquals(100, $sizer->resizeToWidth()); + $this->assertEquals(100, $sizer->resizeToHeight()); + // cropped to size + $this->assertEquals(100, $sizer->cropToWidth()); + $this->assertEquals(50, $sizer->cropToHeight()); + } + public function testSquareImageSizedPortrait(): void + { + // mock 200x200 image + $image = $this->createMock(Image::class); + $image->method("originalWidth")->willReturn(200); + $image->method("originalHeight")->willReturn(200); + // spin up a 50x100 sizer + $sizer = new Cover(50, 100); + $sizer->setImage($image); + // resized to cover + $this->assertEquals(100, $sizer->resizeToWidth()); + $this->assertEquals(100, $sizer->resizeToHeight()); + // cropped to size + $this->assertEquals(50, $sizer->cropToWidth()); + $this->assertEquals(100, $sizer->cropToHeight()); + } + + public function testLandscapeImageSizedSquare(): void + { + // mock 200x100 image + $image = $this->createMock(Image::class); + $image->method("originalWidth")->willReturn(200); + $image->method("originalHeight")->willReturn(100); + // spin up a 50x50 sizer + $sizer = new Cover(50, 50); + $sizer->setImage($image); + // resized to cover + $this->assertEquals(100, $sizer->resizeToWidth()); + $this->assertEquals(50, $sizer->resizeToHeight()); + // cropped to size + $this->assertEquals(50, $sizer->cropToWidth()); + $this->assertEquals(50, $sizer->cropToHeight()); + } + + public function testLandscapeImageSizedLandscape(): void + { + // mock 200x100 image + $image = $this->createMock(Image::class); + $image->method("originalWidth")->willReturn(200); + $image->method("originalHeight")->willReturn(100); + // spin up a 100x50 sizer + $sizer = new Cover(100, 50); + $sizer->setImage($image); + // resized to cover + $this->assertEquals(100, $sizer->resizeToWidth()); + $this->assertEquals(50, $sizer->resizeToHeight()); + // cropped to size + $this->assertEquals(100, $sizer->cropToWidth()); + $this->assertEquals(50, $sizer->cropToHeight()); + } + public function testLandscapeImageSizedPortrait(): void + { + // mock 200x100 image + $image = $this->createMock(Image::class); + $image->method("originalWidth")->willReturn(200); + $image->method("originalHeight")->willReturn(100); + // spin up a 50x100 sizer + $sizer = new Cover(50, 100); + $sizer->setImage($image); + // resized to cover + $this->assertEquals(200, $sizer->resizeToWidth()); + $this->assertEquals(100, $sizer->resizeToHeight()); + // cropped to size + $this->assertEquals(50, $sizer->cropToWidth()); + $this->assertEquals(100, $sizer->cropToHeight()); + } +} \ No newline at end of file diff --git a/tests/input-100x200.png b/tests/input-100x200.png new file mode 100644 index 0000000..8f08d38 Binary files /dev/null and b/tests/input-100x200.png differ diff --git a/tests/input-200x100.png b/tests/input-200x100.png new file mode 100644 index 0000000..d4518f7 Binary files /dev/null and b/tests/input-200x100.png differ diff --git a/tests/input-200x200.png b/tests/input-200x200.png new file mode 100644 index 0000000..cbe4d4b Binary files /dev/null and b/tests/input-200x200.png differ diff --git a/tests/test.svg b/tests/test.svg new file mode 100644 index 0000000..1d0ad19 --- /dev/null +++ b/tests/test.svg @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +