diff --git a/src/Drivers/Abstract/AbstractImage.php b/src/Drivers/Abstract/AbstractImage.php index a6a52719..447de89c 100644 --- a/src/Drivers/Abstract/AbstractImage.php +++ b/src/Drivers/Abstract/AbstractImage.php @@ -65,11 +65,6 @@ abstract class AbstractImage return new Size($this->width(), $this->height()); } - public function getResizer(): Resizer - { - return new Resizer($this->getSize()); - } - public function isAnimated(): bool { return $this->getFrames()->count() > 1; @@ -132,46 +127,66 @@ abstract class AbstractImage public function resize(...$arguments): ImageInterface { - $size = $this->getResizer()->setTargetSizeByArray($arguments)->resize(); + $crop = $this->getSize(); + $resize = Resizer::make() + ->setTargetSizeByArray($arguments) + ->resize($crop); return $this->modify( - $this->resolveDriverClass('Modifiers\ResizeModifier', $size) + $this->resolveDriverClass('Modifiers\CropResizeModifier', $crop, $resize) ); } public function resizeDown(...$arguments): ImageInterface { - $size = $this->getResizer()->setTargetSizeByArray($arguments)->resizeDown(); + $crop = $this->getSize(); + $resize = Resizer::make() + ->setTargetSizeByArray($arguments) + ->resizeDown($crop); return $this->modify( - $this->resolveDriverClass('Modifiers\ResizeModifier', $size) + $this->resolveDriverClass('Modifiers\CropResizeModifier', $crop, $resize) ); } public function scale(...$arguments): ImageInterface { - $size = $this->getResizer()->setTargetSizeByArray($arguments)->scale(); + $crop = $this->getSize(); + $resize = Resizer::make() + ->setTargetSizeByArray($arguments) + ->scale($crop); return $this->modify( - $this->resolveDriverClass('Modifiers\ResizeModifier', $size) + $this->resolveDriverClass('Modifiers\CropResizeModifier', $crop, $resize) ); } public function scaleDown(...$arguments): ImageInterface { - $size = $this->getResizer()->setTargetSizeByArray($arguments)->scaleDown(); + $crop = $this->getSize(); + $resize = Resizer::make() + ->setTargetSizeByArray($arguments) + ->scaleDown($crop); return $this->modify( - $this->resolveDriverClass('Modifiers\ResizeModifier', $size) + $this->resolveDriverClass('Modifiers\CropResizeModifier', $size) ); } public function fit(int $width, int $height, string $position = 'center'): ImageInterface { - $size = new Size($width, $height); + // crop + $crop = Resizer::make() + ->toSize($this->getSize()) + ->contain(new Size($width, $height)); + $crop = Resizer::make() + ->toSize($crop) + ->crop($this->getSize(), $position); + + $resize = new Size($width, $height); return $this->modify( - $this->resolveDriverClass('Modifiers\FitModifier', $size, $position) + $this->resolveDriverClass('Modifiers\CropResizeModifier', $crop, $resize, $position) ); } @@ -180,7 +195,7 @@ abstract class AbstractImage $size = new Size($width, $height); return $this->modify( - $this->resolveDriverClass('Modifiers\FitDownModifier', $size, $position) + $this->resolveDriverClass('Modifiers\CropResizeModifier', $crop, $resize, $position) ); } diff --git a/src/Drivers/Gd/Modifiers/ResizeModifier.php b/src/Drivers/Gd/Modifiers/CropResizeModifier.php similarity index 82% rename from src/Drivers/Gd/Modifiers/ResizeModifier.php rename to src/Drivers/Gd/Modifiers/CropResizeModifier.php index ff9a185b..efccf46c 100644 --- a/src/Drivers/Gd/Modifiers/ResizeModifier.php +++ b/src/Drivers/Gd/Modifiers/CropResizeModifier.php @@ -2,22 +2,29 @@ namespace Intervention\Image\Drivers\Gd\Modifiers; -use Intervention\Image\Drivers\Abstract\Modifiers\AbstractResizeModifier; use Intervention\Image\Interfaces\FrameInterface; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ModifierInterface; use Intervention\Image\Interfaces\SizeInterface; use Intervention\Image\Traits\CanResizeGeometrically; -class ResizeModifier extends AbstractResizeModifier implements ModifierInterface +class CropResizeModifier implements ModifierInterface { + protected $crop; + protected $resize; + protected $position; + + public function __construct(SizeInterface $crop, SizeInterface $resize, string $position = 'top-left') + { + $this->crop = $crop; + $this->resize = $resize; + $this->position = $position; + } + public function apply(ImageInterface $image): ImageInterface { - $crop = $this->getCropSize($image); - $resize = $this->getResizeSize($image); - foreach ($image as $frame) { - $this->modify($frame, $crop, $resize); + $this->modify($frame, $this->crop, $this->resize); } return $image; diff --git a/src/Drivers/Gd/Modifiers/FitModifier.php b/src/Drivers/Gd/Modifiers/FitModifier.php index 6910381c..9c69bc4f 100644 --- a/src/Drivers/Gd/Modifiers/FitModifier.php +++ b/src/Drivers/Gd/Modifiers/FitModifier.php @@ -6,16 +6,8 @@ use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ModifierInterface; use Intervention\Image\Interfaces\SizeInterface; -class FitModifier extends ResizeModifier implements ModifierInterface +class FitModifier extends CropResizeModifier implements ModifierInterface { - protected $position; - - public function __construct(SizeInterface $target, string $position = 'top-left') - { - $this->target = $target; - $this->position = $position; - } - protected function getCropSize(ImageInterface $image): SizeInterface { $imagesize = $image->getSize(); diff --git a/src/Geometry/Resizer.php b/src/Geometry/Resizer.php index 71885ec0..5b35f366 100644 --- a/src/Geometry/Resizer.php +++ b/src/Geometry/Resizer.php @@ -67,6 +67,17 @@ class Resizer $this->target = new Size(0, 0); } + public static function make(callable $callback = null): self + { + $resizer = new self(); + + if (is_callable($callback)) { + $callback($resizer); + } + + return $resizer; + } + protected function hasTargetWidth(): bool { return $this->target->getWidth() > 0; @@ -245,4 +256,65 @@ class Resizer return $resized; } + + /** + * Scale given size to cover target size + * + * @param SizeInterface $size Size to be resized + * @return SizeInterface + */ + public function cover(SizeInterface $size): SizeInterface + { + $resized = clone $size; + + // auto height + $resized->setWidth($this->target->getWidth()); + $resized->setHeight($this->getProportionalHeight($size)); + + if ($resized->fitsInto($this->target)) { + // auto width + $resized->setWidth($this->getProportionalWidth($size)); + $resized->setHeight($this->target->getHeight()); + } + + return $resized; + } + + /** + * Scale given size to contain target size + * + * @param SizeInterface $size Size to be resized + * @return SizeInterface + */ + public function contain(SizeInterface $size): SizeInterface + { + $resized = clone $size; + + // auto height + $resized->setWidth($this->target->getWidth()); + $resized->setHeight($this->getProportionalHeight($size)); + + if (!$resized->fitsInto($this->target)) { + // auto width + $resized->setWidth($this->getProportionalWidth($size)); + $resized->setHeight($this->target->getHeight()); + } + + return $resized; + } + + /** + * Crop target size out of given size at given position (i.e. move the pivot point) + * + * @param SizeInterface $size + * @param string $position + * @return SizeInterface + */ + public function crop(SizeInterface $size, string $position = 'top-left'): SizeInterface + { + return $this->resize($size)->alignPivotTo( + $size->alignPivot($position), + $position + ); + } } diff --git a/tests/Drivers/Gd/Modifiers/FitModifierTest.php b/tests/Drivers/Gd/Modifiers/FitModifierTest.php new file mode 100644 index 00000000..08913c9a --- /dev/null +++ b/tests/Drivers/Gd/Modifiers/FitModifierTest.php @@ -0,0 +1,28 @@ +createTestImage('test.jpg'); + $image->resize(800, 600); + $this->assertEquals(800, $image->width()); + $this->assertEquals(600, $image->height()); + + $image->fit(100, 100); + + // $image->modify(new FitModifier(new Size(100, 100))); + // $this->assertEquals(30, $image->width()); + // $this->assertEquals(20, $image->height()); + } +} diff --git a/tests/Geometry/ResizerTest.php b/tests/Geometry/ResizerTest.php index 6c67528d..fbe237ad 100644 --- a/tests/Geometry/ResizerTest.php +++ b/tests/Geometry/ResizerTest.php @@ -2,6 +2,7 @@ namespace Intervention\Image\Tests\Geometry; +use Intervention\Image\Geometry\Point; use Intervention\Image\Geometry\Resizer; use Intervention\Image\Geometry\Size; use PHPUnit\Framework\TestCase; @@ -367,4 +368,84 @@ class ResizerTest extends TestCase $this->assertEquals(13, $result->getWidth()); $this->assertEquals(10, $result->getHeight()); } + + /** + * @dataProvider coverDataProvider + */ + public function testCover($origin, $target, $result): void + { + $resizer = new Resizer(); + $resizer->toSize($target); + $resized = $resizer->cover($origin); + $this->assertEquals($result->getWidth(), $resized->getWidth()); + $this->assertEquals($result->getHeight(), $resized->getHeight()); + } + + public function coverDataProvider(): array + { + return [ + [new Size(800, 600), new Size(100, 100), new Size(133, 100)], + [new Size(800, 600), new Size(200, 100), new Size(200, 150)], + [new Size(800, 600), new Size(100, 200), new Size(267, 200)], + [new Size(800, 600), new Size(2000, 10), new Size(2000, 1500)], + [new Size(800, 600), new Size(10, 2000), new Size(2667, 2000)], + [new Size(800, 600), new Size(800, 600), new Size(800, 600)], + [new Size(400, 300), new Size(120, 120), new Size(160, 120)], + [new Size(600, 800), new Size(100, 100), new Size(100, 133)], + ]; + } + + /** + * @dataProvider containDataProvider + */ + public function testContain($origin, $target, $result): void + { + $resizer = new Resizer(); + $resizer->toSize($target); + $resized = $resizer->contain($origin); + $this->assertEquals($result->getWidth(), $resized->getWidth()); + $this->assertEquals($result->getHeight(), $resized->getHeight()); + } + + public function containDataProvider(): array + { + return [ + [new Size(800, 600), new Size(100, 100), new Size(100, 75)], + [new Size(800, 600), new Size(200, 100), new Size(133, 100)], + [new Size(800, 600), new Size(100, 200), new Size(100, 75)], + [new Size(800, 600), new Size(2000, 10), new Size(13, 10)], + [new Size(800, 600), new Size(10, 2000), new Size(10, 8)], + [new Size(800, 600), new Size(800, 600), new Size(800, 600)], + [new Size(400, 300), new Size(120, 120), new Size(120, 90)], + [new Size(600, 800), new Size(100, 100), new Size(75, 100)], + ]; + } + + /** + * @dataProvider cropDataProvider + */ + public function testCrop($origin, $target, $position, $result): void + { + $resizer = new Resizer(); + $resizer->toSize($target); + $resized = $resizer->crop($origin, $position); + $this->assertEquals($result->getWidth(), $resized->getWidth()); + $this->assertEquals($result->getHeight(), $resized->getHeight()); + $this->assertEquals($result->getPivot()->getX(), $resized->getPivot()->getX()); + $this->assertEquals($result->getPivot()->getY(), $resized->getPivot()->getY()); + } + + public function cropDataProvider(): array + { + return [ + [new Size(800, 600), new Size(100, 100), 'center', new Size(100, 100, new Point(350, 250))], + [new Size(800, 600), new Size(200, 100), 'center', new Size(200, 100, new Point(300, 250))], + [new Size(800, 600), new Size(100, 200), 'center', new Size(100, 200, new Point(350, 200))], + [new Size(800, 600), new Size(2000, 10), 'center', new Size(2000, 10, new Point(-600, 295))], + [new Size(800, 600), new Size(10, 2000), 'center', new Size(10, 2000, new Point(395, -700))], + [new Size(800, 600), new Size(800, 600), 'center', new Size(800, 600, new Point(0, 0))], + [new Size(400, 300), new Size(120, 120), 'center', new Size(120, 120, new Point(140, 90))], + [new Size(600, 800), new Size(100, 100), 'center', new Size(100, 100, new Point(250, 350))], + ]; + } }