mirror of
https://github.com/Intervention/image.git
synced 2025-09-02 18:32:56 +02:00
Resizing
This commit is contained in:
@@ -65,11 +65,6 @@ abstract class AbstractImage
|
|||||||
return new Size($this->width(), $this->height());
|
return new Size($this->width(), $this->height());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getResizer(): Resizer
|
|
||||||
{
|
|
||||||
return new Resizer($this->getSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAnimated(): bool
|
public function isAnimated(): bool
|
||||||
{
|
{
|
||||||
return $this->getFrames()->count() > 1;
|
return $this->getFrames()->count() > 1;
|
||||||
@@ -132,46 +127,66 @@ abstract class AbstractImage
|
|||||||
|
|
||||||
public function resize(...$arguments): ImageInterface
|
public function resize(...$arguments): ImageInterface
|
||||||
{
|
{
|
||||||
$size = $this->getResizer()->setTargetSizeByArray($arguments)->resize();
|
$crop = $this->getSize();
|
||||||
|
$resize = Resizer::make()
|
||||||
|
->setTargetSizeByArray($arguments)
|
||||||
|
->resize($crop);
|
||||||
|
|
||||||
return $this->modify(
|
return $this->modify(
|
||||||
$this->resolveDriverClass('Modifiers\ResizeModifier', $size)
|
$this->resolveDriverClass('Modifiers\CropResizeModifier', $crop, $resize)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resizeDown(...$arguments): ImageInterface
|
public function resizeDown(...$arguments): ImageInterface
|
||||||
{
|
{
|
||||||
$size = $this->getResizer()->setTargetSizeByArray($arguments)->resizeDown();
|
$crop = $this->getSize();
|
||||||
|
$resize = Resizer::make()
|
||||||
|
->setTargetSizeByArray($arguments)
|
||||||
|
->resizeDown($crop);
|
||||||
|
|
||||||
return $this->modify(
|
return $this->modify(
|
||||||
$this->resolveDriverClass('Modifiers\ResizeModifier', $size)
|
$this->resolveDriverClass('Modifiers\CropResizeModifier', $crop, $resize)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scale(...$arguments): ImageInterface
|
public function scale(...$arguments): ImageInterface
|
||||||
{
|
{
|
||||||
$size = $this->getResizer()->setTargetSizeByArray($arguments)->scale();
|
$crop = $this->getSize();
|
||||||
|
$resize = Resizer::make()
|
||||||
|
->setTargetSizeByArray($arguments)
|
||||||
|
->scale($crop);
|
||||||
|
|
||||||
return $this->modify(
|
return $this->modify(
|
||||||
$this->resolveDriverClass('Modifiers\ResizeModifier', $size)
|
$this->resolveDriverClass('Modifiers\CropResizeModifier', $crop, $resize)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scaleDown(...$arguments): ImageInterface
|
public function scaleDown(...$arguments): ImageInterface
|
||||||
{
|
{
|
||||||
$size = $this->getResizer()->setTargetSizeByArray($arguments)->scaleDown();
|
$crop = $this->getSize();
|
||||||
|
$resize = Resizer::make()
|
||||||
|
->setTargetSizeByArray($arguments)
|
||||||
|
->scaleDown($crop);
|
||||||
|
|
||||||
return $this->modify(
|
return $this->modify(
|
||||||
$this->resolveDriverClass('Modifiers\ResizeModifier', $size)
|
$this->resolveDriverClass('Modifiers\CropResizeModifier', $size)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fit(int $width, int $height, string $position = 'center'): ImageInterface
|
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(
|
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);
|
$size = new Size($width, $height);
|
||||||
|
|
||||||
return $this->modify(
|
return $this->modify(
|
||||||
$this->resolveDriverClass('Modifiers\FitDownModifier', $size, $position)
|
$this->resolveDriverClass('Modifiers\CropResizeModifier', $crop, $resize, $position)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,22 +2,29 @@
|
|||||||
|
|
||||||
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
use Intervention\Image\Drivers\Abstract\Modifiers\AbstractResizeModifier;
|
|
||||||
use Intervention\Image\Interfaces\FrameInterface;
|
use Intervention\Image\Interfaces\FrameInterface;
|
||||||
use Intervention\Image\Interfaces\ImageInterface;
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
use Intervention\Image\Interfaces\ModifierInterface;
|
use Intervention\Image\Interfaces\ModifierInterface;
|
||||||
use Intervention\Image\Interfaces\SizeInterface;
|
use Intervention\Image\Interfaces\SizeInterface;
|
||||||
use Intervention\Image\Traits\CanResizeGeometrically;
|
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
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
{
|
{
|
||||||
$crop = $this->getCropSize($image);
|
|
||||||
$resize = $this->getResizeSize($image);
|
|
||||||
|
|
||||||
foreach ($image as $frame) {
|
foreach ($image as $frame) {
|
||||||
$this->modify($frame, $crop, $resize);
|
$this->modify($frame, $this->crop, $this->resize);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $image;
|
return $image;
|
@@ -6,16 +6,8 @@ use Intervention\Image\Interfaces\ImageInterface;
|
|||||||
use Intervention\Image\Interfaces\ModifierInterface;
|
use Intervention\Image\Interfaces\ModifierInterface;
|
||||||
use Intervention\Image\Interfaces\SizeInterface;
|
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
|
protected function getCropSize(ImageInterface $image): SizeInterface
|
||||||
{
|
{
|
||||||
$imagesize = $image->getSize();
|
$imagesize = $image->getSize();
|
||||||
|
@@ -67,6 +67,17 @@ class Resizer
|
|||||||
$this->target = new Size(0, 0);
|
$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
|
protected function hasTargetWidth(): bool
|
||||||
{
|
{
|
||||||
return $this->target->getWidth() > 0;
|
return $this->target->getWidth() > 0;
|
||||||
@@ -245,4 +256,65 @@ class Resizer
|
|||||||
|
|
||||||
return $resized;
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
28
tests/Drivers/Gd/Modifiers/FitModifierTest.php
Normal file
28
tests/Drivers/Gd/Modifiers/FitModifierTest.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Tests\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\Gd\Image;
|
||||||
|
use Intervention\Image\Drivers\Gd\Modifiers\FitModifier;
|
||||||
|
use Intervention\Image\Geometry\Size;
|
||||||
|
use Intervention\Image\Tests\TestCase;
|
||||||
|
use Intervention\Image\Tests\Traits\CanCreateGdTestImage;
|
||||||
|
|
||||||
|
class FitModifierTest extends TestCase
|
||||||
|
{
|
||||||
|
use CanCreateGdTestImage;
|
||||||
|
|
||||||
|
public function testColorChange(): void
|
||||||
|
{
|
||||||
|
$image = $this->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());
|
||||||
|
}
|
||||||
|
}
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Intervention\Image\Tests\Geometry;
|
namespace Intervention\Image\Tests\Geometry;
|
||||||
|
|
||||||
|
use Intervention\Image\Geometry\Point;
|
||||||
use Intervention\Image\Geometry\Resizer;
|
use Intervention\Image\Geometry\Resizer;
|
||||||
use Intervention\Image\Geometry\Size;
|
use Intervention\Image\Geometry\Size;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@@ -367,4 +368,84 @@ class ResizerTest extends TestCase
|
|||||||
$this->assertEquals(13, $result->getWidth());
|
$this->assertEquals(13, $result->getWidth());
|
||||||
$this->assertEquals(10, $result->getHeight());
|
$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))],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user