1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-26 07:14:31 +02:00

Implement Alignment enum

This commit is contained in:
Oliver Vogel
2025-06-22 09:20:34 +02:00
parent 00e7956d21
commit 7c62d9847f
21 changed files with 221 additions and 154 deletions

88
src/Alignment.php Normal file
View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Intervention\Image;
use Error;
use Intervention\Image\Exceptions\RuntimeException;
enum Alignment: string
{
case TOP = 'top';
case TOP_RIGHT = 'top-right';
case RIGHT = 'right';
case BOTTOM_RIGHT = 'bottom-right';
case BOTTOM = 'bottom';
case BOTTOM_LEFT = 'bottom-left';
case LEFT = 'left';
case TOP_LEFT = 'top-left';
case CENTER = 'center';
/**
* Create position from given identifier
*
* @throws RuntimeException
*/
public static function create(string|self $identifier): self
{
if ($identifier instanceof self) {
return $identifier;
}
try {
$position = self::from(strtolower($identifier));
} catch (Error) {
$position = match (strtolower($identifier)) {
'top-center',
'center-top',
'top-middle',
'middle-top' => self::TOP,
'right-top' => self::TOP_RIGHT,
'right-center',
'center-right',
'right-middle',
'middle-right' => self::RIGHT,
'right-bottom' => self::BOTTOM_RIGHT,
'bottom-center',
'center-bottom',
'bottom-middle',
'middle-bottom' => self::BOTTOM,
'left-bottom' => self::BOTTOM_LEFT,
'left-center',
'center-left',
'left-middle',
'middle-left' => self::LEFT,
'left-top' => self::TOP_LEFT,
'middle',
'center-center',
'center-middle',
'middle-center' => self::CENTER,
default => throw new RuntimeException('Unable to create alignment from "' . $identifier . '".'),
};
}
return $position;
}
/**
* Try to create position from given identifier or return null on failure
*/
public static function tryCreate(string|self $identifier): ?self
{
try {
return self::create($identifier);
} catch (RuntimeException) {
return null;
}
}
}

View File

@@ -96,7 +96,7 @@ abstract class AbstractDecoder implements DecoderInterface
$result = preg_match($pattern, (string) $input, $matches);
return new class($matches, $result)
return new class ($matches, $result)
{
/**
* @param array<mixed> $matches

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Intervention\Image\Drivers;
use Intervention\Image\Exceptions\FontException;
use Intervention\Image\Exceptions\RuntimeException;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\FontInterface;
@@ -19,6 +20,8 @@ abstract class AbstractFontProcessor implements FontProcessorInterface
* {@inheritdoc}
*
* @see FontProcessorInterface::textBlock()
*
* @throws RuntimeException
*/
public function textBlock(string $text, FontInterface $font, PointInterface $position): TextBlock
{
@@ -150,6 +153,7 @@ abstract class AbstractFontProcessor implements FontProcessorInterface
* Build pivot point of textblock according to the font settings and based on given position
*
* @throws FontException
* @throws RuntimeException
*/
protected function buildPivot(TextBlock $block, FontInterface $font, PointInterface $position): PointInterface
{

View File

@@ -18,7 +18,7 @@ class PadModifier extends ContainModifier
)
->alignPivotTo(
$this->getResizeSize($image),
$this->position
$this->alignment
);
}
}

View File

@@ -21,7 +21,7 @@ class PlaceModifier extends GenericPlaceModifier implements SpecializedInterface
public function apply(ImageInterface $image): ImageInterface
{
$watermark = $this->driver()->handleInput($this->element);
$position = $this->getPosition($image, $watermark);
$position = $this->position($image, $watermark);
foreach ($image as $frame) {
imagealphablending($frame->native(), true);

View File

@@ -9,6 +9,7 @@ use Intervention\Image\Colors\Rgb\Channels\Green;
use Intervention\Image\Colors\Rgb\Channels\Red;
use Intervention\Image\Drivers\Gd\Cloner;
use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Exceptions\RuntimeException;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\FrameInterface;
@@ -39,6 +40,7 @@ class RotateModifier extends GenericRotateModifier implements SpecializedInterfa
* color is used for newly create image areas
*
* @throws ColorException
* @throws RuntimeException
*/
protected function modifyFrame(FrameInterface $frame, ColorInterface $background): void
{

View File

@@ -18,7 +18,7 @@ class PadModifier extends ContainModifier
)
->alignPivotTo(
$this->getResizeSize($image),
$this->position
$this->alignment
);
}
}

