diff --git a/.github/workflows/build.yml b/.github/workflows/gmagick.yml similarity index 92% rename from .github/workflows/build.yml rename to .github/workflows/gmagick.yml index 719d5a9..d87b5dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/gmagick.yml @@ -19,12 +19,13 @@ on: - 'infection.json.dist' - 'psalm.xml' -name: build +name: gmagick jobs: phpunit: uses: yiisoft/actions/.github/workflows/phpunit.yml@master with: + extensions: intl, gmagick os: >- ['ubuntu-latest', 'windows-latest'] php: >- diff --git a/.github/workflows/imagick.yml b/.github/workflows/imagick.yml new file mode 100644 index 0000000..244c016 --- /dev/null +++ b/.github/workflows/imagick.yml @@ -0,0 +1,32 @@ +on: + pull_request: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'psalm.xml' + + push: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'psalm.xml' + +name: imagick + +jobs: + phpunit: + uses: yiisoft/actions/.github/workflows/phpunit.yml@master + with: + extensions: intl, imagick + os: >- + ['ubuntu-latest', 'windows-latest'] + php: >- + ['8.1', '8.2', '8.3'] diff --git a/composer.json b/composer.json index cd5155d..1599aa9 100644 --- a/composer.json +++ b/composer.json @@ -1,15 +1,19 @@ { - "name": "yii2/template", - "type": "library", - "description": "_____", + "name": "yii2-extensions/imagine", + "type": "yii2-extension", + "description": "The Imagine integration for the Yii framework.", "keywords": [ - "_____" + "yii2", + "imagine", + "image", + "helper" ], "license": "mit", "minimum-stability": "dev", "prefer-stable": true, "require": { "php": ">=8.1", + "imagine/imagine": "^1.3", "yiisoft/yii2": "^2.2" }, "require-dev": { @@ -19,12 +23,12 @@ }, "autoload": { "psr-4": { - "yii\\template\\": "src" + "yii\\imagine\\": "src" } }, "autoload-dev": { "psr-4": { - "yii\\template\\tests\\": "tests" + "yiiunit\\imagine\\": "tests" } }, "extra": { diff --git a/phpstan.neon b/phpstan.neon index 0b239cb..7c7b893 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -8,7 +8,7 @@ parameters: - YII_ENV_PROD - YII_ENV_TEST - level: 2 + level: 3 paths: - src diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f29a28d..1031c61 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,24 +1,24 @@ - - - tests - - + + + tests + + - - - ./src - - + + + ./src + + diff --git a/src/BaseImage.php b/src/BaseImage.php new file mode 100644 index 0000000..b86a3d4 --- /dev/null +++ b/src/BaseImage.php @@ -0,0 +1,465 @@ + + * @author Qiang Xue + * @since 2.0 + */ +class BaseImage +{ + /** + * GD2 driver definition for Imagine implementation using the GD library. + */ + const DRIVER_GD2 = 'gd2'; + /** + * imagick driver definition. + */ + const DRIVER_IMAGICK = 'imagick'; + /** + * gmagick driver definition. + */ + const DRIVER_GMAGICK = 'gmagick'; + + /** + * @var ImagineInterface instance. + */ + private static $_imagine; + + + /** + * @var array|string the driver to use. This can be either a single driver name or an array of driver names. + * If the latter, the first available driver will be used. + */ + public static $driver = [self::DRIVER_GMAGICK, self::DRIVER_IMAGICK, self::DRIVER_GD2]; + /** + * @var string background color to use when creating thumbnails in `ImageInterface::THUMBNAIL_INSET` mode with + * both width and height specified. Default is white. + * + * @since 2.0.4 + */ + public static $thumbnailBackgroundColor = 'FFF'; + /** + * @var string|int background alpha (transparency) to use when creating thumbnails in + * `ImageInterface::THUMBNAIL_INSET` mode with both width and height specified. Default is solid. + * + * @since 2.0.4 + */ + public static $thumbnailBackgroundAlpha = 100; + + /** + * Returns the `Imagine` object that supports various image manipulations. + * @return ImagineInterface the `Imagine` object + */ + public static function getImagine() + { + if (self::$_imagine === null) { + self::$_imagine = static::createImagine(); + } + + return self::$_imagine; + } + + /** + * @param ImagineInterface $imagine the `Imagine` object. + */ + public static function setImagine($imagine) + { + self::$_imagine = $imagine; + } + + /** + * Creates an `Imagine` object based on the specified [[driver]]. + * @return ImagineInterface the new `Imagine` object + * @throws InvalidConfigException if [[driver]] is unknown or the system doesn't support any [[driver]]. + */ + protected static function createImagine() + { + foreach ((array) static::$driver as $driver) { + switch ($driver) { + case self::DRIVER_GMAGICK: + if (class_exists('Gmagick', false)) { + return new \Imagine\Gmagick\Imagine(); + } + break; + case self::DRIVER_IMAGICK: + if (class_exists('Imagick', false)) { + return new \Imagine\Imagick\Imagine(); + } + break; + case self::DRIVER_GD2: + if (function_exists('gd_info')) { + return new \Imagine\Gd\Imagine(); + } + break; + default: + throw new InvalidConfigException("Unknown driver: $driver"); + } + } + throw new InvalidConfigException('Your system does not support any of these drivers: ' . implode(',', (array) static::$driver)); + } + + /** + * Takes either file path or ImageInterface. In case of file path, creates an instance of ImageInterface from it. + * + * @param string|resource|ImageInterface $image + * @return ImageInterface + * @throws InvalidArgumentException if the image argument is invalid. + * @since 2.1.0 + */ + protected static function ensureImageInterfaceInstance($image) + { + if ($image instanceof ImageInterface) { + return $image; + } + + if (is_resource($image)) { + return static::getImagine()->read($image); + } + + if (is_string($image)) { + return static::getImagine()->open(Yii::getAlias($image)); + } + + throw new InvalidArgumentException( + 'File should be either ImageInterface, resource or a string containing file path.' + ); + } + + /** + * Crops an image. + * + * For example: + * + * ```php + * $obj->crop('path\to\image.jpg', 200, 200, [5, 5]); + * + * $point = new \Imagine\Image\Point(5, 5); + * $obj->crop('path\to\image.jpg', 200, 200, $point); + * ``` + * + * @param string|resource|ImageInterface $image either ImageInterface, resource or a string containing file path + * @param int $width the crop width + * @param int $height the crop height + * @param array $start the starting point. This must be an array with two elements representing `x` and `y` coordinates. + * @return ImageInterface + * @throws InvalidArgumentException if the `$start` parameter is invalid. + */ + public static function crop($image, $width, $height, array $start = [0, 0]) + { + if (!isset($start[0], $start[1])) { + throw new InvalidArgumentException('$start must be an array of two elements.'); + } + + return static::ensureImageInterfaceInstance($image) + ->copy() + ->crop(new Point($start[0], $start[1]), new Box($width, $height)); + } + + /** + * Rotates an image automatically based on EXIF information. + * + * @param string|resource|ImageInterface $image either ImageInterface, resource or a string containing file path + * @param string $color + * @return \Imagine\Image\ImageInterface + * @since 2.1.0 + */ + public static function autorotate($image, $color = '000000') + { + return (new Autorotate($color))->apply(static::ensureImageInterfaceInstance($image)); + } + + /** + * Creates a thumbnail image. + * + * If one of thumbnail dimensions is set to `null`, another one is calculated automatically based on aspect ratio of + * original image. Note that calculated thumbnail dimension may vary depending on the source image in this case. + * + * If both dimensions are specified, resulting thumbnail would be exactly the width and height specified. How it's + * achieved depends on the mode defined via settings parameter. + * + * If `ImageInterface::THUMBNAIL_OUTBOUND` mode is used, which is default, then the thumbnail is scaled so that + * its smallest side equals the length of the corresponding side in the original image. Any excess outside of + * the scaled thumbnail’s area will be cropped, and the returned thumbnail will have the exact width and height + * specified. + * + * If thumbnail mode is `ImageInterface::THUMBNAIL_INSET`, the original image is scaled down so it is fully + * contained within the thumbnail dimensions. The rest is filled with background that could be configured via + * [[Image::$thumbnailBackgroundColor]] and [[Image::$thumbnailBackgroundAlpha]]. + * + * @param string|resource|ImageInterface $image either ImageInterface, resource or a string containing file path + * @param int $width the width in pixels to create the thumbnail + * @param int $height the height in pixels to create the thumbnail + * @param int $settings settings for resizing original image, one or more of the ManipulatorInterface::THUMBNAIL_ flags (joined with |) + * @return ImageInterface + */ + public static function thumbnail($image, $width, $height, $settings = ManipulatorInterface::THUMBNAIL_OUTBOUND) + { + $img = self::ensureImageInterfaceInstance($image); + + /** @var BoxInterface $sourceBox */ + $sourceBox = $img->getSize(); + $thumbnailBox = static::getThumbnailBox($sourceBox, $width, $height); + + $allowUpscale = (bool) ($settings & ImageInterface::THUMBNAIL_FLAG_UPSCALE); + if (self::isUpscaling($sourceBox, $thumbnailBox) && !$allowUpscale) { + return $img->copy(); + } + + $img = $img->thumbnail($thumbnailBox, $settings); + + if ($settings & ImageInterface::THUMBNAIL_OUTBOUND) { + return $img; + } + + $size = $img->getSize(); + + if ($size->getWidth() == $width && $size->getHeight() == $height) { + return $img; + } + + $palette = new RGB(); + $color = $palette->color(static::$thumbnailBackgroundColor, static::$thumbnailBackgroundAlpha); + + // create empty image to preserve aspect ratio of thumbnail + $thumb = static::getImagine()->create($thumbnailBox, $color); + + // calculate points + $startX = 0; + $startY = 0; + if ($size->getWidth() < $width) { + $startX = ceil(($width - $size->getWidth()) / 2); + } + if ($size->getHeight() < $height) { + $startY = ceil(($height - $size->getHeight()) / 2); + } + + $thumb->paste($img, new Point($startX, $startY)); + + return $thumb; + } + + /** + * Resizes an image. + * + * If one of the dimensions is set to `null`, another one is calculated automatically based on aspect ratio of + * original image. + * + * If both of the dimensions are set then new dimensions are calculated so that image keeps aspect ratio. + * + * You can set $keepAspectRatio to false if you want to force fixed width and height. + * + * @param string|resource|ImageInterface $image either ImageInterface, resource or a string containing file path + * @param int $width the width in pixels + * @param int $height the height in pixels + * @param bool $keepAspectRatio should the image keep aspect ratio + * @param bool $allowUpscaling should the image be upscaled if needed + * @return ImageInterface + * + * @since 2.1.1 + */ + public static function resize($image, $width, $height, $keepAspectRatio = true, $allowUpscaling = false) + { + $img = self::ensureImageInterfaceInstance($image)->copy(); + + /** @var BoxInterface $sourceBox */ + $sourceBox = $img->getSize(); + $destinationBox = static::getBox($sourceBox, $width, $height, $keepAspectRatio); + + if ($allowUpscaling === false && self::isUpscaling($sourceBox, $destinationBox)) { + return $img; + } + + return $img->resize($destinationBox); + } + + /** + * Adds a watermark to an existing image. + * @param string|resource|ImageInterface $image either ImageInterface, resource or a string containing file path + * @param string|resource|ImageInterface $watermarkImage either ImageInterface, resource or a string containing watermark file path + * @param array $start the starting point. This must be an array with two elements representing `x` and `y` coordinates. + * @return ImageInterface + * @throws InvalidArgumentException if `$start` is invalid. + */ + public static function watermark($image, $watermarkImage, array $start = [0, 0]) + { + if (!isset($start[0], $start[1])) { + throw new InvalidArgumentException('$start must be an array of two elements.'); + } + + $img = self::ensureImageInterfaceInstance($image); + $watermark = self::ensureImageInterfaceInstance($watermarkImage); + $img->paste($watermark, new Point($start[0], $start[1])); + + return $img; + } + + /** + * Draws a text string on an existing image. + * @param string|resource|ImageInterface $image either ImageInterface, resource or a string containing file path + * @param string $text the text to write to the image + * @param string $fontFile the file path or path alias + * @param array $start the starting position of the text. This must be an array with two elements representing `x` and `y` coordinates. + * @param array $fontOptions the font options. The following options may be specified: + * + * - color: The font color. Defaults to "fff". + * - size: The font size. Defaults to 12. + * - angle: The angle to use to write the text. Defaults to 0. + * + * @return ImageInterface + * @throws InvalidArgumentException if `$fontOptions` is invalid. + */ + public static function text($image, $text, $fontFile, array $start = [0, 0], array $fontOptions = []) + { + if (!isset($start[0], $start[1])) { + throw new InvalidArgumentException('$start must be an array of two elements.'); + } + + $fontSize = ArrayHelper::getValue($fontOptions, 'size', 12); + $fontColor = ArrayHelper::getValue($fontOptions, 'color', 'fff'); + $fontAngle = ArrayHelper::getValue($fontOptions, 'angle', 0); + + $palette = new RGB(); + $color = $palette->color($fontColor); + + $img = self::ensureImageInterfaceInstance($image); + $font = static::getImagine()->font(Yii::getAlias($fontFile), $fontSize, $color); + + $img->draw()->text($text, $font, new Point($start[0], $start[1]), $fontAngle); + + return $img; + } + + /** + * Adds a frame around of the image. Please note that the image size will increase by `$margin` x 2. + * @param string|resource|ImageInterface $image either ImageInterface, resource or a string containing file path + * @param int $margin the frame size to add around the image + * @param string $color the frame color + * @param int $alpha the alpha value of the frame. + * @return ImageInterface + */ + public static function frame($image, $margin = 20, $color = '666', $alpha = 100) + { + $img = self::ensureImageInterfaceInstance($image); + + $size = $img->getSize(); + + $pasteTo = new Point($margin, $margin); + + $palette = new RGB(); + $color = $palette->color($color, $alpha); + + $box = new Box($size->getWidth() + ceil($margin * 2), $size->getHeight() + ceil($margin * 2)); + + $finalImage = static::getImagine()->create($box, $color); + + $finalImage->paste($img, $pasteTo); + + return $finalImage; + } + + /** + * Returns box for a thumbnail to be created. If one of the dimensions is set to `null`, another one is calculated + * automatically based on width to height ratio of original image box. + * + * @param BoxInterface $sourceBox original image box + * @param int $width thumbnail width + * @param int $height thumbnail height + * @return BoxInterface thumbnail box + * + * @since 2.0.4 + */ + public static function getThumbnailBox(BoxInterface $sourceBox, $width, $height) + { + if ($width !== null && $height !== null) { + return new Box($width, $height); + } + + return self::getBox($sourceBox, $width, $height, false); + } + + /** + * Returns box for an image to be created. + * + * If one of the dimensions is set to `null`, another one is calculated automatically based on width to height ratio + * of original image box. + * + * If both of the dimensions are set then new dimensions are calculated so that image keeps aspect ratio. + * + * You can set $keepAspectRatio to false if you want to force fixed width and height. + * + * @param BoxInterface $sourceBox original image box + * @param int $width new image width + * @param int $height new image height + * @param bool $keepAspectRatio should we keep aspect ratio even if both with and height are set + * @return BoxInterface new image box + * @throws InvalidArgumentException if both width and height are null. + * + * @since 2.1.1 + */ + public static function getBox(BoxInterface $sourceBox, $width, $height, $keepAspectRatio = true) + { + if ($width === null && $height === null) { + throw new InvalidArgumentException('Width and height cannot be null at same time.'); + } + + $ratio = $sourceBox->getWidth() / $sourceBox->getHeight(); + if ($keepAspectRatio === false) { + if ($height === null) { + $height = ceil($width / $ratio); + } elseif ($width === null) { + $width = ceil($height * $ratio); + } + } else { + if ($height === null) { + $height = ceil($width / $ratio); + } elseif ($width === null) { + $width = ceil($height * $ratio); + } elseif ($width / $height > $ratio) { + $width = $height * $ratio; + } else { + $height = $width / $ratio; + } + } + + return new Box($width, $height); + } + + /** + * Checks if upscaling is going to happen + * + * @param BoxInterface $sourceBox + * @param BoxInterface $destinationBox + * @return bool + */ + public static function isUpscaling(BoxInterface $sourceBox, BoxInterface $destinationBox) + { + return ($sourceBox->getWidth() <= $destinationBox->getWidth() && $sourceBox->getHeight() <= $destinationBox->getHeight()) || (!$destinationBox->getWidth() && !$destinationBox->getHeight()); + } +} diff --git a/src/Example.php b/src/Example.php deleted file mode 100644 index 067eeb8..0000000 --- a/src/Example.php +++ /dev/null @@ -1,13 +0,0 @@ -save(Yii::getAlias('@runtime/thumb-test-image.jpg'), ['quality' => 50]); + * ``` + * + * @author Antonio Ramirez + * @author Qiang Xue + * @since 2.0 + */ +final class Image extends BaseImage +{ +} diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 2825a4e..0000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertTrue($example->getExample()); - } -} diff --git a/tests/ImageGdTest.php b/tests/ImageGdTest.php new file mode 100644 index 0000000..f535da6 --- /dev/null +++ b/tests/ImageGdTest.php @@ -0,0 +1,33 @@ +markTestSkipped('Skipping ImageGdTest, Gd not installed'); + } else { + Image::setImagine(null); + Image::$driver = Image::DRIVER_GD2; + parent::setUp(); + } + } + + protected function isFontTestSupported() + { + $infos = gd_info(); + + return isset($infos['FreeType Support']) ? $infos['FreeType Support'] : false; + } +} diff --git a/tests/ImageGmagickTest.php b/tests/ImageGmagickTest.php new file mode 100644 index 0000000..a5fcf62 --- /dev/null +++ b/tests/ImageGmagickTest.php @@ -0,0 +1,31 @@ +markTestSkipped('Skipping ImageGmagickTest, Gmagick is not installed'); + } else { + Image::setImagine(null); + Image::$driver = Image::DRIVER_GMAGICK; + parent::setUp(); + } + } + + protected function isFontTestSupported() + { + return true; + } +} diff --git a/tests/ImageImagickTest.php b/tests/ImageImagickTest.php new file mode 100644 index 0000000..e17027c --- /dev/null +++ b/tests/ImageImagickTest.php @@ -0,0 +1,33 @@ +markTestSkipped('Skipping ImageImagickTest, Imagick is not installed'); + } elseif (defined('HHVM_VERSION')) { + $this->markTestSkipped('Imagine does not seem to support HHVM right now.'); + } else { + Image::setImagine(null); + Image::$driver = Image::DRIVER_IMAGICK; + parent::setUp(); + } + } + + protected function isFontTestSupported() + { + return true; + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..2b61969 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,63 @@ +destroyApplication(); + } + + /** + * Populates Yii::$app with a new application + * The application will be destroyed on tearDown() automatically. + * @param array $config The application configuration, if needed + * @param string $appClass name of the application class to create + */ + protected function mockApplication($config = [], $appClass = '\yii\console\Application') + { + new $appClass(ArrayHelper::merge([ + 'id' => 'testapp', + 'basePath' => __DIR__, + 'vendorPath' => dirname(__DIR__) . '/vendor', + ], $config)); + } + + protected function mockWebApplication($config = [], $appClass = '\yii\web\Application') + { + new $appClass(ArrayHelper::merge([ + 'id' => 'testapp', + 'basePath' => __DIR__, + 'vendorPath' => dirname(__DIR__) . '/vendor', + 'components' => [ + 'request' => [ + 'cookieValidationKey' => 'wefJDF8sfdsfSDefwqdxj9oq', + 'scriptFile' => __DIR__ .'/index.php', + 'scriptUrl' => '/index.php', + ], + ] + ], $config)); + } + + /** + * Destroys application in Yii::$app by setting it to null. + */ + protected function destroyApplication() + { + Yii::$app = null; + Yii::$container = new Container(); + } +} diff --git a/tests/base/AbstractImage.php b/tests/base/AbstractImage.php new file mode 100644 index 0000000..b9fc8e6 --- /dev/null +++ b/tests/base/AbstractImage.php @@ -0,0 +1,192 @@ +imageFile = Yii::getAlias('@yiiunit/imagine/data/large.jpg'); + $this->watermarkFile = Yii::getAlias('@yiiunit/imagine/data/xparent.gif'); + $this->runtimeTextFile = Yii::getAlias('@yiiunit/imagine/runtime/image-text-test.png'); + $this->runtimeWatermarkFile = Yii::getAlias('@yiiunit/imagine/runtime/image-watermark-test.png'); + parent::setUp(); + } + + /** + * {@inheritdoc} + */ + protected function tearDown(): void + { + @unlink($this->runtimeTextFile); + @unlink($this->runtimeWatermarkFile); + } + + public function testText() + { + if (!$this->isFontTestSupported()) { + $this->markTestSkipped('Skipping ImageGdTest Gd not installed'); + } + + $fontFile = Yii::getAlias('@yiiunit/imagine/data/GothamRnd-Light.otf'); + + $img = Image::text($this->imageFile, 'Yii-2 Image', $fontFile, [0, 0], [ + 'size' => 12, + 'color' => '000' + ]); + + $img->save($this->runtimeTextFile); + $this->assertTrue(file_exists($this->runtimeTextFile)); + + } + + public function testCrop() + { + $point = [20, 20]; + $img = Image::crop($this->imageFile, 100, 100, $point); + + $this->assertEquals(100, $img->getSize()->getWidth()); + $this->assertEquals(100, $img->getSize()->getHeight()); + + } + + public function testWatermark() + { + $img = Image::watermark($this->imageFile, $this->watermarkFile); + $img->save($this->runtimeWatermarkFile); + $this->assertTrue(file_exists($this->runtimeWatermarkFile)); + } + + public function testFrame() + { + $frameSize = 5; + $original = Image::getImagine()->open($this->imageFile); + $originalSize = $original->getSize(); + $img = Image::frame($this->imageFile, $frameSize, '666', 0); + $size = $img->getSize(); + + $this->assertEquals($size->getWidth(), $originalSize->getWidth() + ($frameSize * 2)); + } + + public function testThumbnail() + { + // THUMBNAIL_OUTBOUND mode. + $img = Image::thumbnail($this->imageFile, 120, 120); + + $this->assertEquals(120, $img->getSize()->getWidth()); + $this->assertEquals(120, $img->getSize()->getHeight()); + + // THUMBNAIL_INSET mode. Missing thumbnail part is filled with background so dimensions are exactly + // the ones specified. + $img = Image::thumbnail($this->imageFile, 120, 120, ImageInterface::THUMBNAIL_INSET); + + $this->assertEquals(120, $img->getSize()->getWidth()); + $this->assertEquals(120, $img->getSize()->getHeight()); + + // Height omitted and is calculated based on original image aspect ratio regardless of the mode. + $img = Image::thumbnail($this->imageFile, 120, null); + + $this->assertEquals(120, $img->getSize()->getWidth()); + $this->assertEquals(62, $img->getSize()->getHeight()); + + $img = Image::thumbnail($this->imageFile, 120, null, ImageInterface::THUMBNAIL_INSET); + + $this->assertEquals(120, $img->getSize()->getWidth()); + $this->assertEquals(62, $img->getSize()->getHeight()); + + // Width omitted and is calculated based on original image aspect ratio regardless of the mode. + $img = Image::thumbnail($this->imageFile, null, 120); + + $this->assertEquals(234, $img->getSize()->getWidth()); + $this->assertEquals(120, $img->getSize()->getHeight()); + + $img = Image::thumbnail($this->imageFile, null, 120, ImageInterface::THUMBNAIL_INSET); + + $this->assertEquals(234, $img->getSize()->getWidth()); + $this->assertEquals(120, $img->getSize()->getHeight()); + } + + public function testThumbnailWithUpscaleFlag() + { + // THUMBNAIL_OUTBOUND mode. + $img = Image::thumbnail($this->imageFile, 700, 700, ImageInterface::THUMBNAIL_OUTBOUND | ImageInterface::THUMBNAIL_FLAG_UPSCALE); + + $this->assertEquals(700, $img->getSize()->getWidth()); + $this->assertEquals(700, $img->getSize()->getHeight()); + + // THUMBNAIL_INSET mode. Missing thumbnail part is filled with background so dimensions are exactly + // the ones specified. + $img = Image::thumbnail($this->imageFile, 700, 700, ImageInterface::THUMBNAIL_INSET | ImageInterface::THUMBNAIL_FLAG_UPSCALE); + + $this->assertEquals(700, $img->getSize()->getWidth()); + $this->assertEquals(700, $img->getSize()->getHeight()); + + // Height omitted and is calculated based on original image aspect ratio regardless of the mode. + $img = Image::thumbnail($this->imageFile, 840, null, ImageInterface::THUMBNAIL_OUTBOUND | ImageInterface::THUMBNAIL_FLAG_UPSCALE); + + $this->assertEquals(840, $img->getSize()->getWidth()); + $this->assertEquals(432, $img->getSize()->getHeight()); + + $img = Image::thumbnail($this->imageFile, 840, null, ImageInterface::THUMBNAIL_INSET | ImageInterface::THUMBNAIL_FLAG_UPSCALE); + + $this->assertEquals(840, $img->getSize()->getWidth()); + $this->assertEquals(432, $img->getSize()->getHeight()); + + // Width omitted and is calculated based on original image aspect ratio regardless of the mode. + $img = Image::thumbnail($this->imageFile, null, 540, ImageInterface::THUMBNAIL_OUTBOUND | ImageInterface::THUMBNAIL_FLAG_UPSCALE); + + $this->assertEquals(1050, $img->getSize()->getWidth()); + $this->assertEquals(540, $img->getSize()->getHeight()); + + $img = Image::thumbnail($this->imageFile, null, 540, ImageInterface::THUMBNAIL_INSET | ImageInterface::THUMBNAIL_FLAG_UPSCALE); + + $this->assertEquals(1050, $img->getSize()->getWidth()); + $this->assertEquals(540, $img->getSize()->getHeight()); + } + + /** + * @dataProvider \yiiunit\imagine\providers\Data::resize + */ + public function testResize($width, $height, $keepAspectRatio, $allowUpscaling, $newWidth, $newHeight) + { + $img = Image::resize($this->imageFile, $width, $height, $keepAspectRatio, $allowUpscaling); + + $this->assertEquals($newWidth, $img->getSize()->getWidth()); + $this->assertEquals($newHeight, $img->getSize()->getHeight()); + } + + public function testShouldThrowExceptionOnDriverInvalidArgument() + { + Image::setImagine(null); + Image::$driver = 'fake-driver'; + + $this->expectException(InvalidConfigException::class); + $this->expectExceptionMessage('Unknown driver: fake-driver'); + Image::getImagine(); + } + + public function testIfAutoRotateThrowsException() + { + $img = Image::thumbnail($this->imageFile, 120, 120); + $this->assertInstanceOf('\Imagine\Image\ImageInterface', Image::autorotate($img)); + } + + abstract protected function isFontTestSupported(); +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..2541676 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,15 @@ + + [350, 350, true, false, 350, 180], + 'Height and width set. Image should be resized to exact dimensions.' => + [350, 350, false, false, 350, 350], + 'Height omitted and is calculated based on original image aspect ratio.' => + [350, null, true, false, 350, 180], + 'Width omitted and is calculated based on original image aspect ratio.' => + [null, 180, true, false, 350, 180], + 'Upscaling' => + [800, 800, true, true, 800, 411], + ]; + } +}