diff --git a/src/Drivers/Gd/Modifiers/ContainModifier.php b/src/Drivers/Gd/Modifiers/ContainModifier.php new file mode 100644 index 000000000..d3c91df5a --- /dev/null +++ b/src/Drivers/Gd/Modifiers/ContainModifier.php @@ -0,0 +1,81 @@ +getCropSize($image); + $resize = $this->getResizeSize($image); + $background = $this->driver()->handleInput($this->background); + + foreach ($image as $frame) { + $this->modify($frame, $crop, $resize, $background); + } + + return $image; + } + + protected function modify( + FrameInterface $frame, + SizeInterface $crop, + SizeInterface $resize, + ColorInterface $background + ): void { + // create new gd image + $modified = $this->driver()->createImage( + $resize->width(), + $resize->height() + )->modify( + new FillModifier($background) + )->core()->native(); + + // make image area transparent to keep transparency + // even if background-color is set + $transparent = imagecolorallocatealpha($modified, 255, 0, 255, 127); + imagealphablending($modified, false); // do not blend / just overwrite + imagecolortransparent($modified, $transparent); + imagefilledrectangle( + $modified, + $crop->pivot()->x(), + $crop->pivot()->y(), + $crop->pivot()->x() + $crop->width() - 1, + $crop->pivot()->y() + $crop->height() - 1, + $transparent + ); + + // copy image from original with blending alpha + imagealphablending($modified, true); + imagecopyresampled( + $modified, + $frame->native(), + $crop->pivot()->x(), + $crop->pivot()->y(), + 0, + 0, + $crop->width(), + $crop->height(), + $frame->size()->width(), + $frame->size()->height() + ); + + // set new content as recource + $frame->setNative($modified); + } +} diff --git a/src/Drivers/Gd/Modifiers/PadDownModifier.php b/src/Drivers/Gd/Modifiers/PadDownModifier.php deleted file mode 100644 index ce3991aa1..000000000 --- a/src/Drivers/Gd/Modifiers/PadDownModifier.php +++ /dev/null @@ -1,7 +0,0 @@ -getCropSize($image); - $resize = $this->getResizeSize($image); - $background = $this->driver()->handleInput($this->background); - - foreach ($image as $frame) { - $this->modify($frame, $crop, $resize, $background); - } - - return $image; - } - - protected function modify( - FrameInterface $frame, - SizeInterface $crop, - SizeInterface $resize, - ColorInterface $background - ): void { - // create new gd image - $modified = $this->driver()->createImage( - $resize->width(), - $resize->height() - )->modify( - new FillModifier($background) - )->core()->native(); - - // make image area transparent to keep transparency - // even if background-color is set - $transparent = imagecolorallocatealpha($modified, 255, 0, 255, 127); - imagealphablending($modified, false); // do not blend / just overwrite - imagecolortransparent($modified, $transparent); - imagefilledrectangle( - $modified, - $crop->pivot()->x(), - $crop->pivot()->y(), - $crop->pivot()->x() + $crop->width() - 1, - $crop->pivot()->y() + $crop->height() - 1, - $transparent - ); - - // copy image from original with blending alpha - imagealphablending($modified, true); - imagecopyresampled( - $modified, - $frame->native(), - $crop->pivot()->x(), - $crop->pivot()->y(), - 0, - 0, - $crop->width(), - $crop->height(), - $frame->size()->width(), - $frame->size()->height() - ); - - // set new content as recource - $frame->setNative($modified); - } } diff --git a/src/Drivers/Imagick/Modifiers/ContainModifier.php b/src/Drivers/Imagick/Modifiers/ContainModifier.php new file mode 100644 index 000000000..f3687f010 --- /dev/null +++ b/src/Drivers/Imagick/Modifiers/ContainModifier.php @@ -0,0 +1,90 @@ +getCropSize($image); + $resize = $this->getResizeSize($image); + $transparent = new ImagickPixel('transparent'); + $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( + $this->driver()->handleInput($this->background) + ); + + foreach ($image as $frame) { + $frame->native()->scaleImage( + $crop->width(), + $crop->height(), + ); + + $frame->native()->setBackgroundColor($transparent); + $frame->native()->setImageBackgroundColor($transparent); + + $frame->native()->extentImage( + $resize->width(), + $resize->height(), + $crop->pivot()->x() * -1, + $crop->pivot()->y() * -1 + ); + + if ($resize->width() > $crop->width()) { + // fill new emerged background + $draw = new ImagickDraw(); + $draw->setFillColor($background); + $draw->rectangle( + 0, + 0, + $crop->pivot()->x() - 1, + $resize->height() + ); + $frame->native()->drawImage($draw); + $draw->rectangle( + $crop->pivot()->x() + $crop->width(), + 0, + $resize->width(), + $resize->height() + ); + $frame->native()->drawImage($draw); + } + + if ($resize->height() > $crop->height()) { + // fill new emerged background + $draw = new ImagickDraw(); + $draw->setFillColor($background); + $draw->rectangle( + 0, + 0, + $resize->width(), + $crop->pivot()->y() - 1 + ); + $frame->native()->drawImage($draw); + $draw->rectangle( + 0, + $crop->pivot()->y() + $crop->height(), + $resize->width(), + $resize->height() + ); + $frame->native()->drawImage($draw); + } + } + + return $image; + } +} diff --git a/src/Drivers/Imagick/Modifiers/PadDownModifier.php b/src/Drivers/Imagick/Modifiers/PadDownModifier.php deleted file mode 100644 index 4cd35e590..000000000 --- a/src/Drivers/Imagick/Modifiers/PadDownModifier.php +++ /dev/null @@ -1,7 +0,0 @@ -getCropSize($image); - $resize = $this->getResizeSize($image); - $transparent = new ImagickPixel('transparent'); - $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative( - $this->driver()->handleInput($this->background) - ); - - foreach ($image as $frame) { - $frame->native()->scaleImage( - $crop->width(), - $crop->height(), - ); - - $frame->native()->setBackgroundColor($transparent); - $frame->native()->setImageBackgroundColor($transparent); - - $frame->native()->extentImage( - $resize->width(), - $resize->height(), - $crop->pivot()->x() * -1, - $crop->pivot()->y() * -1 - ); - - if ($resize->width() > $crop->width()) { - // fill new emerged background - $draw = new ImagickDraw(); - $draw->setFillColor($background); - $draw->rectangle( - 0, - 0, - $crop->pivot()->x() - 1, - $resize->height() - ); - $frame->native()->drawImage($draw); - $draw->rectangle( - $crop->pivot()->x() + $crop->width(), - 0, - $resize->width(), - $resize->height() - ); - $frame->native()->drawImage($draw); - } - - if ($resize->height() > $crop->height()) { - // fill new emerged background - $draw = new ImagickDraw(); - $draw->setFillColor($background); - $draw->rectangle( - 0, - 0, - $crop->width(), - $crop->pivot()->y() - 1 - ); - $frame->native()->drawImage($draw); - $draw->rectangle( - 0, - $crop->pivot()->y() + $crop->height(), - $resize->width(), - $resize->height() - ); - $frame->native()->drawImage($draw); - } - } - - return $image; - } } diff --git a/src/Geometry/Rectangle.php b/src/Geometry/Rectangle.php index f4b59df84..1fc3f1407 100644 --- a/src/Geometry/Rectangle.php +++ b/src/Geometry/Rectangle.php @@ -223,4 +223,9 @@ public function contain(int $width, int $height): SizeInterface { return $this->resizer($width, $height)->contain($this); } + + public function containMax(int $width, int $height): SizeInterface + { + return $this->resizer($width, $height)->containDown($this); + } } diff --git a/src/Geometry/Tools/RectangleResizer.php b/src/Geometry/Tools/RectangleResizer.php index 20a0b2cc2..38292da14 100644 --- a/src/Geometry/Tools/RectangleResizer.php +++ b/src/Geometry/Tools/RectangleResizer.php @@ -73,7 +73,7 @@ public function toSize(SizeInterface $size): self protected function getProportionalWidth(SizeInterface $size): int { - if (! $this->hasTargetHeight()) { + if (!$this->hasTargetHeight()) { return $size->width(); } @@ -82,7 +82,7 @@ protected function getProportionalWidth(SizeInterface $size): int protected function getProportionalHeight(SizeInterface $size): int { - if (! $this->hasTargetWidth()) { + if (!$this->hasTargetWidth()) { return $size->height(); } @@ -231,6 +231,38 @@ public function contain(SizeInterface $size): SizeInterface return $resized; } + /** + * Scale given size to contain target size but prevent upsizing + * + * @param SizeInterface $size Size to be resized + * @return SizeInterface + */ + public function containDown(SizeInterface $size): SizeInterface + { + $resized = new Rectangle($size->width(), $size->height()); + + // auto height + $resized->setWidth( + min($size->width(), $this->getTargetWidth()) + ); + + $resized->setHeight( + min($size->height(), $this->getProportionalHeight($size)) + ); + + if (!$resized->fitsInto($this->getTargetSize())) { + // auto width + $resized->setWidth( + min($size->width(), $this->getProportionalWidth($size)) + ); + $resized->setHeight( + min($size->height(), $this->getTargetHeight()) + ); + } + + return $resized; + } + /** * Crop target size out of given size at given position (i.e. move the pivot point) * diff --git a/src/Image.php b/src/Image.php index 77c26d093..d50a8307f 100644 --- a/src/Image.php +++ b/src/Image.php @@ -42,6 +42,7 @@ use Intervention\Image\Modifiers\BrightnessModifier; use Intervention\Image\Modifiers\ColorizeModifier; use Intervention\Image\Modifiers\ColorspaceModifier; +use Intervention\Image\Modifiers\ContainModifier; use Intervention\Image\Modifiers\ContrastModifier; use Intervention\Image\Modifiers\CropModifier; use Intervention\Image\Modifiers\DrawEllipseModifier; @@ -57,7 +58,6 @@ use Intervention\Image\Modifiers\GammaModifier; use Intervention\Image\Modifiers\GreyscaleModifier; use Intervention\Image\Modifiers\InvertModifier; -use Intervention\Image\Modifiers\PadDownModifier; use Intervention\Image\Modifiers\PadModifier; use Intervention\Image\Modifiers\PixelateModifier; use Intervention\Image\Modifiers\PlaceModifier; @@ -524,7 +524,7 @@ public function fitDown(int $width, int $height, string $position = 'center'): I /** * {@inheritdoc} * - * @see ImageInterface::pad() + * @see ImageInterface::padDown() */ public function pad( int $width, @@ -538,15 +538,15 @@ public function pad( /** * {@inheritdoc} * - * @see ImageInterface::padDown() + * @see ImageInterface::pad() */ - public function padDown( + public function contain( int $width, int $height, mixed $background = 'ffffff', string $position = 'center' ): ImageInterface { - return $this->modify(new PadDownModifier($width, $height, $background, $position)); + return $this->modify(new ContainModifier($width, $height, $background, $position)); } /** diff --git a/src/Interfaces/ImageInterface.php b/src/Interfaces/ImageInterface.php index e25f37117..e7cd72121 100644 --- a/src/Interfaces/ImageInterface.php +++ b/src/Interfaces/ImageInterface.php @@ -349,10 +349,12 @@ public function fitDown(int $width, int $height, string $position = 'center'): I /** * Padded resizing means that the original image is scaled until it fits the - * defined target size with unchanged aspect ratio. Compared to the fit() - * method, this call does not create cropped areas, but new empty areas - * on the sides of the result image. These are filled with the specified - * background color. + * defined target size with unchanged aspect ratio. The original image is + * not scaled up but only down. + * + * Compared to the fit() method, this method does not create cropped areas, + * but possibly new empty areas on the sides of the result image. These are + * filled with the specified background color. * * @param int $width * @param int $height @@ -368,8 +370,8 @@ public function pad( ): ImageInterface; /** - * This method does the same thing as pad() but does not exceed the size of - * the original image. You can use this if you want to prevent up-sampling. + * This method does the same as pad(), but the original image is also scaled + * up if the target size exceeds the original size. * * @param int $width * @param int $height @@ -377,7 +379,7 @@ public function pad( * @param string $position * @return ImageInterface */ - public function padDown( + public function contain( int $width, int $height, mixed $background = 'ffffff', @@ -385,8 +387,9 @@ public function padDown( ): ImageInterface; /** - * Cut out a rectangular part of the current image with given width and height at a given position. - * Define optional x,y offset coordinates to move the cutout by the given amount of pixels. + * Cut out a rectangular part of the current image with given width and + * height at a given position. Define optional x,y offset coordinates + * to move the cutout by the given amount of pixels. * * @param int $width * @param int $height diff --git a/src/Interfaces/SizeInterface.php b/src/Interfaces/SizeInterface.php index 4777aaea8..38c586d64 100644 --- a/src/Interfaces/SizeInterface.php +++ b/src/Interfaces/SizeInterface.php @@ -103,4 +103,5 @@ public function scale(?int $width = null, ?int $height = null): SizeInterface; public function scaleDown(?int $width = null, ?int $height = null): SizeInterface; public function cover(int $width, int $height): SizeInterface; public function contain(int $width, int $height): SizeInterface; + public function containMax(int $width, int $height): SizeInterface; } diff --git a/src/Modifiers/ContainModifier.php b/src/Modifiers/ContainModifier.php new file mode 100644 index 000000000..ce98dafad --- /dev/null +++ b/src/Modifiers/ContainModifier.php @@ -0,0 +1,30 @@ +size() + ->contain($this->width, $this->height) + ->alignPivotTo($this->getResizeSize($image), $this->position); + } + + public function getResizeSize(ImageInterface $image): SizeInterface + { + return new Rectangle($this->width, $this->height); + } +} diff --git a/src/Modifiers/PadDownModifier.php b/src/Modifiers/PadDownModifier.php deleted file mode 100644 index a97b10318..000000000 --- a/src/Modifiers/PadDownModifier.php +++ /dev/null @@ -1,25 +0,0 @@ -getResizeSize($image); - - return $image->size() - ->contain($resize->width(), $resize->height()) - ->alignPivotTo($resize, $this->position); - } - - public function getResizeSize(ImageInterface $image): SizeInterface - { - return (new Rectangle($this->width, $this->height)) - ->resizeDown($image->width(), $image->height()); - } -} diff --git a/src/Modifiers/PadModifier.php b/src/Modifiers/PadModifier.php index abc08d7a2..7cf1f9866 100644 --- a/src/Modifiers/PadModifier.php +++ b/src/Modifiers/PadModifier.php @@ -2,29 +2,15 @@ namespace Intervention\Image\Modifiers; -use Intervention\Image\Geometry\Rectangle; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\SizeInterface; -class PadModifier extends AbstractModifier +class PadModifier extends ContainModifier { - public function __construct( - public int $width, - public int $height, - public mixed $background = 'ffffff', - public string $position = 'center' - ) { - } - public function getCropSize(ImageInterface $image): SizeInterface { return $image->size() - ->contain($this->width, $this->height) + ->containMax($this->width, $this->height) ->alignPivotTo($this->getResizeSize($image), $this->position); } - - public function getResizeSize(ImageInterface $image): SizeInterface - { - return new Rectangle($this->width, $this->height); - } } diff --git a/tests/Drivers/Gd/Modifiers/PadModifierTest.php b/tests/Drivers/Gd/Modifiers/PadUpModifierTest.php similarity index 78% rename from tests/Drivers/Gd/Modifiers/PadModifierTest.php rename to tests/Drivers/Gd/Modifiers/PadUpModifierTest.php index 95dbe1c19..7a29aae21 100644 --- a/tests/Drivers/Gd/Modifiers/PadModifierTest.php +++ b/tests/Drivers/Gd/Modifiers/PadUpModifierTest.php @@ -2,15 +2,15 @@ namespace Intervention\Image\Tests\Drivers\Gd\Modifiers; -use Intervention\Image\Modifiers\PadModifier; +use Intervention\Image\Modifiers\ContainModifier; use Intervention\Image\Tests\TestCase; use Intervention\Image\Tests\Traits\CanCreateGdTestImage; /** * @requires extension imagick - * @covers \Intervention\Image\Modifiers\PadModifier + * @covers \Intervention\Image\Modifiers\ContainModifier */ -class PadModifierTest extends TestCase +class ContainModifierTest extends TestCase { use CanCreateGdTestImage; @@ -19,7 +19,7 @@ public function testModify(): void $image = $this->createTestImage('blocks.png'); $this->assertEquals(640, $image->width()); $this->assertEquals(480, $image->height()); - $image->modify(new PadModifier(200, 100, 'ff0')); + $image->modify(new ContainModifier(200, 100, 'ff0')); $this->assertEquals(200, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0)); diff --git a/tests/Drivers/Imagick/Modifiers/PadModifierTest.php b/tests/Drivers/Imagick/Modifiers/PadUpModifierTest.php similarity index 83% rename from tests/Drivers/Imagick/Modifiers/PadModifierTest.php rename to tests/Drivers/Imagick/Modifiers/PadUpModifierTest.php index 5cc59a701..ea069f3b0 100644 --- a/tests/Drivers/Imagick/Modifiers/PadModifierTest.php +++ b/tests/Drivers/Imagick/Modifiers/PadUpModifierTest.php @@ -2,15 +2,15 @@ namespace Intervention\Image\Tests\Drivers\Imagick\Modifiers; -use Intervention\Image\Modifiers\PadModifier; +use Intervention\Image\Modifiers\ContainModifier; use Intervention\Image\Tests\TestCase; use Intervention\Image\Tests\Traits\CanCreateImagickTestImage; /** * @requires extension imagick - * @covers \Intervention\Image\Modifiers\PadModifier + * @covers \Intervention\Image\Modifiers\ContainModifier */ -class PadModifierTest extends TestCase +class ContainModifierTest extends TestCase { use CanCreateImagickTestImage; @@ -19,7 +19,7 @@ public function testModify(): void $image = $this->createTestImage('blocks.png'); $this->assertEquals(640, $image->width()); $this->assertEquals(480, $image->height()); - $result = $image->modify(new PadModifier(200, 100, 'ff0')); + $result = $image->modify(new ContainModifier(200, 100, 'ff0')); $this->assertEquals(200, $image->width()); $this->assertEquals(100, $image->height()); $this->assertColor(255, 255, 0, 255, $image->pickColor(0, 0));