1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-29 16:50:07 +02:00

Add bezier curve drawing tool

This commit is contained in:
gammalogic
2024-06-08 12:05:24 +01:00
committed by Oliver Vogel
parent 6d0d0b2002
commit 216f4cffb4
11 changed files with 927 additions and 0 deletions

View File

@@ -0,0 +1,230 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Drivers\Gd\Modifiers;
use RuntimeException;
use Intervention\Image\Exceptions\GeometryException;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SpecializedInterface;
use Intervention\Image\Modifiers\DrawBezierModifier as ModifiersDrawBezierModifier;
class DrawBezierModifier extends ModifiersDrawBezierModifier implements SpecializedInterface
{
/**
* @throws RuntimeException
* @throws GeometryException
*/
public function apply(ImageInterface $image): ImageInterface
{
foreach ($image as $frame) {
if ($this->drawable->count() !== 3 && $this->drawable->count() !== 4) {
throw new GeometryException('You must specify either 3 or 4 points to create a bezier curve');
}
list($polygon, $polygon_border_segments) = $this->calculateBezierPoints();
if ($this->drawable->hasBackgroundColor() || $this->drawable->hasBorder()) {
imagealphablending($frame->native(), true);
imageantialias($frame->native(), true);
}
if ($this->drawable->hasBackgroundColor()) {
$background_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->backgroundColor()
);
imagefilledpolygon(
$frame->native(),
$polygon,
$background_color
);
}
if ($this->drawable->hasBorder() && $this->drawable->borderSize() > 0) {
$border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->borderColor()
);
if ($this->drawable->borderSize() === 1) {
imagesetthickness($frame->native(), $this->drawable->borderSize());
for ($i = 0; $i < count($polygon); $i += 2) {
if (array_key_exists($i + 2, $polygon) && array_key_exists($i + 3, $polygon)) {
imageline(
$frame->native(),
$polygon[$i + 0],
$polygon[$i + 1],
$polygon[$i + 2],
$polygon[$i + 3],
$border_color
);
}
}
} else {
$polygon_border_segments_total = count($polygon_border_segments);
for ($i = 0; $i < $polygon_border_segments_total; $i += 1) {
imagefilledpolygon(
$frame->native(),
$polygon_border_segments[$i],
$border_color
);
}
}
}
}
return $image;
}
/**
* Calculate interpolation points for quadratic beziers using the Bernstein polynomial form
*
* @param float $t
* @return array{'x': float, 'y': float}
*/
public function calculateQuadraticBezierInterpolationPoint(float $t = 0.05): array
{
$remainder = 1 - $t;
$control_point_1_multiplier = $remainder * $remainder;
$control_point_2_multiplier = $remainder * $t * 2;
$control_point_3_multiplier = $t * $t;
$x = (
$this->drawable->first()->x() * $control_point_1_multiplier +
$this->drawable->second()->x() * $control_point_2_multiplier +
$this->drawable->last()->x() * $control_point_3_multiplier
);
$y = (
$this->drawable->first()->y() * $control_point_1_multiplier +
$this->drawable->second()->y() * $control_point_2_multiplier +
$this->drawable->last()->y() * $control_point_3_multiplier
);
return ['x' => $x, 'y' => $y];
}
/**
* Calculate interpolation points for cubic beziers using the Bernstein polynomial form
*
* @param float $t
* @return array{'x': float, 'y': float}
*/
public function calculateCubicBezierInterpolationPoint(float $t = 0.05): array
{
$remainder = 1 - $t;
$t_squared = $t * $t;
$remainder_squared = $remainder * $remainder;
$control_point_1_multiplier = $remainder_squared * $remainder;
$control_point_2_multiplier = $remainder_squared * $t * 3;
$control_point_3_multiplier = $t_squared * $remainder * 3;
$control_point_4_multiplier = $t_squared * $t;
$x = (
$this->drawable->first()->x() * $control_point_1_multiplier +
$this->drawable->second()->x() * $control_point_2_multiplier +
$this->drawable->third()->x() * $control_point_3_multiplier +
$this->drawable->last()->x() * $control_point_4_multiplier
);
$y = (
$this->drawable->first()->y() * $control_point_1_multiplier +
$this->drawable->second()->y() * $control_point_2_multiplier +
$this->drawable->third()->y() * $control_point_3_multiplier +
$this->drawable->last()->y() * $control_point_4_multiplier
);
return ['x' => $x, 'y' => $y];
}
/**
* Calculate the points needed to draw a quadratic or cubic bezier with optional border/stroke
*
* @throws GeometryException
* @return array{0: array<mixed>, 1: array<mixed>}
*/
public function calculateBezierPoints(): array
{
if ($this->drawable->count() !== 3 && $this->drawable->count() !== 4) {
throw new GeometryException('You must specify either 3 or 4 points to create a bezier curve');
}
$polygon = [];
$inner_polygon = [];
$outer_polygon = [];
$polygon_border_segments = [];
// define ratio t; equivalent to 5 percent distance along edge
$t = (float) 0.05;
$polygon[] = $this->drawable->first()->x();
$polygon[] = $this->drawable->first()->y();
for ($i = 0 + $t; $i < 1; $i += $t) {
if ($this->drawable->count() === 3) {
$ip = $this->calculateQuadraticBezierInterpolationPoint($i);
} elseif ($this->drawable->count() === 4) {
$ip = $this->calculateCubicBezierInterpolationPoint($i);
}
$polygon[] = (int) $ip['x'];
$polygon[] = (int) $ip['y'];
}
$polygon[] = $this->drawable->last()->x();
$polygon[] = $this->drawable->last()->y();
if ($this->drawable->hasBorder() && $this->drawable->borderSize() > 1) {
// create the border/stroke effect by calculating two new curves with offset positions
// from the main polygon and then connecting the inner/outer curves to create separate
// 4-point polygon segments
$polygon_total_points = count($polygon);
$offset = ($this->drawable->borderSize() / 2);
for ($i = 0; $i < $polygon_total_points; $i += 2) {
if (array_key_exists($i + 2, $polygon) && array_key_exists($i + 3, $polygon)) {
$dx = $polygon[$i + 2] - $polygon[$i];
$dy = $polygon[$i + 3] - $polygon[$i + 1];
$dxy_sqrt = ($dx * $dx + $dy * $dy) ** 0.5;
// inner polygon
$scale = $offset / $dxy_sqrt;
$ox = -$dy * $scale;
$oy = $dx * $scale;
$inner_polygon[] = $ox + $polygon[$i + 0];
$inner_polygon[] = $oy + $polygon[$i + 1];
$inner_polygon[] = $ox + $polygon[$i + 2];
$inner_polygon[] = $oy + $polygon[$i + 3];
// outer polygon
$scale = -$offset / $dxy_sqrt;
$ox = -$dy * $scale;
$oy = $dx * $scale;
$outer_polygon[] = $ox + $polygon[$i + 0];
$outer_polygon[] = $oy + $polygon[$i + 1];
$outer_polygon[] = $ox + $polygon[$i + 2];
$outer_polygon[] = $oy + $polygon[$i + 3];
}
}
$inner_polygon_total_points = count($inner_polygon);
for ($i = 0; $i < $inner_polygon_total_points; $i += 2) {
if (array_key_exists($i + 2, $inner_polygon) && array_key_exists($i + 3, $inner_polygon)) {
$polygon_border_segments[] = [
$inner_polygon[$i + 0],
$inner_polygon[$i + 1],
$outer_polygon[$i + 0],
$outer_polygon[$i + 1],
$outer_polygon[$i + 2],
$outer_polygon[$i + 3],
$inner_polygon[$i + 2],
$inner_polygon[$i + 3],
];
}
}
}
return [$polygon, $polygon_border_segments];
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Drivers\Imagick\Modifiers;
use ImagickDraw;
use RuntimeException;
use Intervention\Image\Exceptions\GeometryException;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SpecializedInterface;
use Intervention\Image\Modifiers\DrawBezierModifier as GenericDrawBezierModifier;
class DrawBezierModifier extends GenericDrawBezierModifier implements SpecializedInterface
{
/**
* @throws RuntimeException
* @throws GeometryException
*/
public function apply(ImageInterface $image): ImageInterface
{
if ($this->drawable->count() !== 3 && $this->drawable->count() !== 4) {
throw new GeometryException('You must specify either 3 or 4 points to create a bezier curve');
}
$drawing = new ImagickDraw();
if ($this->drawable->hasBackgroundColor()) {
$background_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->backgroundColor()
);
} else {
$background_color = 'transparent';
}
$drawing->setFillColor($background_color);
if ($this->drawable->hasBorder() && $this->drawable->borderSize() > 0) {
$border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
$this->borderColor()
);
$drawing->setStrokeColor($border_color);
$drawing->setStrokeWidth($this->drawable->borderSize());
}
$drawing->pathStart();
$drawing->pathMoveToAbsolute(
$this->drawable->first()->x(),
$this->drawable->first()->y()
);
if ($this->drawable->count() === 3) {
$drawing->pathCurveToQuadraticBezierAbsolute(
$this->drawable->second()->x(),
$this->drawable->second()->y(),
$this->drawable->last()->x(),
$this->drawable->last()->y()
);
} elseif ($this->drawable->count() === 4) {
$drawing->pathCurveToAbsolute(
$this->drawable->second()->x(),
$this->drawable->second()->y(),
$this->drawable->third()->x(),
$this->drawable->third()->y(),
$this->drawable->last()->x(),
$this->drawable->last()->y()
);
}
$drawing->pathFinish();
foreach ($image as $frame) {
$frame->native()->drawImage($drawing);
}
return $image;
}
}