View File

@@ -14,7 +14,7 @@ class PlaceModifier extends GenericPlaceModifier implements SpecializedInterface
public function apply(ImageInterface $image): ImageInterface
{
$watermark = $this->driver()->handleInput($this->element);
$position = $this->getPosition($image, $watermark);
$position = $this->position($image, $watermark);
// set opacity of watermark
if ($this->opacity < 100) {

View File

@@ -7,6 +7,8 @@ namespace Intervention\Image\Geometry;
use ArrayAccess;
use ArrayIterator;
use Countable;
use Intervention\Image\Alignment;
use Intervention\Image\Exceptions\RuntimeException;
use Traversable;
use IteratorAggregate;
use Intervention\Image\Geometry\Traits\HasBackgroundColor;
@@ -257,24 +259,19 @@ class Polygon implements IteratorAggregate, Countable, ArrayAccess, DrawableInte
/**
* Align all points of polygon horizontally to given position around pivot point
*
* @throws RuntimeException
*/
public function align(string $position): self
public function align(string|Alignment $position): self
{
switch (strtolower($position)) {
case 'center':
case 'middle':
$diff = $this->centerPoint()->x() - $this->pivot()->x();
break;
case 'right':
$diff = $this->mostRightPoint()->x() - $this->pivot()->x();
break;
default:
case 'left':
$diff = $this->mostLeftPoint()->x() - $this->pivot()->x();
break;
}
$diff = match (Alignment::create($position)) {
Alignment::CENTER => $this->centerPoint()->x() - $this->pivot()->x(),
Alignment::RIGHT => $this->mostRightPoint()->x() - $this->pivot()->x(),
Alignment::LEFT => $this->mostLeftPoint()->x() - $this->pivot()->x(),
default => throw new RuntimeException(
'Only use horizontal alignment values (Alignment::CENTER, Alignment::RIGHT or Alignment::LEFT).',
),
};
foreach ($this->points as $point) {
$point->setX(
@@ -287,24 +284,19 @@ class Polygon implements IteratorAggregate, Countable, ArrayAccess, DrawableInte
/**
* Align all points of polygon vertically to given position around pivot point
*
* @throws RuntimeException
*/
public function valign(string $position): self
public function valign(string|Alignment $position): self
{
switch (strtolower($position)) {
case 'center':
case 'middle':
$diff = $this->centerPoint()->y() - $this->pivot()->y();
break;
case 'top':
$diff = $this->mostTopPoint()->y() - $this->pivot()->y() - $this->height();
break;
default:
case 'bottom':
$diff = $this->mostBottomPoint()->y() - $this->pivot()->y() + $this->height();
break;
}
$diff = match (Alignment::create($position)) {
Alignment::CENTER => $this->centerPoint()->y() - $this->pivot()->y(),
Alignment::TOP => $this->mostTopPoint()->y() - $this->pivot()->y() - $this->height(),
Alignment::BOTTOM => $this->mostBottomPoint()->y() - $this->pivot()->y() + $this->height(),
default => throw new RuntimeException(
'Only use vertical alignment values (Alignment::CENTER, Alignment::TOP or Alignment::BOTTOM).',
),
};
foreach ($this->points as $point) {
$point->setY(

View File

@@ -8,6 +8,8 @@ use Intervention\Image\Exceptions\GeometryException;
use Intervention\Image\Geometry\Tools\RectangleResizer;
use Intervention\Image\Interfaces\PointInterface;
use Intervention\Image\Interfaces\SizeInterface;
use Intervention\Image\Alignment;
use Intervention\Image\Exceptions\RuntimeException;
class Rectangle extends Polygon implements SizeInterface
{
@@ -78,89 +80,61 @@ class Rectangle extends Polygon implements SizeInterface
/**
* Move pivot to the given position in the rectangle and adjust the new
* position by given offset values.
*
* @throws RuntimeException
*/
public function movePivot(string $position, int $offset_x = 0, int $offset_y = 0): self
public function movePivot(string|Alignment $position, int $offset_x = 0, int $offset_y = 0): self
{
switch (strtolower($position)) {
case 'top':
case 'top-center':
case 'top-middle':
case 'center-top':
case 'middle-top':
$x = intval(round($this->width() / 2)) + $offset_x;
$y = $offset_y;
break;
$point = match (Alignment::create($position)) {
Alignment::TOP => new Point(
intval(round($this->width() / 2)) + $offset_x,
$offset_y,
),
Alignment::TOP_RIGHT => new Point(
$this->width() - $offset_x,
$offset_y,
),
Alignment::LEFT => new Point(
$offset_x,
intval(round($this->height() / 2)) + $offset_y,
),
Alignment::RIGHT => new Point(
$this->width() - $offset_x,
intval(round($this->height() / 2)) + $offset_y,
),
Alignment::BOTTOM_LEFT => new Point(
$offset_x,
$this->height() - $offset_y,
),
Alignment::BOTTOM => new Point(
intval(round($this->width() / 2)) + $offset_x,
$this->height() - $offset_y,
),
Alignment::BOTTOM_RIGHT => new Point(
$this->width() - $offset_x,
$this->height() - $offset_y,
),
Alignment::CENTER => new Point(
intval(round($this->width() / 2)) + $offset_x,
intval(round($this->height() / 2)) + $offset_y,
),
Alignment::TOP_LEFT => new Point(
$offset_x,
$offset_y,
),
};
case 'top-right':
case 'right-top':
$x = $this->width() - $offset_x;
$y = $offset_y;
break;
case 'left':
case 'left-center':
case 'left-middle':
case 'center-left':
case 'middle-left':
$x = $offset_x;
$y = intval(round($this->height() / 2)) + $offset_y;
break;
case 'right':
case 'right-center':
case 'right-middle':
case 'center-right':
case 'middle-right':
$x = $this->width() - $offset_x;
$y = intval(round($this->height() / 2)) + $offset_y;
break;
case 'bottom-left':
case 'left-bottom':
$x = $offset_x;
$y = $this->height() - $offset_y;
break;
case 'bottom':
case 'bottom-center':
case 'bottom-middle':
case 'center-bottom':
case 'middle-bottom':
$x = intval(round($this->width() / 2)) + $offset_x;
$y = $this->height() - $offset_y;
break;
case 'bottom-right':
case 'right-bottom':
$x = $this->width() - $offset_x;
$y = $this->height() - $offset_y;
break;
case 'center':
case 'middle':
case 'center-center':
case 'middle-middle':
$x = intval(round($this->width() / 2)) + $offset_x;
$y = intval(round($this->height() / 2)) + $offset_y;
break;
default:
case 'top-left':
case 'left-top':
$x = $offset_x;
$y = $offset_y;
break;
}
$this->pivot->setPosition($x, $y);
$this->pivot->setPosition(...$point);
return $this;
}
/**
* Align pivot relative to given size at given position
*
* @throws RuntimeException
*/
public function alignPivotTo(SizeInterface $size, string $position): self
public function alignPivotTo(SizeInterface $size, string|Alignment $position): self
{
$reference = new self($size->width(), $size->height());
$reference->movePivot($position);

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Intervention\Image\Geometry\Tools;
use Intervention\Image\Alignment;
use Intervention\Image\Exceptions\GeometryException;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\SizeInterface;
@@ -331,7 +332,7 @@ class RectangleResizer
/**
* Crop target size out of given size at given position (i.e. move the pivot point)
*/
public function crop(SizeInterface $size, string $position = 'top-left'): SizeInterface
public function crop(SizeInterface $size, string|Alignment $position = Alignment::TOP_LEFT): SizeInterface
{
return $this->resize($size)->alignPivotTo(
$size->movePivot($position),

View File

@@ -667,9 +667,9 @@ final class Image implements ImageInterface
*
* @see ImageInterface::cover()
*/
public function cover(int $width, int $height, string $position = 'center'): ImageInterface
public function cover(int $width, int $height, string|Alignment $alignment = Alignment::CENTER): ImageInterface
{
return $this->modify(new CoverModifier($width, $height, $position));
return $this->modify(new CoverModifier($width, $height, $alignment));
}
/**
@@ -677,9 +677,9 @@ final class Image implements ImageInterface
*
* @see ImageInterface::coverDown()
*/
public function coverDown(int $width, int $height, string $position = 'center'): ImageInterface
public function coverDown(int $width, int $height, string|Alignment $alignment = Alignment::CENTER): ImageInterface
{
return $this->modify(new CoverDownModifier($width, $height, $position));
return $this->modify(new CoverDownModifier($width, $height, $alignment));
}
/**
@@ -691,9 +691,9 @@ final class Image implements ImageInterface
?int $width = null,
?int $height = null,
mixed $background = null,
string $position = 'center'
string|Alignment $alignment = Alignment::CENTER
): ImageInterface {
return $this->modify(new ResizeCanvasModifier($width, $height, $background, $position));
return $this->modify(new ResizeCanvasModifier($width, $height, $background, $alignment));
}
/**
@@ -705,9 +705,9 @@ final class Image implements ImageInterface
?int $width = null,
?int $height = null,
mixed $background = null,
string $position = 'center'
string|Alignment $alignment = Alignment::CENTER
): ImageInterface {
return $this->modify(new ResizeCanvasRelativeModifier($width, $height, $background, $position));
return $this->modify(new ResizeCanvasRelativeModifier($width, $height, $background, $alignment));
}
/**
@@ -719,9 +719,9 @@ final class Image implements ImageInterface
int $width,
int $height,
mixed $background = null,
string $position = 'center'
string|Alignment $alignment = Alignment::CENTER
): ImageInterface {
return $this->modify(new PadModifier($width, $height, $background, $position));
return $this->modify(new PadModifier($width, $height, $background, $alignment));
}
/**
@@ -733,9 +733,9 @@ final class Image implements ImageInterface
int $width,
int $height,
mixed $background = null,
string $position = 'center'
string|Alignment $alignment = Alignment::CENTER
): ImageInterface {
return $this->modify(new ContainModifier($width, $height, $background, $position));
return $this->modify(new ContainModifier($width, $height, $background, $alignment));
}
/**
@@ -749,9 +749,9 @@ final class Image implements ImageInterface
int $offset_x = 0,
int $offset_y = 0,
mixed $background = null,
string $position = 'top-left'
string|Alignment $alignment = Alignment::TOP_LEFT
): ImageInterface {
return $this->modify(new CropModifier($width, $height, $offset_x, $offset_y, $background, $position));
return $this->modify(new CropModifier($width, $height, $offset_x, $offset_y, $background, $alignment));
}
/**
@@ -771,12 +771,12 @@ final class Image implements ImageInterface
*/
public function place(
mixed $element,
string $position = 'top-left',
string|Alignment $alignment = Alignment::TOP_LEFT,
int $offset_x = 0,
int $offset_y = 0,
int $opacity = 100
): ImageInterface {
return $this->modify(new PlaceModifier($element, $position, $offset_x, $offset_y, $opacity));
return $this->modify(new PlaceModifier($element, $alignment, $offset_x, $offset_y, $opacity));
}
/**

View File

@@ -18,6 +18,7 @@ use Intervention\Image\Geometry\Polygon;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\MediaType;
use Intervention\Image\Origin;
use Intervention\Image\Alignment;
use IteratorAggregate;
/**
@@ -453,7 +454,7 @@ interface ImageInterface extends IteratorAggregate, Countable
*
* @throws RuntimeException
*/
public function cover(int $width, int $height, string $position = 'center'): self;
public function cover(int $width, int $height, string|Alignment $alignment = Alignment::CENTER): self;
/**
* Same as cover() but do not exceed the original image size
@@ -462,7 +463,7 @@ interface ImageInterface extends IteratorAggregate, Countable
*
* @throws RuntimeException
*/
public function coverDown(int $width, int $height, string $position = 'center'): self;
public function coverDown(int $width, int $height, string|Alignment $alignment = Alignment::CENTER): self;
/**
* Resize the boundaries of the current image to given width and height.
@@ -478,7 +479,7 @@ interface ImageInterface extends IteratorAggregate, Countable
?int $width = null,
?int $height = null,
mixed $background = null,
string $position = 'center'
string|Alignment $alignment = Alignment::CENTER
): self;
/**
@@ -494,7 +495,7 @@ interface ImageInterface extends IteratorAggregate, Countable
?int $width = null,
?int $height = null,
mixed $background = null,
string $position = 'center'
string|Alignment $alignment = Alignment::CENTER
): self;
/**
@@ -515,7 +516,7 @@ interface ImageInterface extends IteratorAggregate, Countable
int $width,
int $height,
mixed $background = null,
string $position = 'center'
string|Alignment $alignment = Alignment::CENTER
): self;
/**
@@ -531,7 +532,7 @@ interface ImageInterface extends IteratorAggregate, Countable
int $width,
int $height,
mixed $background = null,
string $position = 'center'
string|Alignment $alignment = Alignment::CENTER
): self;
/**
@@ -549,7 +550,7 @@ interface ImageInterface extends IteratorAggregate, Countable
int $offset_x = 0,
int $offset_y = 0,
mixed $background = null,
string $position = 'top-left'
string|Alignment $alignment = Alignment::TOP_LEFT
): self;
/**
@@ -571,7 +572,7 @@ interface ImageInterface extends IteratorAggregate, Countable
*/
public function place(
mixed $element,
string $position = 'top-left',
string|Alignment $alignment = Alignment::TOP_LEFT,
int $offset_x = 0,
int $offset_y = 0,
int $opacity = 100

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Intervention\Image\Interfaces;
use Intervention\Image\Exceptions\GeometryException;
use Intervention\Image\Alignment;
interface SizeInterface
{
@@ -61,12 +62,12 @@ interface SizeInterface
/**
* Move pivot to given position in size
*/
public function movePivot(string $position, int $offset_x = 0, int $offset_y = 0): self;
public function movePivot(string|Alignment $position, int $offset_x = 0, int $offset_y = 0): self;
/**
* Align pivot of current object to given position
*/
public function alignPivotTo(self $size, string $position): self;
public function alignPivotTo(self $size, string|Alignment $position): self;
/**
* Calculate the relative position to another Size

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Intervention\Image\Modifiers;
use Intervention\Image\Alignment;
use Intervention\Image\Drivers\SpecializableModifier;
use Intervention\Image\Exceptions\RuntimeException;
use Intervention\Image\Geometry\Rectangle;
@@ -17,7 +18,7 @@ class ContainModifier extends SpecializableModifier
public int $width,
public int $height,
public mixed $background = null,
public string $position = 'center'
public string|Alignment $alignment = Alignment::CENTER
) {
//
}
@@ -34,7 +35,7 @@ class ContainModifier extends SpecializableModifier
)
->alignPivotTo(
$this->getResizeSize($image),
$this->position
$this->alignment
);
}

View File

@@ -9,18 +9,17 @@ use Intervention\Image\Exceptions\RuntimeException;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SizeInterface;
use Intervention\Image\Alignment;
class CoverModifier extends SpecializableModifier
{
/**
* Create new modifier object
*
* @return void
*/
public function __construct(
public int $width,
public int $height,
public string $position = 'center'
public string|Alignment $alignment = Alignment::CENTER
) {
//
}
@@ -36,7 +35,7 @@ class CoverModifier extends SpecializableModifier
return $crop->contain(
$imagesize->width(),
$imagesize->height()
)->alignPivotTo($imagesize, $this->position);
)->alignPivotTo($imagesize, $this->alignment);
}
/**

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Intervention\Image\Modifiers;
use Intervention\Image\Alignment;
use Intervention\Image\Drivers\SpecializableModifier;
use Intervention\Image\Exceptions\RuntimeException;
use Intervention\Image\Geometry\Rectangle;
@@ -24,7 +25,7 @@ class CropModifier extends SpecializableModifier
public int $offset_x = 0,
public int $offset_y = 0,
public mixed $background = null,
public string $position = 'top-left'
public string|Alignment $alignment = Alignment::TOP_LEFT
) {
//
}
@@ -35,11 +36,11 @@ class CropModifier extends SpecializableModifier
public function crop(ImageInterface $image): SizeInterface
{
$crop = new Rectangle($this->width, $this->height);
$crop->align($this->position);
$crop->movePivot($this->alignment);
return $crop->alignPivotTo(
$image->size(),
$this->position
$this->alignment
);
}

View File

@@ -18,7 +18,7 @@ class PadModifier extends ContainModifier
)
->alignPivotTo(
$this->getResizeSize($image),
$this->position
$this->alignment
);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Intervention\Image\Modifiers;
use Intervention\Image\Alignment;
use Intervention\Image\Drivers\SpecializableModifier;
use Intervention\Image\Exceptions\RuntimeException;
use Intervention\Image\Interfaces\ImageInterface;
@@ -18,7 +19,7 @@ class PlaceModifier extends SpecializableModifier
*/
public function __construct(
public mixed $element,
public string $position = 'top-left',
public string|Alignment $alignment = Alignment::TOP_LEFT,
public int $offset_x = 0,
public int $offset_y = 0,
public int $opacity = 100
@@ -29,16 +30,16 @@ class PlaceModifier extends SpecializableModifier
/**
* @throws RuntimeException
*/
public function getPosition(ImageInterface $image, ImageInterface $watermark): PointInterface
public function position(ImageInterface $image, ImageInterface $watermark): PointInterface
{
$image_size = $image->size()->movePivot(
$this->position,
$this->alignment,
$this->offset_x,
$this->offset_y
);
$watermark_size = $watermark->size()->movePivot(
$this->position
$this->alignment
);
return $image_size->relativePositionTo($watermark_size);

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Intervention\Image\Modifiers;
use Intervention\Image\Alignment;
use Intervention\Image\Drivers\SpecializableModifier;
use Intervention\Image\Exceptions\RuntimeException;
use Intervention\Image\Geometry\Rectangle;
@@ -22,7 +23,7 @@ class ResizeCanvasModifier extends SpecializableModifier
public ?int $width = null,
public ?int $height = null,
public mixed $background = null,
public string $position = 'center'
public string|Alignment $alignment = Alignment::CENTER
) {
//
}
@@ -45,7 +46,7 @@ class ResizeCanvasModifier extends SpecializableModifier
),
};
return $size->alignPivotTo($image->size(), $this->position);
return $size->alignPivotTo($image->size(), $this->alignment);
}
/**

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Intervention\Image\Tests\Unit\Geometry;
use Generator;
use Intervention\Image\Alignment;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Intervention\Image\Geometry\Point;
@@ -205,7 +206,7 @@ final class RectangleResizerTest extends TestCase
}
#[DataProvider('cropDataProvider')]
public function testCrop(Rectangle $origin, Rectangle $target, string $position, Rectangle $result): void
public function testCrop(Rectangle $origin, Rectangle $target, string|Alignment $position, Rectangle $result): void
{
$resizer = new RectangleResizer();
$resizer->toSize($target);