1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-01 03:20:17 +02:00

Add drawing methods

- Image::drawCircle()
- Image::drawEllipse()
This commit is contained in:
Oliver Vogel
2022-07-23 15:58:56 +02:00
parent efe7d5bb67
commit c3d3e4ff7d
8 changed files with 293 additions and 78 deletions

View File

@@ -4,6 +4,8 @@ namespace Intervention\Image\Drivers\Abstract;
use Intervention\Image\Collection; use Intervention\Image\Collection;
use Intervention\Image\EncodedImage; use Intervention\Image\EncodedImage;
use Intervention\Image\Geometry\Circle;
use Intervention\Image\Geometry\Ellipse;
use Intervention\Image\Geometry\Point; use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Rectangle; use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\CollectionInterface; use Intervention\Image\Interfaces\CollectionInterface;
@@ -227,6 +229,22 @@ abstract class AbstractImage implements ImageInterface
return $this->modify($modifier); return $this->modify($modifier);
} }
public function drawEllipse(int $x, int $y, ?callable $init = null): ImageInterface
{
$ellipse = $this->runCallback($init, new Ellipse(0, 0));
$modifier = $this->resolveDriverClass('Modifiers\DrawEllipseModifier', new Point($x, $y), $ellipse);
return $this->modify($modifier);
}
public function drawCircle(int $x, int $y, ?callable $init = null): ImageInterface
{
$circle = $this->runCallback($init, new Circle(0));
$modifier = $this->resolveDriverClass('Modifiers\DrawEllipseModifier', new Point($x, $y), $circle);
return $this->modify($modifier);
}
public function resize(?int $width = null, ?int $height = null): ImageInterface public function resize(?int $width = null, ?int $height = null): ImageInterface
{ {
return $this->modify( return $this->modify(

View File

@@ -2,6 +2,8 @@
namespace Intervention\Image\Drivers\Abstract\Modifiers; namespace Intervention\Image\Drivers\Abstract\Modifiers;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\DrawableInterface; use Intervention\Image\Interfaces\DrawableInterface;
use Intervention\Image\Interfaces\PointInterface; use Intervention\Image\Interfaces\PointInterface;
use Intervention\Image\Traits\CanHandleInput; use Intervention\Image\Traits\CanHandleInput;
@@ -16,4 +18,31 @@ class AbstractDrawModifier
) { ) {
// //
} }
public function drawable(): DrawableInterface
{
return $this->drawable;
}
protected function getBackgroundColor(): ?ColorInterface
{
try {
$color = $this->handleInput($this->drawable->getBackgroundColor());
} catch (DecoderException $e) {
return $this->handleInput('transparent');
}
return $color;
}
protected function getBorderColor(): ?ColorInterface
{
try {
$color = $this->handleInput($this->drawable->getBorderColor());
} catch (DecoderException $e) {
return $this->handleInput('transparent');
}
return $color;
}
} }

View File

@@ -0,0 +1,53 @@
<?php
namespace Intervention\Image\Drivers\Gd\Modifiers;
use Intervention\Image\Drivers\Abstract\Modifiers\AbstractDrawModifier;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\ModifierInterface;
class DrawEllipseModifier extends AbstractDrawModifier implements ModifierInterface
{
public function apply(ImageInterface $image): ImageInterface
{
return $image->eachFrame(function ($frame) {
if ($this->drawable()->hasBorder()) {
// slightly smaller ellipse to keep 1px bordered edges clean
if ($this->drawable()->hasBackgroundColor()) {
imagefilledellipse(
$frame->getCore(),
$this->position->getX(),
$this->position->getY(),
$this->drawable()->getWidth() - 1,
$this->drawable()->getHeight() - 1,
$this->getBackgroundColor()->toInt()
);
}
imagesetthickness($frame->getCore(), $this->drawable()->getBorderSize());
// gd's imageellipse doesn't respect imagesetthickness so i use
// imagearc with 359.9 degrees here.
imagearc(
$frame->getCore(),
$this->position->getX(),
$this->position->getY(),
$this->drawable()->getWidth(),
$this->drawable()->getHeight(),
0,
359.99,
$this->getBorderColor()->toInt()
);
} else {
imagefilledellipse(
$frame->getCore(),
$this->position->getX(),
$this->position->getY(),
$this->drawable()->getWidth(),
$this->drawable()->getHeight(),
$this->getBackgroundColor()->toInt()
);
}
});
}
}

View File