221
src/Geometry/Bezier.php Normal file
View File

@@ -0,0 +1,221 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Geometry;
use ArrayAccess;
use ArrayIterator;
use Countable;
use Traversable;
use IteratorAggregate;
use Intervention\Image\Geometry\Traits\HasBackgroundColor;
use Intervention\Image\Geometry\Traits\HasBorder;
use Intervention\Image\Interfaces\DrawableInterface;
use Intervention\Image\Interfaces\PointInterface;
/**
* @implements IteratorAggregate<Point>
* @implements ArrayAccess<int, Point>
*/
class Bezier implements IteratorAggregate, Countable, ArrayAccess, DrawableInterface
{
use HasBorder;
use HasBackgroundColor;
/**
* Create new bezier instance
*
* @param array<Point> $points
* @param PointInterface $pivot
* @return void
*/
public function __construct(
protected array $points = [],
protected PointInterface $pivot = new Point()
) {
}
/**
* {@inheritdoc}
*
* @see DrawableInterface::position()
*/
public function position(): PointInterface
{
return $this->pivot;
}
/**
* Implement iteration through all points of bezier
*
* @return Traversable<Point>
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->points);
}
/**
* Return current pivot point
*
* @return PointInterface
*/
public function pivot(): PointInterface
{
return $this->pivot;
}
/**
* Change pivot point to given point
*
* @param Point $pivot
* @return Bezier
*/
public function setPivot(Point $pivot): self
{
$this->pivot = $pivot;
return $this;
}
/**
* Return first control point of bezier
*
* @return ?Point
*/
public function first(): ?Point
{
if ($point = reset($this->points)) {
return $point;
}
return null;
}
/**
* Return second control point of bezier
*
* @return ?Point
*/
public function second(): ?Point
{
if (array_key_exists(1, $this->points)) {
return $this->points[1];
}
return null;
}
/**
* Return third control point of bezier
*
* @return ?Point
*/
public function third(): ?Point
{
if (array_key_exists(2, $this->points)) {
return $this->points[2];
}
return null;
}
/**
* Return last control point of bezier
*
* @return ?Point
*/
public function last(): ?Point
{
if ($point = end($this->points)) {
return $point;
}
return null;
}
/**
* Return bezier's point count
*
* @return int
*/
public function count(): int
{
return count($this->points);
}
/**
* Determine if point exists at given offset
*
* @param mixed $offset
* @return bool
*/
public function offsetExists($offset): bool
{
return array_key_exists($offset, $this->points);
}
/**
* Return point at given offset
*
* @param mixed $offset
* @return Point
*/
public function offsetGet($offset): mixed
{
return $this->points[$offset];
}
/**
* Set point at given offset
*
* @param mixed $offset
* @param Point $value
* @return void
*/
public function offsetSet($offset, $value): void
{
$this->points[$offset] = $value;
}
/**
* Unset offset at given offset
*
* @param mixed $offset
* @return void
*/
public function offsetUnset($offset): void
{
unset($this->points[$offset]);
}
/**
* Add given point to bezier
*
* @param Point $point
* @return Bezier
*/
public function addPoint(Point $point): self
{
$this->points[] = $point;
return $this;
}
/**
* Return array of all x/y values of all points of bezier
*
* @return array<int>
*/
public function toArray(): array
{
$coordinates = [];
foreach ($this->points as $point) {
$coordinates[] = $point->x();
$coordinates[] = $point->y();
}
return $coordinates;
}
}

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Geometry\Factories;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Bezier;
class BezierFactory
{
protected Bezier $bezier;
/**
* Create new factory instance
*
* @param callable|Bezier $init
* @return void
*/
public function __construct(callable|Bezier $init)
{
$this->bezier = is_a($init, Bezier::class) ? $init : new Bezier([]);
if (is_callable($init)) {
$init($this);
}
}
/**
* Add a point to the bezier to be produced
*
* @param int $x
* @param int $y
* @return BezierFactory
*/
public function point(int $x, int $y): self
{
$this->bezier->addPoint(new Point($x, $y));
return $this;
}
/**
* Set the background color of the bezier to be produced
*
* @param mixed $color
* @return BezierFactory
*/
public function background(mixed $color): self
{
$this->bezier->setBackgroundColor($color);
return $this;
}
/**
* Set the border color & border size of the bezier to be produced
*
* @param mixed $color
* @param int $size
* @return BezierFactory
*/
public function border(mixed $color, int $size = 1): self
{
$this->bezier->setBorder($color, $size);
return $this;
}
/**
* Produce the bezier
*
* @return Bezier
*/
public function __invoke(): Bezier
{
return $this->bezier;
}
}

