diff --git a/src/Interfaces/Box.php b/src/Interfaces/Box.php index aa0630e..e9e34d0 100644 --- a/src/Interfaces/Box.php +++ b/src/Interfaces/Box.php @@ -2,10 +2,13 @@ namespace SimonHamp\TheOg\Interfaces; +use Intervention\Image\Geometry\Point; use Intervention\Image\Interfaces\ImageInterface; interface Box { + public function anchor(): Point; + public function name(string $name): static; public function getName(): ?string; diff --git a/src/Layout/Box.php b/src/Layout/Box.php index d5640b2..fdb9d5b 100644 --- a/src/Layout/Box.php +++ b/src/Layout/Box.php @@ -2,33 +2,32 @@ namespace SimonHamp\TheOg\Layout; +use Closure; use Intervention\Image\Geometry\Point; use Intervention\Image\Geometry\Rectangle; use Intervention\Image\Interfaces\ImageInterface; use SimonHamp\TheOg\Interfaces\Box as BoxInterface; -readonly class Box implements BoxInterface +class Box implements BoxInterface { public Position $anchor; public Rectangle $box; - public string $name; + public readonly string $name; - public Point $position; + public readonly Rectangle $renderedBox; + + public readonly Point $position; /** * @var Closure */ - public mixed $relativeTo; - - public Position $relativeToPosition; - - public Rectangle $renderedBox; + public readonly Closure $relativeTo; - public function box(int $width, int $height): self + public function box(int|float $width, int|float $height): self { - $this->box = new Rectangle($width, $height); + $this->box = new Rectangle(intval(floor($width)), intval(floor($height))); return $this; } @@ -39,7 +38,6 @@ public function position( int $x, int $y, ?callable $relativeTo = null, - Position $position = Position::TopLeft, Position $anchor = Position::TopLeft ): self { @@ -47,7 +45,6 @@ public function position( if ($relativeTo) { $this->relativeTo = $relativeTo; - $this->relativeToPosition = $position; } $this->anchor = $anchor; diff --git a/src/Layout/Layouts/Avatar.php b/src/Layout/Layouts/Avatar.php new file mode 100644 index 0000000..5a98b88 --- /dev/null +++ b/src/Layout/Layouts/Avatar.php @@ -0,0 +1,52 @@ +addFeature((new PictureBox()) + ->path($this->picture()) + ->circle() + ->box(300, 300) + ->position( + x: 0, + y: 0, + relativeTo: fn () => $this->mountArea()->anchor(Position::Center)->moveY(-100), + anchor: Position::Center, + ) + ); + + $this->addFeature((new TextBox()) + ->text($this->title()) + ->color($this->config->theme->getTitleColor()) + ->font($this->config->theme->getTitleFont()) + ->size(56) + ->box($this->mountArea()->box->width() / 1.5, 300) + ->position( + x: 0, + y: 0, + relativeTo: fn () => $this->mountArea()->anchor(Position::Center)->moveY(100), + anchor: Position::MiddleTop, + ) + ); + } + + public function url(): string + { + return strtoupper(parent::url()); + } +} diff --git a/src/Layout/PictureBox.php b/src/Layout/PictureBox.php index 6a30c2b..e0cc7f0 100644 --- a/src/Layout/PictureBox.php +++ b/src/Layout/PictureBox.php @@ -2,22 +2,75 @@ namespace SimonHamp\TheOg\Layout; -use Intervention\Image\Drivers\Imagick\Modifiers\PlaceModifier; -use Intervention\Image\Image; +use Imagick; +use ImagickDraw; +use ImagickPixel; use Intervention\Image\ImageManager; use Intervention\Image\Interfaces\ImageInterface; -readonly class PictureBox extends Box + class PictureBox extends Box { public string $path; + /** + * @var array> + */ + public array $maskQueue; + + protected ImageInterface $picture; + public function render(ImageInterface $image): void { - $picture = ImageManager::imagick() - ->read(file_get_contents($this->path)) - ->cover($this->box->width(), $this->box->height()); + if (! empty($this->maskQueue)) { + foreach ($this->maskQueue as $mask) { + $this->mask($mask()); + } + } + + $position = $this->calculatePosition(); + + $image->place( + element: $this->getPicture(), + offset_x: $position->x(), + offset_y: $position->y() + ); + } + + /** + * Apply a mask image to the picture + */ + public function mask(Imagick $mask): void + { + $base = $this->getPicture()->core()->native(); + + $base->setImageMatte(true); + + $base->compositeImage($mask, Imagick::COMPOSITE_DSTIN, 0, 0); + + $base->writeImage(__DIR__.'/../../circle.png'); + } + + public function circle(): static + { + $this->maskQueue[] = function () { + $width = $this->box->width(); + $start = intval(floor($width / 2)); + + // Create the circle + $circle = new ImagickDraw(); + $circle->setFillColor(new ImagickPixel('#fff')); + $circle->circle($start, $start, $start, $width - 10); + + // Draw it to an Imagick instance + $image = new Imagick(); + $image->newImage($width, $width, 'none', 'png'); + $image->setImageMatte(true); + $image->drawImage($circle); + + return $image; + }; - $image->place($picture); + return $this; } public function path(string $path): self @@ -25,4 +78,11 @@ public function path(string $path): self $this->path = $path; return $this; } + + protected function getPicture(): ImageInterface + { + return $this->picture ??= ImageManager::imagick() + ->read(file_get_contents($this->path)) + ->cover($this->box->width(), $this->box->height()); + } } diff --git a/src/Layout/TextBox.php b/src/Layout/TextBox.php index 3976456..6b77119 100644 --- a/src/Layout/TextBox.php +++ b/src/Layout/TextBox.php @@ -14,7 +14,7 @@ use SimonHamp\TheOg\Interfaces\Font; use SimonHamp\TheOg\Modifiers\TextModifier as CustomTextModifier; -readonly class TextBox extends Box +class TextBox extends Box { public ColorInterface $color; public Font $font; diff --git a/tests/Integration/ImageTest.php b/tests/Integration/ImageTest.php index 6816f52..9b5da5f 100644 --- a/tests/Integration/ImageTest.php +++ b/tests/Integration/ImageTest.php @@ -6,6 +6,7 @@ use SimonHamp\TheOg\Background as BuiltInBackground; use SimonHamp\TheOg\BorderPosition; use SimonHamp\TheOg\Image; +use SimonHamp\TheOg\Layout\Layouts\Avatar; use SimonHamp\TheOg\Layout\Layouts\GitHubBasic; use SimonHamp\TheOg\Layout\Layouts\TwoUp; use SimonHamp\TheOg\Theme; @@ -170,5 +171,14 @@ public static function snapshotImages(): iterable ->callToAction('ONLY $99!'), 'twoup-custom-theme', ]; + + yield 'avatar layout' => [ + (new Image()) + ->layout(new Avatar) + ->accentColor('#003') + ->picture('https://i.pravatar.cc/300?img=10') + ->title('Simone Hampstead'), + 'avatar-layout', + ]; } } diff --git a/tests/Integration/__snapshots__/ImageTest__test_basic_image with data set avatar layout__1.png b/tests/Integration/__snapshots__/ImageTest__test_basic_image with data set avatar layout__1.png new file mode 100644 index 0000000..9f13fe2 Binary files /dev/null and b/tests/Integration/__snapshots__/ImageTest__test_basic_image with data set avatar layout__1.png differ