@@ -3,8 +3,6 @@
namespace Intervention\Image\Drivers\Gd\Modifiers; namespace Intervention\Image\Drivers\Gd\Modifiers;
use Intervention\Image\Drivers\Abstract\Modifiers\AbstractDrawModifier; use Intervention\Image\Drivers\Abstract\Modifiers\AbstractDrawModifier;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\ModifierInterface; use Intervention\Image\Interfaces\ModifierInterface;
@@ -14,58 +12,31 @@ class DrawRectangleModifier extends AbstractDrawModifier implements ModifierInte
{ {
$image->eachFrame(function ($frame) { $image->eachFrame(function ($frame) {
// draw background // draw background
if ($this->rectangle()->hasBackgroundColor()) { if ($this->drawable()->hasBackgroundColor()) {
imagefilledrectangle( imagefilledrectangle(
$frame->getCore(), $frame->getCore(),
$this->position->getX(), $this->position->getX(),
$this->position->getY(), $this->position->getY(),
$this->position->getX() + $this->rectangle()->bottomRightPoint()->getY(), $this->position->getX() + $this->drawable()->bottomRightPoint()->getX(),
$this->position->getY() + $this->rectangle()->bottomRightPoint()->getY(), $this->position->getY() + $this->drawable()->bottomRightPoint()->getY(),
$this->getBackgroundColor() $this->getBackgroundColor()->toInt()
); );
} }
if ($this->rectangle()->hasBorder()) { if ($this->drawable()->hasBorder()) {
// draw border // draw border
imagesetthickness($frame->getCore(), $this->rectangle()->getBorderSize()); imagesetthickness($frame->getCore(), $this->drawable()->getBorderSize());
imagerectangle( imagerectangle(
$frame->getCore(), $frame->getCore(),
$this->position->getX(), $this->position->getX(),
$this->position->getY(), $this->position->getY(),
$this->position->getX() + $this->rectangle()->bottomRightPoint()->getY(), $this->position->getX() + $this->drawable()->bottomRightPoint()->getX(),
$this->position->getY() + $this->rectangle()->bottomRightPoint()->getY(), $this->position->getY() + $this->drawable()->bottomRightPoint()->getY(),
$this->getBorderColor() $this->getBorderColor()->toInt()
); );
} }
}); });
return $image; return $image;
} }
private function rectangle(): Rectangle
{
return $this->drawable;
}
private function getBackgroundColor(): int
{
try {
$color = $this->handleInput($this->rectangle()->getBackgroundColor());
} catch (DecoderException $e) {
return 2130706432; // transparent
}
return $color->toInt();
}
private function getBorderColor(): int
{
try {
$color = $this->handleInput($this->rectangle()->getBorderColor());
} catch (DecoderException $e) {
return 2130706432; // transparent
}
return $color->toInt();
}
} }

View File

@@ -0,0 +1,58 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Modifiers;
use ImagickDraw;
use Intervention\Image\Drivers\Abstract\Modifiers\AbstractDrawModifier;
use Intervention\Image\Drivers\Imagick\Color;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\ModifierInterface;
class DrawEllipseModifier extends AbstractDrawModifier implements ModifierInterface
{
public function apply(ImageInterface $image): ImageInterface
{
return $image->eachFrame(function ($frame) {
$drawing = new ImagickDraw();
$drawing->setFillColor($this->getBackgroundColor()->getPixel());
if ($this->drawable()->hasBorder()) {
$drawing->setStrokeWidth($this->drawable()->getBorderSize());
$drawing->setStrokeColor($this->getBorderColor()->getPixel());
}
$drawing->ellipse(
$this->position->getX(),
$this->position->getY(),
$this->drawable()->getWidth() / 2,
$this->drawable()->getHeight() / 2,
0,
360
);
$frame->getCore()->drawImage($drawing);
});
}
protected function getBackgroundColor(): ColorInterface
{
$color = parent::getBackgroundColor();
if (!is_a($color, Color::class)) {
throw new DecoderException('Unable to decode background color.');
}
return $color;
}
protected function getBorderColor(): ColorInterface
{
$color = parent::getBorderColor();
if (!is_a($color, Color::class)) {
throw new DecoderException('Unable to decode border color.');
}
return $color;
}
}

View File