View File

@@ -27,6 +27,7 @@ use Intervention\Image\Encoders\PngEncoder;
use Intervention\Image\Encoders\TiffEncoder;
use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Exceptions\EncoderException;
use Intervention\Image\Geometry\Factories\BezierFactory;
use Intervention\Image\Geometry\Factories\CircleFactory;
use Intervention\Image\Geometry\Factories\EllipseFactory;
use Intervention\Image\Geometry\Factories\LineFactory;
@@ -58,6 +59,7 @@ use Intervention\Image\Modifiers\ColorspaceModifier;
use Intervention\Image\Modifiers\ContainModifier;
use Intervention\Image\Modifiers\ContrastModifier;
use Intervention\Image\Modifiers\CropModifier;
use Intervention\Image\Modifiers\DrawBezierModifier;
use Intervention\Image\Modifiers\DrawEllipseModifier;
use Intervention\Image\Modifiers\DrawLineModifier;
use Intervention\Image\Modifiers\DrawPixelModifier;
@@ -874,6 +876,20 @@ final class Image implements ImageInterface
);
}
/**
* {@inheritdoc}
*
* @see ImageInterface::drawBezier()
*/
public function drawBezier(callable $init): ImageInterface
{
return $this->modify(
new DrawBezierModifier(
call_user_func(new BezierFactory($init)),
),
);
}
/**
* {@inheritdoc}
*

View File

@@ -749,6 +749,16 @@ interface ImageInterface extends IteratorAggregate, Countable
*/
public function drawLine(callable $init): self;
/**
* Draw a bezier curve on the current image
*
* @link
* @param callable $init
* @throws RuntimeException
* @return ImageInterface
*/
public function drawBezier(callable $init): self;
/**
* Encode image to given media (mime) type. If no type is given the image
* will be encoded to the format of the originally read image.

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Modifiers;
use Intervention\Image\Geometry\Bezier;
use Intervention\Image\Interfaces\DrawableInterface;
class DrawBezierModifier extends AbstractDrawModifier
{
public function __construct(public Bezier $drawable)
{
}
public function drawable(): DrawableInterface
{
return $this->drawable;
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Tests\Unit\Drivers\Gd\Modifiers;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
use Intervention\Image\Modifiers\DrawBezierModifier;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Bezier;
use Intervention\Image\Tests\GdTestCase;
#[RequiresPhpExtension('gd')]
#[CoversClass(\Intervention\Image\Modifiers\DrawPixelModifier::class)]
#[CoversClass(\Intervention\Image\Drivers\Gd\Modifiers\DrawPixelModifier::class)]
final class DrawBezierModifierTest extends GdTestCase
{
public function testApply(): void
{
$image = $this->readTestImage('trim.png');
$this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex());
$drawable = new Bezier([
new Point(0, 0),
new Point(15, 0),
new Point(15, 15),
new Point(0, 15)
]);
$drawable->setBackgroundColor('b53717');
$image->modify(new DrawBezierModifier($drawable));
$this->assertEquals('b53717', $image->pickColor(5, 5)->toHex());
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Tests\Unit\Drivers\Imagick\Modifiers;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
use Intervention\Image\Modifiers\DrawBezierModifier;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Bezier;
use Intervention\Image\Tests\ImagickTestCase;
#[RequiresPhpExtension('imagick')]
#[CoversClass(\Intervention\Image\Modifiers\DrawBezierModifier::class)]
#[CoversClass(\Intervention\Image\Drivers\Imagick\Modifiers\DrawBezierModifier::class)]
final class DrawBezierModifierTest extends ImagickTestCase
{
public function testApply(): void
{
$image = $this->readTestImage('trim.png');
$this->assertEquals('00aef0', $image->pickColor(14, 14)->toHex());
$drawable = new Bezier([
new Point(0, 0),
new Point(15, 0),
new Point(15, 15),
new Point(0, 15)
]);
$drawable->setBackgroundColor('b53717');
$image->modify(new DrawBezierModifier($drawable));
$this->assertEquals('b53717', $image->pickColor(5, 5)->toHex());
}
}

View File

@@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Tests\Unit\Geometry;
use PHPUnit\Framework\Attributes\CoversClass;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Bezier;
use Intervention\Image\Tests\BaseTestCase;
#[CoversClass(\Intervention\Image\Geometry\Bezier::class)]
final class BezierTest extends BaseTestCase
{
public function testConstructor(): void
{
$bezier = new Bezier([]);
$this->assertInstanceOf(Bezier::class, $bezier);
$this->assertEquals(0, $bezier->count());
}
public function testCount(): void
{
$bezier = new Bezier([
new Point(),
new Point(),
new Point(),
new Point()
]);
$this->assertEquals(4, $bezier->count());
}
public function testArrayAccess(): void
{
$bezier = new Bezier([
new Point(),
new Point(),
new Point(),
new Point()
]);
$this->assertInstanceOf(Point::class, $bezier[0]);
$this->assertInstanceOf(Point::class, $bezier[1]);
$this->assertInstanceOf(Point::class, $bezier[2]);
$this->assertInstanceOf(Point::class, $bezier[3]);
}
public function testAddPoint(): void
{
$bezier = new Bezier([
new Point(),
new Point()
]);
$this->assertEquals(2, $bezier->count());
$result = $bezier->addPoint(new Point());
$this->assertEquals(3, $bezier->count());
$this->assertInstanceOf(Bezier::class, $result);
}
public function testFirst(): void
{
$bezier = new Bezier([
new Point(50, 45),
new Point(100, -49),
new Point(-100, 100),
new Point(200, 300),
]);
$this->assertEquals(50, $bezier->first()->x());
$this->assertEquals(45, $bezier->first()->y());
}
public function testFirstEmpty(): void
{
$bezier = new Bezier();
$this->assertNull($bezier->first());
}
public function testSecond(): void
{
$bezier = new Bezier([
new Point(50, 45),
new Point(100, -49),
new Point(-100, 100),
new Point(200, 300),
]);
$this->assertEquals(100, $bezier->second()->x());
$this->assertEquals(-49, $bezier->second()->y());
}
public function testSecondEmpty(): void
{
$bezier = new Bezier();
$this->assertNull($bezier->second());
}
public function testThird(): void
{
$bezier = new Bezier([
new Point(50, 45),
new Point(100, -49),
new Point(-100, 100),
new Point(200, 300),
]);
$this->assertEquals(-100, $bezier->third()->x());
$this->assertEquals(100, $bezier->third()->y());
}
public function testThirdEmpty(): void
{
$bezier = new Bezier();
$this->assertNull($bezier->third());
}
public function testLast(): void
{
$bezier = new Bezier([
new Point(50, 45),
new Point(100, -49),
new Point(-100, 100),
new Point(200, 300),
]);
$this->assertEquals(200, $bezier->last()->x());
$this->assertEquals(300, $bezier->last()->y());
}
public function testLastEmpty(): void
{
$bezier = new Bezier();
$this->assertNull($bezier->last());
}
public function testOffsetExists(): void
{
$bezier = new Bezier();
$this->assertFalse($bezier->offsetExists(0));
$this->assertFalse($bezier->offsetExists(1));
$bezier->addPoint(new Point(0, 0));
$this->assertTrue($bezier->offsetExists(0));
$this->assertFalse($bezier->offsetExists(1));
}
public function testOffsetSetUnset(): void
{
$bezier = new Bezier();
$bezier->offsetSet(0, new Point());
$bezier->offsetSet(2, new Point());
$this->assertTrue($bezier->offsetExists(0));
$this->assertFalse($bezier->offsetExists(1));
$this->assertTrue($bezier->offsetExists(2));
$bezier->offsetUnset(2);
$this->assertTrue($bezier->offsetExists(0));
$this->assertFalse($bezier->offsetExists(1));
$this->assertFalse($bezier->offsetExists(2));
}
public function testGetSetPivotPoint(): void
{
$bezier = new Bezier();
$this->assertInstanceOf(Point::class, $bezier->pivot());
$this->assertEquals(0, $bezier->pivot()->x());
$this->assertEquals(0, $bezier->pivot()->y());
$result = $bezier->setPivot(new Point(12, 34));
$this->assertInstanceOf(Bezier::class, $result);
$this->assertEquals(12, $bezier->pivot()->x());
$this->assertEquals(34, $bezier->pivot()->y());
}
public function testToArray(): void
{
$bezier = new Bezier([
new Point(50, 50),
new Point(100, 50),
new Point(-50, -100),
new Point(50, 100),
]);
$this->assertEquals([50, 50, 100, 50, -50, -100, 50, 100], $bezier->toArray());
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Tests\Unit\Geometry\Factories;
use Intervention\Image\Geometry\Factories\BezierFactory;
use Intervention\Image\Geometry\Bezier;
use Intervention\Image\Tests\BaseTestCase;
final class BezierFactoryTest extends BaseTestCase
{
public function testFactoryCallback(): void
{
$factory = new BezierFactory(function ($bezier) {
$bezier->background('f00');
$bezier->border('ff0', 10);
$bezier->point(300, 260);
$bezier->point(150, 335);
$bezier->point(300, 410);
});
$bezier = $factory();
$this->assertInstanceOf(Bezier::class, $bezier);
$this->assertTrue($bezier->hasBackgroundColor());
$this->assertEquals('f00', $bezier->backgroundColor());
$this->assertEquals('ff0', $bezier->borderColor());
$this->assertEquals(10, $bezier->borderSize());
$this->assertEquals(3, $bezier->count());
}
}