From 8ce7b0ba443d6ca209fe542636506414cd8d021d Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Wed, 11 May 2022 13:01:23 +0300 Subject: [PATCH] Added retina support for `Image\Adapter` operations --- .../Contracts/Image/ImageAdapterInterface.php | 18 +++ .../Grav/Framework/Image/Adapter/Adapter.php | 20 +++ .../Framework/Image/Adapter/GdAdapter.php | 117 ++++++++++++++++-- 3 files changed, 143 insertions(+), 12 deletions(-) diff --git a/system/src/Grav/Framework/Contracts/Image/ImageAdapterInterface.php b/system/src/Grav/Framework/Contracts/Image/ImageAdapterInterface.php index 236451a58..757f1daea 100644 --- a/system/src/Grav/Framework/Contracts/Image/ImageAdapterInterface.php +++ b/system/src/Grav/Framework/Contracts/Image/ImageAdapterInterface.php @@ -36,6 +36,24 @@ interface ImageAdapterInterface extends ImageInfoInterface, ImageSaveInterface */ public function getName(): string; + /** + * Gets the retina scaling for the image. + * + * Image size for resize operations and image sizes or coordinates will be multiplied by this factor. + * + * @return int + */ + public function getRetinaScale(): int; + + /** + * Sets the retina scaling for the image. + * + * NOTE: Set this before any image operations. + * + * @return $this + */ + public function setRetinaScale(int $scale); + /** * Resizes the image. * diff --git a/system/src/Grav/Framework/Image/Adapter/Adapter.php b/system/src/Grav/Framework/Image/Adapter/Adapter.php index 0a55cf9a3..ca8f8c321 100644 --- a/system/src/Grav/Framework/Image/Adapter/Adapter.php +++ b/system/src/Grav/Framework/Image/Adapter/Adapter.php @@ -11,6 +11,26 @@ abstract class Adapter implements ImageAdapterInterface { /** @var int */ protected $orientation = 1; + /** @var int */ + protected $scale = 1; + + /** + * @return int + */ + public function getRetinaScale(): int + { + return $this->scale; + } + + /** + * {@inheritdoc} + */ + public function setRetinaScale(int $scale) + { + $this->scale = $scale; + + return $this; + } /** * {@inheritdoc} diff --git a/system/src/Grav/Framework/Image/Adapter/GdAdapter.php b/system/src/Grav/Framework/Image/Adapter/GdAdapter.php index 06ed2216b..ab4e3909b 100644 --- a/system/src/Grav/Framework/Image/Adapter/GdAdapter.php +++ b/system/src/Grav/Framework/Image/Adapter/GdAdapter.php @@ -68,13 +68,17 @@ class GdAdapter extends Adapter * * @param int $width * @param int $height + * @param int $scale * @return static */ - public static function create(int $width, int $height): GdAdapter + public static function create(int $width, int $height, int $scale = 1): GdAdapter { $resource = static::createResource($width, $height); - return new static($resource); + $image = new static($resource); + $image->scale = $scale; + + return $image; } /** @@ -178,6 +182,13 @@ class GdAdapter extends Adapter */ public function resize(?int $background, int $target_width, int $target_height, int $new_width, int $new_height): GdAdapter { + if ($this->scale !== 1) { + $target_width *= $this->scale; + $target_height *= $this->scale; + $new_width *= $this->scale; + $new_height *= $this->scale; + } + $width = $this->width(); $height = $this->height(); $dst_x = (int)(($target_width - $new_width) / 2); @@ -211,6 +222,13 @@ class GdAdapter extends Adapter */ public function crop(int $x, int $y, int $width, int $height): GdAdapter { + if ($this->scale !== 1) { + $x *= $this->scale; + $y *= $this->scale; + $width *= $this->scale; + $height *= $this->scale; + } + $destination = imagecreatetruecolor($width, $height); if (!$destination) { throw new RuntimeException('Image crop failed'); @@ -391,17 +409,30 @@ class GdAdapter extends Adapter throw new InvalidArgumentException('Image to be merged needs to be instance of GdAdapter'); } + $scale = $this->scale; + $otherScale = $other->getRetinaScale(); + + $x *= $scale; + $y *= $scale; + + if (null !== $width) { + $otherWidth = $width * $otherScale; + $width *= $scale; + } else { + $otherWidth = $other->width(); + $width = (int)($otherWidth * $scale / $otherScale); + } + + if (null !== $height) { + $otherHeight = $height * $otherScale; + $height *= $scale; + } else { + $otherHeight = $other->height(); + $height = (int)($otherHeight * $scale / $otherScale); + } + imagealphablending($this->resource, true); - - if (null === $width) { - $width = $other->width(); - } - - if (null === $height) { - $height = $other->height(); - } - - imagecopyresampled($this->resource, $other->getResource(), $x, $y, 0, 0, $width, $height, $width, $height); + imagecopyresampled($this->resource, $other->getResource(), $x, $y, 0, 0, $width, $height, $otherWidth, $otherHeight); return $this; } @@ -428,6 +459,11 @@ class GdAdapter extends Adapter */ public function fill(int $color = 0xffffff, int $x = 0, int $y = 0): GdAdapter { + if ($this->scale !== 1) { + $x *= $this->scale; + $y *= $this->scale; + } + imagealphablending($this->resource, false); imagefill($this->resource, $x, $y, $this->allocateColor($color)); @@ -439,6 +475,12 @@ class GdAdapter extends Adapter */ public function write(string $font, string $text, int $x = 0, int $y = 0, float $size = 12.0, float $angle = 0.0, int $color = 0x000000, string $align = 'left'): GdAdapter { + if ($this->scale !== 1) { + $x *= $this->scale; + $y *= $this->scale; + $size *= $this->scale; + } + imagealphablending($this->resource, true); if ($align !== 'left') { @@ -463,6 +505,13 @@ class GdAdapter extends Adapter */ public function rectangle(int $x1, int $y1, int $x2, int $y2, int $color, bool $filled = false): GdAdapter { + if ($this->scale !== 1) { + $x1 *= $this->scale; + $y1 *= $this->scale; + $x2 *= $this->scale; + $y2 *= $this->scale; + } + $c = $this->allocateColor($color); if ($filled) { imagefilledrectangle($this->resource, $x1, $y1, $x2, $y2, $c); @@ -478,6 +527,14 @@ class GdAdapter extends Adapter */ public function roundedRectangle(int $x1, int $y1, int $x2, int $y2, int $radius, int $color, bool $filled = false): GdAdapter { + if ($this->scale !== 1) { + $x1 *= $this->scale; + $y1 *= $this->scale; + $x2 *= $this->scale; + $y2 *= $this->scale; + $radius *= $this->scale; + } + $c = $this->allocateColor($color); if ($filled) { @@ -509,6 +566,13 @@ class GdAdapter extends Adapter */ public function line(int $x1, int $y1, int $x2, int $y2, $color = 0x000000): GdAdapter { + if ($this->scale !== 1) { + $x1 *= $this->scale; + $y1 *= $this->scale; + $x2 *= $this->scale; + $y2 *= $this->scale; + } + imageline($this->resource, $x1, $y1, $x2, $y2, $this->allocateColor($color)); return $this; @@ -519,6 +583,13 @@ class GdAdapter extends Adapter */ public function ellipse(int $cx, int $cy, int $width, int $height, $color = 0x000000, bool $filled = false): GdAdapter { + if ($this->scale !== 1) { + $cx *= $this->scale; + $cy *= $this->scale; + $width *= $this->scale; + $height *= $this->scale; + } + $c = $this->allocateColor($color); if ($filled) { imagefilledellipse($this->resource, $cx, $cy, $width, $height, $c); @@ -534,6 +605,12 @@ class GdAdapter extends Adapter */ public function circle(int $cx, int $cy, int $r, $color = 0x000000, bool $filled = false): GdAdapter { + if ($this->scale !== 1) { + $cx *= $this->scale; + $cy *= $this->scale; + $r *= $this->scale; + } + return $this->ellipse($cx, $cy, $r, $r, $this->allocateColor($color), $filled); } @@ -542,6 +619,13 @@ class GdAdapter extends Adapter */ public function polygon(array $points, $color, bool $filled = false): GdAdapter { + if ($this->scale !== 1) { + foreach ($points as &$point) { + $point *= $this->scale; + } + unset($point); + } + $num = (int)(count($points) / 2); $c = $this->allocateColor($color); @@ -725,6 +809,11 @@ class GdAdapter extends Adapter */ protected function getColor(int $x, int $y) { + if ($this->scale !== 1) { + $x *= $this->scale; + $y *= $this->scale; + } + return imagecolorat($this->resource, $x, $y); } @@ -739,6 +828,10 @@ class GdAdapter extends Adapter */ protected function getTTFBox(string $font, string $text, float $size, float $angle = 0): array { + if ($this->scale !== 1) { + $size *= $this->scale; + } + $box = imagettfbbox($size, $angle, $font, $text); if (false === $box) { throw new RuntimeException('Failed to allocate room for text');