1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-26 23:35:12 +02:00

Restructure and change pad() & padDown() methods

- padDown() no longer exists
- pad() does not upscale the original image
- new method contain() which does the same as pad() but is able to upscale
This commit is contained in:
Oliver Vogel
2023-12-03 16:05:50 +01:00
parent 987367d430
commit 865adc522d
16 changed files with 270 additions and 238 deletions

View File

@@ -0,0 +1,81 @@
<?php
namespace Intervention\Image\Drivers\Gd\Modifiers;
use Intervention\Image\Drivers\DriverModifier;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\FrameInterface;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SizeInterface;
use Intervention\Image\Modifiers\FillModifier;
/**
* @method SizeInterface getCropSize(ImageInterface $image)
* @method SizeInterface getResizeSize(ImageInterface $image)
* @property int $width
* @property int $height
* @property mixed $background
* @property string $position
*/
class ContainModifier extends DriverModifier
{
public function apply(ImageInterface $image): ImageInterface
{
$crop = $this->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);
}
}

View File

@@ -1,7 +0,0 @@
<?php
namespace Intervention\Image\Drivers\Gd\Modifiers;
class PadDownModifier extends PadModifier
{
}

View File

@@ -2,80 +2,6 @@
namespace Intervention\Image\Drivers\Gd\Modifiers;
use Intervention\Image\Drivers\DriverModifier;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\FrameInterface;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SizeInterface;
use Intervention\Image\Modifiers\FillModifier;
/**
* @method SizeInterface getCropSize(ImageInterface $image)
* @method SizeInterface getResizeSize(ImageInterface $image)
* @property int $width
* @property int $height
* @property mixed $background
* @property string $position
*/
class PadModifier extends DriverModifier
class PadModifier extends ContainModifier
{
public function apply(ImageInterface $image): ImageInterface
{
$crop = $this->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);
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Modifiers;
use ImagickDraw;
use ImagickPixel;
use Intervention\Image\Drivers\DriverModifier;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SizeInterface;
/**
* @method SizeInterface getCropSize(ImageInterface $image)
* @method SizeInterface getResizeSize(ImageInterface $image)
* @property int $width
* @property int $height
* @property mixed $background
* @property string $position
*/
class ContainModifier extends DriverModifier
{
public function apply(ImageInterface $image): ImageInterface
{
$crop = $this->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;
}
}

View File

@@ -1,7 +0,0 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Modifiers;
class PadDownModifier extends PadModifier
{
}

View File

@@ -2,89 +2,6 @@
namespace Intervention\Image\Drivers\Imagick\Modifiers;
use ImagickDraw;
use ImagickPixel;
use Intervention\Image\Drivers\DriverModifier;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SizeInterface;
/**
* @method SizeInterface getCropSize(ImageInterface $image)
* @method SizeInterface getResizeSize(ImageInterface $image)
* @property int $width
* @property int $height
* @property mixed $background
* @property string $position
*/
class PadModifier extends DriverModifier
class PadModifier extends ContainModifier
{
public function apply(ImageInterface $image): ImageInterface
{
$crop = $this->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;
}
}

View File

@@ -223,4 +223,9 @@ class Rectangle extends Polygon implements SizeInterface, DrawableInterface
{
return $this->resizer($width, $height)->contain($this);
}
public function containMax(int $width, int $height): SizeInterface
{
return $this->resizer($width, $height)->containDown($this);
}
}

View File

@@ -73,7 +73,7 @@ class RectangleResizer
protected function getProportionalWidth(SizeInterface $size): int
{
if (! $this->hasTargetHeight()) {
if (!$this->hasTargetHeight()) {
return $size->width();
}
@@ -82,7 +82,7 @@ class RectangleResizer
protected function getProportionalHeight(SizeInterface $size): int
{
if (! $this->hasTargetWidth()) {
if (!$this->hasTargetWidth()) {
return $size->height();
}
@@ -231,6 +231,38 @@ class RectangleResizer
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)
*

View File

@@ -42,6 +42,7 @@ use Intervention\Image\Modifiers\BlurModifier;
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\FlopModifier;
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 @@ final class Image implements ImageInterface, Countable
/**
* {@inheritdoc}
*
* @see ImageInterface::pad()
* @see ImageInterface::padDown()
*/
public function pad(
int $width,
@@ -538,15 +538,15 @@ final class Image implements ImageInterface, Countable
/**
* {@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));
}
/**

View File

@@ -349,10 +349,12 @@ interface ImageInterface extends IteratorAggregate, Countable
/**
* 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 @@ interface ImageInterface extends IteratorAggregate, Countable
): 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 @@ interface ImageInterface extends IteratorAggregate, Countable
* @param string $position
* @return ImageInterface
*/
public function padDown(
public function contain(
int $width,
int $height,
mixed $background = 'ffffff',
@@ -385,8 +387,9 @@ interface ImageInterface extends IteratorAggregate, Countable
): 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

View File

@@ -103,4 +103,5 @@ interface 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;
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Intervention\Image\Modifiers;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SizeInterface;
class ContainModifier extends AbstractModifier
{
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)
->alignPivotTo($this->getResizeSize($image), $this->position);
}
public function getResizeSize(ImageInterface $image): SizeInterface
{
return new Rectangle($this->width, $this->height);
}
}

View File

@@ -1,25 +0,0 @@
<?php
namespace Intervention\Image\Modifiers;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SizeInterface;
class PadDownModifier extends PadModifier
{
public function getCropSize(ImageInterface $image): SizeInterface
{
$resize = $this->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());
}
}

View File

@@ -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);
}
}

View File

@@ -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 @@ class PadModifierTest extends TestCase
$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));

View File

@@ -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 @@ class PadModifierTest extends TestCase
$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));