@@ -3,13 +3,12 @@
namespace Intervention\Image\Drivers\Imagick\Modifiers; namespace Intervention\Image\Drivers\Imagick\Modifiers;
use ImagickDraw; use ImagickDraw;
use ImagickPixel;
use Intervention\Image\Drivers\Abstract\Modifiers\AbstractDrawModifier; use Intervention\Image\Drivers\Abstract\Modifiers\AbstractDrawModifier;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\ModifierInterface;
use Intervention\Image\Drivers\Imagick\Color; use Intervention\Image\Drivers\Imagick\Color;
use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Geometry\Rectangle; use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\ModifierInterface;
use Intervention\Image\Interfaces\ColorInterface;
class DrawRectangleModifier extends AbstractDrawModifier implements ModifierInterface class DrawRectangleModifier extends AbstractDrawModifier implements ModifierInterface
{ {
@@ -17,18 +16,18 @@ class DrawRectangleModifier extends AbstractDrawModifier implements ModifierInte
{ {
// setup rectangle // setup rectangle
$drawing = new ImagickDraw(); $drawing = new ImagickDraw();
$drawing->setFillColor($this->getBackgroundColor()); $drawing->setFillColor($this->getBackgroundColor()->getPixel());
if ($this->rectangle()->hasBorder()) { if ($this->drawable()->hasBorder()) {
$drawing->setStrokeColor($this->getBorderColor()); $drawing->setStrokeColor($this->getBorderColor()->getPixel());
$drawing->setStrokeWidth($this->rectangle()->getBorderSize()); $drawing->setStrokeWidth($this->drawable()->getBorderSize());
} }
// build rectangle // build rectangle
$drawing->rectangle( $drawing->rectangle(
$this->position->getX(), $this->position->getX(),
$this->position->getY(), $this->position->getY(),
$this->position->getX() + $this->rectangle()->bottomRightPoint()->getX(), $this->position->getX() + $this->drawable()->bottomRightPoint()->getX(),
$this->position->getY() + $this->rectangle()->bottomRightPoint()->getY() $this->position->getY() + $this->drawable()->bottomRightPoint()->getY()
); );
$image->eachFrame(function ($frame) use ($drawing) { $image->eachFrame(function ($frame) use ($drawing) {
@@ -38,39 +37,23 @@ class DrawRectangleModifier extends AbstractDrawModifier implements ModifierInte
return $image; return $image;
} }
private function rectangle(): Rectangle protected function getBackgroundColor(): ColorInterface
{
return $this->drawable;
}
private function getBackgroundColor(): ImagickPixel
{
try {
$color = $this->handleInput($this->rectangle()->getBackgroundColor());
} catch (DecoderException $e) {
$color = null;
}
return $this->filterImagickPixel($color);
}
private function getBorderColor(): ImagickPixel
{
try {
$color = $this->handleInput($this->rectangle()->getBorderColor());
} catch (DecoderException $e) {
$color = null;
}
return $this->filterImagickPixel($color);
}
private function filterImagickPixel($color): ImagickPixel
{ {
$color = parent::getBackgroundColor();
if (!is_a($color, Color::class)) { if (!is_a($color, Color::class)) {
return new ImagickPixel('transparent'); throw new DecoderException('Unable to decode background color.');
} }
return $color->getPixel(); return $color;
}
protected function getBorderColor(): ColorInterface
{
$color = parent::getBorderColor();
if (!is_a($color, Color::class)) {
throw new DecoderException('Unable to decode border color.');
}
return $color;
} }
} }

48
src/Geometry/Circle.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
namespace Intervention\Image\Geometry;
class Circle extends Ellipse
{
public function __construct(
int $diameter,
protected ?Point $pivot = null
) {
$this->setWidth($diameter);
$this->setHeight($diameter);
$this->pivot = $pivot ? $pivot : new Point();
}
public function diameter(int $diameter): self
{
return $this->setDiameter($diameter);
}
public function setDiameter(int $diameter): self
{
$this->setWidth($diameter);
$this->setHeight($diameter);
return $this;
}
public function getDiameter(): int
{
return $this->diameter;
}
public function radius(int $radius): self
{
return $this->setRadius($radius);
}
public function setRadius(int $radius): self
{
return $this->diameter(intval($radius * 2));
}
public function getRadius(): int
{
return intval($this->diameter / 2);
}
}

55
src/Geometry/Ellipse.php Normal file
View File

@@ -0,0 +1,55 @@
<?php
namespace Intervention\Image\Geometry;
use Intervention\Image\Geometry\Traits\HasBackgroundColor;
use Intervention\Image\Geometry\Traits\HasBorder;
use Intervention\Image\Interfaces\DrawableInterface;
class Ellipse implements DrawableInterface
{
use HasBorder;
use HasBackgroundColor;
public function __construct(
protected int $width,
protected int $height,
protected ?Point $pivot = null
) {
$this->pivot = $pivot ? $pivot : new Point();
}
public function size(int $width, int $height): self
{
return $this->setSize($width, $height);
}
public function setSize(int $width, int $height): self
{
return $this->setWidth($width)->setHeight($height);
}
public function setWidth(int $width): self
{
$this->width = $width;
return $this;
}
public function setHeight(int $height): self
{
$this->height = $height;
return $this;
}
public function getWidth(): int
{
return $this->width;
}
public function getHeight(): int
{
return $this->height;
}
}