diff --git a/src/Drivers/Abstract/AbstractImage.php b/src/Drivers/Abstract/AbstractImage.php index 6626cad3..01b702ab 100644 --- a/src/Drivers/Abstract/AbstractImage.php +++ b/src/Drivers/Abstract/AbstractImage.php @@ -4,6 +4,8 @@ namespace Intervention\Image\Drivers\Abstract; use Intervention\Image\Collection; use Intervention\Image\EncodedImage; +use Intervention\Image\Geometry\Circle; +use Intervention\Image\Geometry\Ellipse; use Intervention\Image\Geometry\Point; use Intervention\Image\Geometry\Rectangle; use Intervention\Image\Interfaces\CollectionInterface; @@ -227,6 +229,22 @@ abstract class AbstractImage implements ImageInterface 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 { return $this->modify( diff --git a/src/Drivers/Abstract/Modifiers/AbstractDrawModifier.php b/src/Drivers/Abstract/Modifiers/AbstractDrawModifier.php index 1dfa0f13..04fe1f46 100644 --- a/src/Drivers/Abstract/Modifiers/AbstractDrawModifier.php +++ b/src/Drivers/Abstract/Modifiers/AbstractDrawModifier.php @@ -2,6 +2,8 @@ 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\PointInterface; 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; + } } diff --git a/src/Drivers/Gd/Modifiers/DrawEllipseModifier.php b/src/Drivers/Gd/Modifiers/DrawEllipseModifier.php new file mode 100644 index 00000000..d7adef56 --- /dev/null +++ b/src/Drivers/Gd/Modifiers/DrawEllipseModifier.php @@ -0,0 +1,53 @@ +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() + ); + } + }); + } +} diff --git a/src/Drivers/Gd/Modifiers/DrawRectangleModifier.php b/src/Drivers/Gd/Modifiers/DrawRectangleModifier.php index 3f949aca..a12c252f 100644 --- a/src/Drivers/Gd/Modifiers/DrawRectangleModifier.php +++ b/src/Drivers/Gd/Modifiers/DrawRectangleModifier.php @@ -3,8 +3,6 @@ namespace Intervention\Image\Drivers\Gd\Modifiers; 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\ModifierInterface; @@ -14,58 +12,31 @@ class DrawRectangleModifier extends AbstractDrawModifier implements ModifierInte { $image->eachFrame(function ($frame) { // draw background - if ($this->rectangle()->hasBackgroundColor()) { + if ($this->drawable()->hasBackgroundColor()) { imagefilledrectangle( $frame->getCore(), $this->position->getX(), $this->position->getY(), - $this->position->getX() + $this->rectangle()->bottomRightPoint()->getY(), - $this->position->getY() + $this->rectangle()->bottomRightPoint()->getY(), - $this->getBackgroundColor() + $this->position->getX() + $this->drawable()->bottomRightPoint()->getX(), + $this->position->getY() + $this->drawable()->bottomRightPoint()->getY(), + $this->getBackgroundColor()->toInt() ); } - if ($this->rectangle()->hasBorder()) { + if ($this->drawable()->hasBorder()) { // draw border - imagesetthickness($frame->getCore(), $this->rectangle()->getBorderSize()); + imagesetthickness($frame->getCore(), $this->drawable()->getBorderSize()); imagerectangle( $frame->getCore(), $this->position->getX(), $this->position->getY(), - $this->position->getX() + $this->rectangle()->bottomRightPoint()->getY(), - $this->position->getY() + $this->rectangle()->bottomRightPoint()->getY(), - $this->getBorderColor() + $this->position->getX() + $this->drawable()->bottomRightPoint()->getX(), + $this->position->getY() + $this->drawable()->bottomRightPoint()->getY(), + $this->getBorderColor()->toInt() ); } }); 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(); - } } diff --git a/src/Drivers/Imagick/Modifiers/DrawEllipseModifier.php b/src/Drivers/Imagick/Modifiers/DrawEllipseModifier.php new file mode 100644 index 00000000..075842ea --- /dev/null +++ b/src/Drivers/Imagick/Modifiers/DrawEllipseModifier.php @@ -0,0 +1,58 @@ +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; + } +} diff --git a/src/Drivers/Imagick/Modifiers/DrawRectangleModifier.php b/src/Drivers/Imagick/Modifiers/DrawRectangleModifier.php index bb6f9be0..f16bdb5a 100644 --- a/src/Drivers/Imagick/Modifiers/DrawRectangleModifier.php +++ b/src/Drivers/Imagick/Modifiers/DrawRectangleModifier.php @@ -3,13 +3,12 @@ namespace Intervention\Image\Drivers\Imagick\Modifiers; use ImagickDraw; -use ImagickPixel; 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\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 { @@ -17,18 +16,18 @@ class DrawRectangleModifier extends AbstractDrawModifier implements ModifierInte { // setup rectangle $drawing = new ImagickDraw(); - $drawing->setFillColor($this->getBackgroundColor()); - if ($this->rectangle()->hasBorder()) { - $drawing->setStrokeColor($this->getBorderColor()); - $drawing->setStrokeWidth($this->rectangle()->getBorderSize()); + $drawing->setFillColor($this->getBackgroundColor()->getPixel()); + if ($this->drawable()->hasBorder()) { + $drawing->setStrokeColor($this->getBorderColor()->getPixel()); + $drawing->setStrokeWidth($this->drawable()->getBorderSize()); } // build rectangle $drawing->rectangle( $this->position->getX(), $this->position->getY(), - $this->position->getX() + $this->rectangle()->bottomRightPoint()->getX(), - $this->position->getY() + $this->rectangle()->bottomRightPoint()->getY() + $this->position->getX() + $this->drawable()->bottomRightPoint()->getX(), + $this->position->getY() + $this->drawable()->bottomRightPoint()->getY() ); $image->eachFrame(function ($frame) use ($drawing) { @@ -38,39 +37,23 @@ class DrawRectangleModifier extends AbstractDrawModifier implements ModifierInte return $image; } - private function rectangle(): Rectangle - { - 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 + protected function getBackgroundColor(): ColorInterface { + $color = parent::getBackgroundColor(); 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; } } diff --git a/src/Geometry/Circle.php b/src/Geometry/Circle.php new file mode 100644 index 00000000..841d6a96 --- /dev/null +++ b/src/Geometry/Circle.php @@ -0,0 +1,48 @@ +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); + } +} diff --git a/src/Geometry/Ellipse.php b/src/Geometry/Ellipse.php new file mode 100644 index 00000000..fb37dc7e --- /dev/null +++ b/src/Geometry/Ellipse.php @@ -0,0 +1,55 @@ +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; + } +}