mirror of
https://github.com/getgrav/grav.git
synced 2026-09-02 04:47:32 +02:00
Added Grav\Framework\Image classes
This commit is contained in:
@@ -83,7 +83,8 @@
|
||||
"ext-memcache": "Needed to support Memcache servers",
|
||||
"ext-memcached": "Needed to support Memcached servers",
|
||||
"ext-redis": "Needed to support Redis servers",
|
||||
"ext-exif": "Needed to use exif data from images."
|
||||
"ext-exif": "Needed to use exif data from images.",
|
||||
"ext-gd": "Needed to resize images."
|
||||
},
|
||||
"config": {
|
||||
"apcu-autoloader": true,
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Contracts\Image;
|
||||
|
||||
/**
|
||||
* Image adapter interface.
|
||||
*/
|
||||
interface ImageAdapterInterface extends ImageInfoInterface, ImageSaveInterface
|
||||
{
|
||||
/**
|
||||
* Returns true if the adapter is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isEnabled(): bool;
|
||||
|
||||
/**
|
||||
* Returns true if the image type is supported by the adapter.
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupported(string $type): bool;
|
||||
|
||||
/**
|
||||
* Get the raw resource.
|
||||
*
|
||||
* @return object|resource
|
||||
*/
|
||||
public function getResource();
|
||||
|
||||
/**
|
||||
* Gets the name of the adapter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* Resizes the image.
|
||||
*
|
||||
* @param int|null $background
|
||||
* @param int $target_width
|
||||
* @param int $target_height
|
||||
* @param int $new_width
|
||||
* @param int $new_height
|
||||
* @return $this
|
||||
*/
|
||||
public function resize(?int $background, int $target_width, int $target_height, int $new_width, int $new_height);
|
||||
|
||||
/**
|
||||
* Crops the image.
|
||||
*
|
||||
* @param int $x The top-left x position of the crop box
|
||||
* @param int $y The top-left y position of the crop box
|
||||
* @param int $width The width of the crop box
|
||||
* @param int $height The height of the crop box
|
||||
* @return $this
|
||||
*/
|
||||
public function crop(int $x, int $y, int $width, int $height);
|
||||
|
||||
/**
|
||||
* Fixes image orientation based on exif data.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function fixOrientation();
|
||||
|
||||
/**
|
||||
* Applies orientation using exif orientation value.
|
||||
*
|
||||
* @param int $exif_orientation
|
||||
* @return $this
|
||||
*/
|
||||
public function applyExifOrientation(int $exif_orientation);
|
||||
|
||||
/**
|
||||
* Enables progressive image loading.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function enableProgressive();
|
||||
|
||||
/**
|
||||
* Fills the image background if the image is transparent.
|
||||
*
|
||||
* @param int|null $background Image background, null if transparent
|
||||
* @return $this
|
||||
*/
|
||||
public function fillBackground(?int $background = 0xffffff);
|
||||
|
||||
/**
|
||||
* Negates the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function negate();
|
||||
|
||||
/**
|
||||
* Changes the brightness of the image.
|
||||
*
|
||||
* @param int $brightness Image brightness
|
||||
* @return $this
|
||||
*/
|
||||
public function brightness(int $brightness);
|
||||
|
||||
/**
|
||||
* Changes the contrast of the image.
|
||||
*
|
||||
* @param int $contrast Image contrast [-100, 100]
|
||||
* @return $this
|
||||
*/
|
||||
public function contrast(int $contrast);
|
||||
|
||||
/**
|
||||
* Applies a grayscale level effect on the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function grayscale();
|
||||
|
||||
/**
|
||||
* Embosses the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function emboss();
|
||||
|
||||
/**
|
||||
* Smooths the image.
|
||||
*
|
||||
* @param int $p value between [-10, 10]
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function smooth(int $p);
|
||||
|
||||
/**
|
||||
* Sharpens the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function sharp();
|
||||
|
||||
/**
|
||||
* Applies edge filtering to the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function edge();
|
||||
|
||||
/**
|
||||
* Colorizes the image.
|
||||
*
|
||||
* @param int $red Value in range [-255, 255]
|
||||
* @param int $green Value in range [-255, 255]
|
||||
* @param int $blue Value in range [-255, 255]
|
||||
* @return $this
|
||||
*/
|
||||
public function colorize(int $red, int $green, int $blue);
|
||||
|
||||
/**
|
||||
* Applies sepia toning to the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function sepia();
|
||||
|
||||
/**
|
||||
* Merges the image with another image.
|
||||
*
|
||||
* @param ImageAdapterInterface $other
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return $this
|
||||
*/
|
||||
public function merge(ImageAdapterInterface $other, int $x = 0, int $y = 0, int $width = 0, int $height = 0);
|
||||
|
||||
/**
|
||||
* Rotates the image.
|
||||
*
|
||||
* @param float $angle
|
||||
* @param int|null $background Image background, null if transparent
|
||||
* @return $this
|
||||
*/
|
||||
public function rotate(float $angle, ?int $background = 0xffffff);
|
||||
|
||||
/**
|
||||
* Fills the image with color.
|
||||
*
|
||||
* @param int $color
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @return $this
|
||||
*/
|
||||
public function fill(int $color = 0xffffff, int $x = 0, int $y = 0);
|
||||
|
||||
/**
|
||||
* Writes text to the image.
|
||||
*
|
||||
* @param string $font
|
||||
* @param string $text
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param float $size
|
||||
* @param float $angle
|
||||
* @param int $color
|
||||
* @param string $align
|
||||
* @return $this
|
||||
*/
|
||||
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');
|
||||
|
||||
/**
|
||||
* Draws a rectangle.
|
||||
*
|
||||
* @param int $x1
|
||||
* @param int $y1
|
||||
* @param int $x2
|
||||
* @param int $y2
|
||||
* @param int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function rectangle(int $x1, int $y1, int $x2, int $y2, int $color, bool $filled = false);
|
||||
|
||||
/**
|
||||
* Draws a rounded rectangle.
|
||||
*
|
||||
* @param int $x1
|
||||
* @param int $y1
|
||||
* @param int $x2
|
||||
* @param int $y2
|
||||
* @param int $radius
|
||||
* @param int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function roundedRectangle(int $x1, int $y1, int $x2, int $y2, int $radius, int $color, bool $filled = false);
|
||||
|
||||
/**
|
||||
* Draws a line.
|
||||
*
|
||||
* @param int $x1
|
||||
* @param int $y1
|
||||
* @param int $x2
|
||||
* @param int $y2
|
||||
* @param int $color
|
||||
* @return $this
|
||||
*/
|
||||
public function line(int $x1, int $y1, int $x2, int $y2, int $color = 0x000000);
|
||||
|
||||
/**
|
||||
* Draws an ellipse.
|
||||
*
|
||||
* @param int $cx
|
||||
* @param int $cy
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function ellipse(int $cx, int $cy, int $width, int $height, int $color = 0x000000, bool $filled = false);
|
||||
|
||||
/**
|
||||
* Draws a circle.
|
||||
*
|
||||
* @param int $cx
|
||||
* @param int $cy
|
||||
* @param int $r
|
||||
* @param int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function circle(int $cx, int $cy, int $r, int $color = 0x000000, bool $filled = false);
|
||||
|
||||
/**
|
||||
* Draws a polygon.
|
||||
*
|
||||
* @param array $points
|
||||
* @param int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function polygon(array $points, int $color, bool $filled = false);
|
||||
|
||||
/**
|
||||
* Flips the image.
|
||||
*
|
||||
* @param bool $flipVertical
|
||||
* @param bool $flipHorizontal
|
||||
* @return $this
|
||||
*/
|
||||
public function flip(bool $flipVertical, bool $flipHorizontal);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Contracts\Image;
|
||||
|
||||
/**
|
||||
* Image information interface.
|
||||
*/
|
||||
interface ImageInfoInterface
|
||||
{
|
||||
/**
|
||||
* Get image width.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function width(): int;
|
||||
|
||||
/**
|
||||
* Get image height.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function height(): int;
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Contracts\Image;
|
||||
|
||||
use Grav\Framework\Image\Image;
|
||||
|
||||
/**
|
||||
* Image operations interface.
|
||||
*/
|
||||
interface ImageOperationsInterface extends ImageInfoInterface
|
||||
{
|
||||
/**
|
||||
* Works as resize() excepts that the layout will be cropped.
|
||||
*
|
||||
* @param string|int|null $width New width
|
||||
* @param string|int|null $height New height
|
||||
* @param string|int $background Background color
|
||||
* @return $this
|
||||
*/
|
||||
public function cropResize($width = null, $height = null, $background = 0xffffff);
|
||||
|
||||
/**
|
||||
* Resizes the image preserving scale. Can enlarge it.
|
||||
*
|
||||
* @param string|int|null $width New width
|
||||
* @param string|int|null $height New height
|
||||
* @param string|int $background Background color
|
||||
* @param bool $crop
|
||||
* @return $this
|
||||
*/
|
||||
public function scaleResize($width = null, $height = null, $background = 0xffffff, bool $crop = false);
|
||||
|
||||
/**
|
||||
* Resizes the image forcing the destination to have exactly the given width and the height.
|
||||
*
|
||||
* @param string|int|null $width New width
|
||||
* @param string|int|null $height New height
|
||||
* @param string|int $background Background color
|
||||
* @return $this
|
||||
*/
|
||||
public function forceResize($width = null, $height = null, $background = 0xffffff);
|
||||
|
||||
/**
|
||||
* Resizes the image. It will never be enlarged.
|
||||
*
|
||||
* @param string|int|null $width New width
|
||||
* @param string|int|null $height New height
|
||||
* @param string|int $background Background color
|
||||
* @param bool $force
|
||||
* @param bool $rescale
|
||||
* @param bool $crop
|
||||
* @return $this
|
||||
*/
|
||||
public function resize($width = null, $height = null, $background = 0xffffff, bool $force = false, bool $rescale = false, bool $crop = false);
|
||||
|
||||
/**
|
||||
* Perform a zoom crop of the image to desired width and height.
|
||||
*
|
||||
* @param string|int|null $width New width
|
||||
* @param string|int|null $height New height
|
||||
* @param string|int $background Background color
|
||||
* @param string|int $xPosLetter
|
||||
* @param string|int $yPosLetter
|
||||
* @return $this
|
||||
*/
|
||||
public function zoomCrop($width, $height, $background = 0xffffff, $xPosLetter = 'center', $yPosLetter = 'center');
|
||||
|
||||
/**
|
||||
* Crops the image.
|
||||
*
|
||||
* @param int $x the top-left x position of the crop box
|
||||
* @param int $y the top-left y position of the crop box
|
||||
* @param int $width the width of the crop box
|
||||
* @param int $height the height of the crop box
|
||||
* @return $this
|
||||
*/
|
||||
public function crop(int $x, int $y, int $width, int $height);
|
||||
|
||||
/**
|
||||
* Read exif rotation from file and apply it.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function fixOrientation();
|
||||
|
||||
/**
|
||||
* Apply orientation using Exif orientation value.
|
||||
*
|
||||
* @param int $exif_orientation
|
||||
* @return $this
|
||||
*/
|
||||
public function applyExifOrientation(int $exif_orientation);
|
||||
|
||||
/**
|
||||
* enable progressive image loading.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function enableProgressive();
|
||||
|
||||
/**
|
||||
* Fills the image background to $bg if the image is transparent.
|
||||
*
|
||||
* @param string|int $background background color
|
||||
* @return $this
|
||||
*/
|
||||
public function fillBackground($background = 0xffffff);
|
||||
|
||||
/**
|
||||
* Negates the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function negate();
|
||||
|
||||
/**
|
||||
* Changes the brightness of the image.
|
||||
*
|
||||
* @param int $brightness the brightness
|
||||
* @return $this
|
||||
*/
|
||||
public function brightness(int $brightness);
|
||||
|
||||
/**
|
||||
* Contrasts the image.
|
||||
*
|
||||
* @param int $contrast the contrast [-100, 100]
|
||||
* @return $this
|
||||
*/
|
||||
public function contrast(int $contrast);
|
||||
|
||||
/**
|
||||
* Apply a grayscale level effect on the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function grayscale();
|
||||
|
||||
/**
|
||||
* Emboss the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function emboss();
|
||||
|
||||
/**
|
||||
* Smooth the image.
|
||||
*
|
||||
* @param int $p value between [-10,10]
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function smooth(int $p);
|
||||
|
||||
/**
|
||||
* Sharpens the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function sharp();
|
||||
|
||||
/**
|
||||
* Edges the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function edge();
|
||||
|
||||
/**
|
||||
* Colorize the image.
|
||||
*
|
||||
* @param int $red value in range [-255, 255]
|
||||
* @param int $green value in range [-255, 255]
|
||||
* @param int $blue value in range [-255, 255]
|
||||
* @return $this
|
||||
*/
|
||||
public function colorize(int $red, int $green, int $blue);
|
||||
|
||||
/**
|
||||
* apply sepia to the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function sepia();
|
||||
|
||||
/**
|
||||
* Merge with another image.
|
||||
*
|
||||
* @param Image $other
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return $this
|
||||
*/
|
||||
public function merge(Image $other, int $x = 0, int $y = 0, int $width = 0, int $height = 0);
|
||||
|
||||
/**
|
||||
* Rotate the image.
|
||||
*
|
||||
* @param float $angle
|
||||
* @param string|int $background
|
||||
* @return $this
|
||||
*/
|
||||
public function rotate(float $angle, $background = 0xffffff);
|
||||
|
||||
/**
|
||||
* Fills the image.
|
||||
*
|
||||
* @param string|int $color
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @return $this
|
||||
*/
|
||||
public function fill($color = 0xffffff, int $x = 0, int $y = 0);
|
||||
|
||||
/**
|
||||
* write text to the image.
|
||||
*
|
||||
* @param string $font
|
||||
* @param string $text
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param float $size
|
||||
* @param float $angle
|
||||
* @param string|int $color
|
||||
* @param string $align
|
||||
* @return $this
|
||||
*/
|
||||
public function write(string $font, string $text, int $x = 0, int $y = 0, float $size = 12.0, float $angle = 0.0, $color = 0x000000, string $align = 'left');
|
||||
|
||||
/**
|
||||
* Draws a rectangle.
|
||||
*
|
||||
* @param int $x1
|
||||
* @param int $y1
|
||||
* @param int $x2
|
||||
* @param int $y2
|
||||
* @param string|int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function rectangle(int $x1, int $y1, int $x2, int $y2, $color, bool $filled = false);
|
||||
|
||||
/**
|
||||
* Draws a rounded rectangle.
|
||||
*
|
||||
* @param int $x1
|
||||
* @param int $y1
|
||||
* @param int $x2
|
||||
* @param int $y2
|
||||
* @param int $radius
|
||||
* @param string|int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function roundedRectangle(int $x1, int $y1, int $x2, int $y2, int $radius, $color, bool $filled = false);
|
||||
|
||||
/**
|
||||
* Draws a line.
|
||||
*
|
||||
* @param int $x1
|
||||
* @param int $y1
|
||||
* @param int $x2
|
||||
* @param int $y2
|
||||
* @param string|int $color
|
||||
* @return $this
|
||||
*/
|
||||
public function line(int $x1, int $y1, int $x2, int $y2, $color = 0x000000);
|
||||
|
||||
/**
|
||||
* Draws an ellipse.
|
||||
*
|
||||
* @param int $cx
|
||||
* @param int $cy
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param string|int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function ellipse(int $cx, int $cy, int $width, int $height, $color = 0x000000, bool $filled = false);
|
||||
|
||||
/**
|
||||
* Draws a circle.
|
||||
*
|
||||
* @param int $cx
|
||||
* @param int $cy
|
||||
* @param int $r
|
||||
* @param string|int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function circle(int $cx, int $cy, int $r, $color = 0x000000, bool $filled = false);
|
||||
|
||||
/**
|
||||
* Draws a polygon.
|
||||
*
|
||||
* @param array $points
|
||||
* @param string|int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function polygon(array $points, $color, bool $filled = false);
|
||||
|
||||
/**
|
||||
* Flips the image.
|
||||
*
|
||||
* @param bool $flipVertical
|
||||
* @param bool $flipHorizontal
|
||||
* @return $this
|
||||
*/
|
||||
public function flip(bool $flipVertical, bool $flipHorizontal);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Contracts\Image;
|
||||
|
||||
/**
|
||||
* Image save interface.
|
||||
*/
|
||||
interface ImageSaveInterface
|
||||
{
|
||||
/**
|
||||
* Save the image as a gif.
|
||||
*
|
||||
* @param string $file
|
||||
* @return $this
|
||||
*/
|
||||
public function saveGif(string $file);
|
||||
|
||||
/**
|
||||
* Save the image as a png.
|
||||
*
|
||||
* @param string $file
|
||||
* @return $this
|
||||
*/
|
||||
public function savePng(string $file);
|
||||
|
||||
/**
|
||||
* Save the image as a Webp.
|
||||
*
|
||||
* @param string $file
|
||||
* @param int $quality
|
||||
* @return $this
|
||||
*/
|
||||
public function saveWebp(string $file, int $quality);
|
||||
|
||||
/**
|
||||
* Save the image as a jpeg.
|
||||
*
|
||||
* @param string $file
|
||||
* @param int $quality
|
||||
* @return $this
|
||||
*/
|
||||
public function saveJpeg(string $file, int $quality);
|
||||
}
|
||||
65
system/src/Grav/Framework/Image/Adapter/Adapter.php
Normal file
65
system/src/Grav/Framework/Image/Adapter/Adapter.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Image\Adapter;
|
||||
|
||||
use Grav\Framework\Contracts\Image\ImageAdapterInterface;
|
||||
|
||||
/**
|
||||
* Abstract Image Adapter.
|
||||
*/
|
||||
abstract class Adapter implements ImageAdapterInterface
|
||||
{
|
||||
/** @var int */
|
||||
protected $orientation = 1;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fixOrientation()
|
||||
{
|
||||
return $this->applyExifOrientation($this->orientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyExifOrientation(int $exif_orientation)
|
||||
{
|
||||
switch ($exif_orientation) {
|
||||
case 1: // do nothing
|
||||
break;
|
||||
|
||||
case 2: // horizontal flip
|
||||
$this->flip(false, true);
|
||||
break;
|
||||
|
||||
case 3: // 180 rotate left
|
||||
$this->rotate(180.0);
|
||||
break;
|
||||
|
||||
case 4: // vertical flip
|
||||
$this->flip(true, false);
|
||||
break;
|
||||
|
||||
case 5: // vertical flip + 90 rotate right
|
||||
$this->flip(true, false);
|
||||
$this->rotate(-90.0);
|
||||
break;
|
||||
|
||||
case 6: // 90 rotate right
|
||||
$this->rotate(-90.0);
|
||||
break;
|
||||
|
||||
case 7: // horizontal flip + 90 rotate right
|
||||
$this->flip(false, true);
|
||||
$this->rotate(-90.0);
|
||||
break;
|
||||
|
||||
case 8: // 90 rotate left
|
||||
$this->rotate(90.0);
|
||||
break;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
816
system/src/Grav/Framework/Image/Adapter/GdAdapter.php
Normal file
816
system/src/Grav/Framework/Image/Adapter/GdAdapter.php
Normal file
@@ -0,0 +1,816 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Image\Adapter;
|
||||
|
||||
use Grav\Framework\Contracts\Image\ImageAdapterInterface;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* GD Image adapter.
|
||||
*/
|
||||
class GdAdapter extends Adapter
|
||||
{
|
||||
/** @var array<string,int> */
|
||||
public static $types = [
|
||||
'jpeg' => \IMG_JPG,
|
||||
'jpg' => \IMG_JPG,
|
||||
'gif' => \IMG_GIF,
|
||||
'png' => \IMG_PNG,
|
||||
'webp' => \IMG_WEBP
|
||||
];
|
||||
|
||||
/** @var \GdImage|resource */
|
||||
protected $resource;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isEnabled(): bool
|
||||
{
|
||||
return extension_loaded('gd') && function_exists('gd_info');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isSupported(string $type): bool
|
||||
{
|
||||
$test = self::$types[$type] ?? 0;
|
||||
|
||||
return (bool)(imagetypes() & $test);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \GdImage|resource $resource
|
||||
*/
|
||||
public function __construct($resource)
|
||||
{
|
||||
if (PHP_VERSION_ID > 80000) {
|
||||
if (!$resource instanceof \GdImage) {
|
||||
throw new InvalidArgumentException('Resource has to be GD Image');
|
||||
}
|
||||
} elseif (!is_resource($resource)) {
|
||||
throw new InvalidArgumentException('Resource has to be GD Image');
|
||||
}
|
||||
|
||||
$this->resource = $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'GD';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \GdImage|resource
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
return $this->resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fillBackground(?int $background = 0xffffff)
|
||||
{
|
||||
$w = $this->width();
|
||||
$h = $this->height();
|
||||
$n = imagecreatetruecolor($w, $h);
|
||||
if (!$n) {
|
||||
throw new RuntimeException('Image background color fill failed');
|
||||
}
|
||||
imagefill($n, 0, 0, $this->allocateColor($background));
|
||||
imagecopyresampled($n, $this->resource, 0, 0, 0, 0, $w, $h, $w, $h);
|
||||
imagedestroy($this->resource);
|
||||
$this->resource = $n;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resize(?int $background, int $target_width, int $target_height, int $new_width, int $new_height)
|
||||
{
|
||||
$width = $this->width();
|
||||
$height = $this->height();
|
||||
$n = imagecreatetruecolor($target_width, $target_height);
|
||||
if (!$n) {
|
||||
throw new RuntimeException('Failed to resize image: image creation failed');
|
||||
}
|
||||
|
||||
if ($background !== null) {
|
||||
imagefill($n, 0, 0, $this->allocateColor($background));
|
||||
} else {
|
||||
imagealphablending($n, false);
|
||||
$color = $this->allocateColor(null);
|
||||
|
||||
imagefill($n, 0, 0, $color);
|
||||
imagesavealpha($n, true);
|
||||
}
|
||||
|
||||
imagecopyresampled($n, $this->resource, ($target_width - $new_width) / 2, ($target_height - $new_height) / 2, 0, 0, $new_width, $new_height, $width, $height);
|
||||
imagedestroy($this->resource);
|
||||
|
||||
$this->resource = $n;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function crop(int $x, int $y, int $width, int $height)
|
||||
{
|
||||
$destination = imagecreatetruecolor($width, $height);
|
||||
if (!$destination) {
|
||||
throw new RuntimeException('Image crop failed');
|
||||
}
|
||||
|
||||
imagealphablending($destination, false);
|
||||
imagesavealpha($destination, true);
|
||||
imagecopy($destination, $this->resource, 0, 0, $x, $y, $this->width(), $this->height());
|
||||
imagedestroy($this->resource);
|
||||
|
||||
$this->resource = $destination;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function negate()
|
||||
{
|
||||
imagefilter($this->resource, IMG_FILTER_NEGATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function brightness($brightness)
|
||||
{
|
||||
imagefilter($this->resource, IMG_FILTER_BRIGHTNESS, $brightness);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function contrast($contrast)
|
||||
{
|
||||
imagefilter($this->resource, IMG_FILTER_CONTRAST, $contrast);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function grayscale()
|
||||
{
|
||||
imagefilter($this->resource, IMG_FILTER_GRAYSCALE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function emboss()
|
||||
{
|
||||
imagefilter($this->resource, IMG_FILTER_EMBOSS);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function smooth(int $p)
|
||||
{
|
||||
imagefilter($this->resource, IMG_FILTER_SMOOTH, $p);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sharp()
|
||||
{
|
||||
imagefilter($this->resource, IMG_FILTER_MEAN_REMOVAL);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function edge()
|
||||
{
|
||||
imagefilter($this->resource, IMG_FILTER_EDGEDETECT);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function colorize(int $red, int $green, int $blue)
|
||||
{
|
||||
imagefilter($this->resource, IMG_FILTER_COLORIZE, $red, $green, $blue);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sepia()
|
||||
{
|
||||
imagefilter($this->resource, IMG_FILTER_GRAYSCALE);
|
||||
imagefilter($this->resource, IMG_FILTER_COLORIZE, 100, 50, 0);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gaussianBlur(int $blurFactor = 1)
|
||||
{
|
||||
if ($blurFactor < 1) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$originalWidth = $this->width();
|
||||
$originalHeight = $this->height();
|
||||
|
||||
$smallestWidth = (int)ceil($originalWidth * (0.5 ** $blurFactor));
|
||||
$smallestHeight = (int)ceil($originalHeight * (0.5 ** $blurFactor));
|
||||
|
||||
// for the first run, the previous image is the original input
|
||||
$prevImage = $this->resource;
|
||||
$prevWidth = $originalWidth;
|
||||
$prevHeight = $originalHeight;
|
||||
|
||||
// scale way down and gradually scale back up, blurring all the way
|
||||
for ($i = 0; $i < $blurFactor; ++$i) {
|
||||
// determine dimensions of next image
|
||||
$nextWidth = (int)($smallestWidth * (2 ** $i));
|
||||
$nextHeight = (int)($smallestHeight * (2 ** $i));
|
||||
|
||||
// resize previous image to next size
|
||||
$nextImage = imagecreatetruecolor($nextWidth, $nextHeight);
|
||||
if (!$nextImage) {
|
||||
throw new RuntimeException('Image gaussian blur failed');
|
||||
}
|
||||
imagecopyresized($nextImage, $prevImage, 0, 0, 0, 0,
|
||||
$nextWidth, $nextHeight, $prevWidth, $prevHeight);
|
||||
|
||||
// apply blur filter
|
||||
imagefilter($nextImage, IMG_FILTER_GAUSSIAN_BLUR);
|
||||
|
||||
// now the new image becomes the previous image for the next step
|
||||
$prevImage = $nextImage;
|
||||
$prevWidth = $nextWidth;
|
||||
$prevHeight = $nextHeight;
|
||||
}
|
||||
|
||||
// scale back to original size and blur one more time
|
||||
imagecopyresized($this->resource, $nextImage,
|
||||
0, 0, 0, 0, $originalWidth, $originalHeight, $nextWidth, $nextHeight);
|
||||
imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR);
|
||||
|
||||
// clean up
|
||||
imagedestroy($prevImage);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param GdAdapter $other
|
||||
*/
|
||||
public function merge(ImageAdapterInterface $other, int $x = 0, int $y = 0, int $width = null, int $height = null)
|
||||
{
|
||||
if (!$other instanceof self) {
|
||||
throw new InvalidArgumentException('Image to be merged needs to be instance of GdAdapter');
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rotate(float $angle, ?int $background = 0xffffff)
|
||||
{
|
||||
$resource = imagerotate($this->resource, $angle, $this->allocateColor($background));
|
||||
if (!$resource) {
|
||||
throw new RuntimeException('Image rotate failed');
|
||||
}
|
||||
|
||||
$this->resource = $resource;
|
||||
imagealphablending($this->resource, true);
|
||||
imagesavealpha($this->resource, true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fill(int $color = 0xffffff, int $x = 0, int $y = 0)
|
||||
{
|
||||
imagealphablending($this->resource, false);
|
||||
imagefill($this->resource, $x, $y, $this->allocateColor($color));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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')
|
||||
{
|
||||
imagealphablending($this->resource, true);
|
||||
|
||||
if ($align !== 'left') {
|
||||
$sim_size = $this->getTTFBox($font, $text, $size, $angle);
|
||||
|
||||
if ($align === 'center') {
|
||||
$x -= $sim_size['width'] / 2;
|
||||
}
|
||||
|
||||
if ($align === 'right') {
|
||||
$x -= $sim_size['width'];
|
||||
}
|
||||
}
|
||||
|
||||
imagettftext($this->resource, $size, $angle, $x, $y, $this->allocateColor($color), $font, $text);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rectangle(int $x1, int $y1, int $x2, int $y2, int $color, bool $filled = false)
|
||||
{
|
||||
$c = $this->allocateColor($color);
|
||||
if ($filled) {
|
||||
imagefilledrectangle($this->resource, $x1, $y1, $x2, $y2, $c);
|
||||
} else {
|
||||
imagerectangle($this->resource, $x1, $y1, $x2, $y2, $c);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function roundedRectangle(int $x1, int $y1, int $x2, int $y2, int $radius, int $color, bool $filled = false)
|
||||
{
|
||||
$c = $this->allocateColor($color);
|
||||
|
||||
if ($filled) {
|
||||
imagefilledrectangle($this->resource, $x1 + $radius, $y1, $x2 - $radius, $y2, $c);
|
||||
imagefilledrectangle($this->resource, $x1, $y1 + $radius, $x1 + $radius - 1, $y2 - $radius, $c);
|
||||
imagefilledrectangle($this->resource, $x2 - $radius + 1, $y1 + $radius, $x2, $y2 - $radius, $c);
|
||||
|
||||
imagefilledarc($this->resource, $x1 + $radius, $y1 + $radius, $radius * 2, $radius * 2, 180, 270, $c, IMG_ARC_PIE);
|
||||
imagefilledarc($this->resource, $x2 - $radius, $y1 + $radius, $radius * 2, $radius * 2, 270, 360, $c, IMG_ARC_PIE);
|
||||
imagefilledarc($this->resource, $x1 + $radius, $y2 - $radius, $radius * 2, $radius * 2, 90, 180, $c, IMG_ARC_PIE);
|
||||
imagefilledarc($this->resource, $x2 - $radius, $y2 - $radius, $radius * 2, $radius * 2, 360, 90, $c, IMG_ARC_PIE);
|
||||
} else {
|
||||
imageline($this->resource, $x1 + $radius, $y1, $x2 - $radius, $y1, $c);
|
||||
imageline($this->resource, $x1 + $radius, $y2, $x2 - $radius, $y2, $c);
|
||||
imageline($this->resource, $x1, $y1 + $radius, $x1, $y2 - $radius, $c);
|
||||
imageline($this->resource, $x2, $y1 + $radius, $x2, $y2 - $radius, $c);
|
||||
|
||||
imagearc($this->resource, $x1 + $radius, $y1 + $radius, $radius * 2, $radius * 2, 180, 270, $c);
|
||||
imagearc($this->resource, $x2 - $radius, $y1 + $radius, $radius * 2, $radius * 2, 270, 360, $c);
|
||||
imagearc($this->resource, $x1 + $radius, $y2 - $radius, $radius * 2, $radius * 2, 90, 180, $c);
|
||||
imagearc($this->resource, $x2 - $radius, $y2 - $radius, $radius * 2, $radius * 2, 360, 90, $c);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function line(int $x1, int $y1, int $x2, int $y2, $color = 0x000000)
|
||||
{
|
||||
imageline($this->resource, $x1, $y1, $x2, $y2, $this->allocateColor($color));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function ellipse(int $cx, int $cy, int $width, int $height, $color = 0x000000, bool $filled = false)
|
||||
{
|
||||
$c = $this->allocateColor($color);
|
||||
if ($filled) {
|
||||
imagefilledellipse($this->resource, $cx, $cy, $width, $height, $c);
|
||||
} else {
|
||||
imageellipse($this->resource, $cx, $cy, $width, $height, $c);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function circle(int $cx, int $cy, int $r, $color = 0x000000, bool $filled = false)
|
||||
{
|
||||
return $this->ellipse($cx, $cy, $r, $r, $this->allocateColor($color), $filled);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function polygon(array $points, $color, bool $filled = false)
|
||||
{
|
||||
$num = (int)(count($points) / 2);
|
||||
$c = $this->allocateColor($color);
|
||||
|
||||
if ($filled) {
|
||||
imagefilledpolygon($this->resource, $points, $num, $c);
|
||||
} else {
|
||||
imagepolygon($this->resource, $points, $num, $c);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function flip(bool $flipVertical, bool $flipHorizontal)
|
||||
{
|
||||
if (!$flipVertical && !$flipHorizontal) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (function_exists('imageflip')) {
|
||||
if ($flipVertical && $flipHorizontal) {
|
||||
$flipMode = \IMG_FLIP_BOTH;
|
||||
} elseif ($flipVertical && !$flipHorizontal) {
|
||||
$flipMode = \IMG_FLIP_VERTICAL;
|
||||
} elseif (!$flipVertical && $flipHorizontal) {
|
||||
$flipMode = \IMG_FLIP_HORIZONTAL;
|
||||
}
|
||||
|
||||
if (isset($flipMode)) {
|
||||
imageflip($this->resource, $flipMode);
|
||||
}
|
||||
} else {
|
||||
$width = $this->width();
|
||||
$height = $this->height();
|
||||
|
||||
$src_x = 0;
|
||||
$src_y = 0;
|
||||
$src_width = $width;
|
||||
$src_height = $height;
|
||||
|
||||
if ($flipVertical) {
|
||||
$src_y = $height - 1;
|
||||
$src_height = -$height;
|
||||
}
|
||||
|
||||
if ($flipHorizontal) {
|
||||
$src_x = $width - 1;
|
||||
$src_width = -$width;
|
||||
}
|
||||
|
||||
$imgdest = imagecreatetruecolor($width, $height);
|
||||
if (!$imgdest) {
|
||||
throw new RuntimeException('Image flip failed');
|
||||
}
|
||||
|
||||
imagealphablending($imgdest, false);
|
||||
imagesavealpha($imgdest, true);
|
||||
|
||||
if (imagecopyresampled($imgdest, $this->resource, 0, 0, $src_x, $src_y, $width, $height, $src_width, $src_height)) {
|
||||
imagedestroy($this->resource);
|
||||
$this->resource = $imgdest;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function width(): int
|
||||
{
|
||||
return imagesx($this->resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function height(): int
|
||||
{
|
||||
return imagesy($this->resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveGif(string $filepath)
|
||||
{
|
||||
$transColor = imagecolorallocatealpha($this->resource, 255, 255, 255, 127);
|
||||
if (!$transColor) {
|
||||
throw new RuntimeException('Image save failed');
|
||||
}
|
||||
|
||||
imagecolortransparent($this->resource, $transColor);
|
||||
imagegif($this->resource, $filepath);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function savePng(string $filepath)
|
||||
{
|
||||
imagepng($this->resource, $filepath);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveWebp(string $filepath, int $quality)
|
||||
{
|
||||
imagewebp($this->resource, $filepath, $quality);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveJpeg(string $filepath, int $quality)
|
||||
{
|
||||
imagejpeg($this->resource, $filepath, $quality);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function enableProgressive()
|
||||
{
|
||||
imageinterlace($this->resource, true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create empty image.
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return void
|
||||
*/
|
||||
protected function createImage(int $width, int $height): void
|
||||
{
|
||||
$this->resource = imagecreatetruecolor($width, $height) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create image from a string.
|
||||
*
|
||||
* @param string $data
|
||||
* @return void
|
||||
*/
|
||||
protected function createImageFromString(string $data): void
|
||||
{
|
||||
$this->resource = @imagecreatefromstring($data) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the image to true color.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function convertToTrueColor(): void
|
||||
{
|
||||
if (!imageistruecolor($this->resource)) {
|
||||
imagepalettetotruecolor($this->resource);
|
||||
}
|
||||
|
||||
imagesavealpha($this->resource, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open the file using jpeg.
|
||||
*
|
||||
* @param string $filepath
|
||||
* @return void
|
||||
*/
|
||||
protected function openJpeg(string $filepath): void
|
||||
{
|
||||
if (file_exists($filepath) && filesize($filepath)) {
|
||||
$this->resource = @imagecreatefromjpeg($filepath) ?: null;
|
||||
} else {
|
||||
$this->resource = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open the file using gif.
|
||||
*
|
||||
* @param string $filepath
|
||||
* @return void
|
||||
*/
|
||||
protected function openGif(string $filepath): void
|
||||
{
|
||||
if (file_exists($filepath) && filesize($filepath)) {
|
||||
$this->resource = @imagecreatefromgif($filepath) ?: null;
|
||||
} else {
|
||||
$this->resource = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open the file using PNG.
|
||||
*
|
||||
* @param string $filepath
|
||||
* @return void
|
||||
*/
|
||||
protected function openPng(string $filepath): void
|
||||
{
|
||||
if (file_exists($filepath) && filesize($filepath)) {
|
||||
$this->resource = @imagecreatefrompng($filepath) ?: null;
|
||||
} else {
|
||||
$this->resource = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open the file using WEBP.
|
||||
*
|
||||
* @param string $filepath
|
||||
* @return void
|
||||
*/
|
||||
protected function openWebp(string $filepath): void
|
||||
{
|
||||
if (file_exists($filepath) && filesize($filepath)) {
|
||||
$this->resource = @imagecreatefromwebp($filepath) ?: null;
|
||||
} else {
|
||||
$this->resource = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color in x, y.
|
||||
*
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @return int|false
|
||||
*/
|
||||
protected function getColor(int $x, int $y)
|
||||
{
|
||||
return imagecolorat($this->resource, $x, $y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load image resource.
|
||||
*
|
||||
* @param \GdImage|resource $resource
|
||||
*/
|
||||
protected function loadResource($resource): void
|
||||
{
|
||||
$this->resource = $resource;
|
||||
|
||||
imagesavealpha($this->resource, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load file.
|
||||
*
|
||||
* @param string $filepath
|
||||
* @param string $type
|
||||
* @return void
|
||||
* @throws UnexpectedValueException
|
||||
*/
|
||||
protected function loadFile(string $filepath, string $type): void
|
||||
{
|
||||
if (!static::isSupported($type)) {
|
||||
throw new UnexpectedValueException('Type ' . $type . ' is not supported by GD');
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'jpeg':
|
||||
$this->openJpeg($filepath);
|
||||
break;
|
||||
case 'gif':
|
||||
$this->openGif($filepath);
|
||||
break;
|
||||
case 'png':
|
||||
$this->openPng($filepath);
|
||||
break;
|
||||
case 'webp':
|
||||
$this->openWebp($filepath);
|
||||
break;
|
||||
default:
|
||||
throw new UnexpectedValueException('Unable to open file (' . $filepath . ')');
|
||||
}
|
||||
|
||||
if (null === $this->getResource()) {
|
||||
throw new UnexpectedValueException('Unable to open file (' . $filepath . ')');
|
||||
}
|
||||
|
||||
$this->convertToTrueColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the bounding box of a text using TrueType fonts.
|
||||
*
|
||||
* @param string $font
|
||||
* @param string $text
|
||||
* @param float $size
|
||||
* @param float $angle
|
||||
* @return array
|
||||
*/
|
||||
protected function getTTFBox(string $font, string $text, float $size, float $angle = 0): array
|
||||
{
|
||||
$box = imagettfbbox($size, $angle, $font, $text);
|
||||
if (false === $box) {
|
||||
throw new RuntimeException('Failed to allocate room for text');
|
||||
}
|
||||
|
||||
return [
|
||||
'width' => abs($box[2] - $box[0]),
|
||||
'height' => abs($box[3] - $box[5]),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Allocate color for the image.
|
||||
*
|
||||
* @param int|null $color
|
||||
* @return int
|
||||
*/
|
||||
protected function allocateColor(?int $color): int
|
||||
{
|
||||
$colorRGBA = $color ?? 0x7fffffff;
|
||||
|
||||
$b = ($colorRGBA) & 0xff;
|
||||
$colorRGBA >>= 8;
|
||||
$g = ($colorRGBA) & 0xff;
|
||||
$colorRGBA >>= 8;
|
||||
$r = ($colorRGBA) & 0xff;
|
||||
$colorRGBA >>= 8;
|
||||
$a = ($colorRGBA) & 0xff;
|
||||
|
||||
$c = imagecolorallocatealpha($this->resource, $r, $g, $b, $a);
|
||||
|
||||
if (false !== $c && $color === null) {
|
||||
imagecolortransparent($this->resource, $c);
|
||||
}
|
||||
|
||||
if (false === $c) {
|
||||
throw new RuntimeException('Failed to allocate color');
|
||||
}
|
||||
|
||||
return $c;
|
||||
}
|
||||
}
|
||||
79
system/src/Grav/Framework/Image/Image.php
Normal file
79
system/src/Grav/Framework/Image/Image.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Image;
|
||||
|
||||
use Grav\Framework\Compat\Serializable;
|
||||
use Grav\Framework\Contracts\Image\ImageOperationsInterface;
|
||||
use Grav\Framework\Image\Traits\ImageOperationsTrait;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Image class.
|
||||
*/
|
||||
class Image implements ImageOperationsInterface
|
||||
{
|
||||
use ImageOperationsTrait;
|
||||
use Serializable;
|
||||
|
||||
/** @var string */
|
||||
protected $filepath;
|
||||
/** @var array */
|
||||
protected $info;
|
||||
|
||||
/**
|
||||
* @param string $filepath
|
||||
* @param array $info
|
||||
*/
|
||||
public function __construct(string $filepath, array $info)
|
||||
{
|
||||
$this->filepath = $filepath;
|
||||
$this->width = $info['width'] ?? 0;
|
||||
$this->height = $info['height'] ?? 0;
|
||||
$this->orientation = isset($info['exif']['Orientation']) ? (int)$info['exif']['Orientation'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [
|
||||
'image' => 1,
|
||||
'filepath' => $this->filepath,
|
||||
'info' => $this->info,
|
||||
'width' => $this->width,
|
||||
'height' => $this->height,
|
||||
'orientation' => $this->orientation,
|
||||
'operations' => $this->operations,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$image = $data['image'] ?? null;
|
||||
if ($image !== 1) {
|
||||
throw new RuntimeException('Cannot unserialize image: Version mismatch');
|
||||
}
|
||||
|
||||
$this->filepath = $data['filepath'];
|
||||
$this->info = $data['info'];
|
||||
$this->width = $data['width'];
|
||||
$this->height = $data['height'];
|
||||
$this->orientation = $data['orientation'];
|
||||
$this->operations = $data['operations'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the hash for the image.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateHash(): string
|
||||
{
|
||||
return sha1(serialize($this));
|
||||
}
|
||||
}
|
||||
225
system/src/Grav/Framework/Image/ImageColor.php
Normal file
225
system/src/Grav/Framework/Image/ImageColor.php
Normal file
@@ -0,0 +1,225 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Image;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Image color class.
|
||||
*/
|
||||
class ImageColor
|
||||
{
|
||||
/**
|
||||
* All HTML color names.
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
private static $colors = [
|
||||
'aliceblue' => 0xf0f8ff,
|
||||
'antiquewhite' => 0xfaebd7,
|
||||
'aqua' => 0x00ffff,
|
||||
'aquamarine' => 0x7fffd4,
|
||||
'azure' => 0xf0ffff,
|
||||
'beige' => 0xf5f5dc,
|
||||
'bisque' => 0xffe4c4,
|
||||
'black' => 0x000000,
|
||||
'blanchedalmond' => 0xffebcd,
|
||||
'blue' => 0x0000ff,
|
||||
'blueviolet' => 0x8a2be2,
|
||||
'brown' => 0xa52a2a,
|
||||
'burlywood' => 0xdeb887,
|
||||
'cadetblue' => 0x5f9ea0,
|
||||
'chartreuse' => 0x7fff00,
|
||||
'chocolate' => 0xd2691e,
|
||||
'coral' => 0xff7f50,
|
||||
'cornflowerblue' => 0x6495ed,
|
||||
'cornsilk' => 0xfff8dc,
|
||||
'crimson' => 0xdc143c,
|
||||
'cyan' => 0x00ffff,
|
||||
'darkblue' => 0x00008b,
|
||||
'darkcyan' => 0x008b8b,
|
||||
'darkgoldenrod' => 0xb8860b,
|
||||
'darkgray' => 0xa9a9a9,
|
||||
'darkgreen' => 0x006400,
|
||||
'darkgrey' => 0xa9a9a9,
|
||||
'darkkhaki' => 0xbdb76b,
|
||||
'darkmagenta' => 0x8b008b,
|
||||
'darkolivegreen' => 0x556b2f,
|
||||
'darkorange' => 0xff8c00,
|
||||
'darkorchid' => 0x9932cc,
|
||||
'darkred' => 0x8b0000,
|
||||
'darksalmon' => 0xe9967a,
|
||||
'darkseagreen' => 0x8fbc8f,
|
||||
'darkslateblue' => 0x483d8b,
|
||||
'darkslategray' => 0x2f4f4f,
|
||||
'darkslategrey' => 0x2f4f4f,
|
||||
'darkturquoise' => 0x00ced1,
|
||||
'darkviolet' => 0x9400d3,
|
||||
'deeppink' => 0xff1493,
|
||||
'deepskyblue' => 0x00bfff,
|
||||
'dimgray' => 0x696969,
|
||||
'dimgrey' => 0x696969,
|
||||
'dodgerblue' => 0x1e90ff,
|
||||
'firebrick' => 0xb22222,
|
||||
'floralwhite' => 0xfffaf0,
|
||||
'forestgreen' => 0x228b22,
|
||||
'fuchsia' => 0xff00ff,
|
||||
'gainsboro' => 0xdcdcdc,
|
||||
'ghostwhite' => 0xf8f8ff,
|
||||
'goldenrod' => 0xdaa520,
|
||||
'gold' => 0xffd700,
|
||||
'gray' => 0x808080,
|
||||
'green' => 0x008000,
|
||||
'greenyellow' => 0xadff2f,
|
||||
'grey' => 0x808080,
|
||||
'honeydew' => 0xf0fff0,
|
||||
'hotpink' => 0xff69b4,
|
||||
'indianred' => 0xcd5c5c,
|
||||
'indigo' => 0x4b0082,
|
||||
'ivory' => 0xfffff0,
|
||||
'khaki' => 0xf0e68c,
|
||||
'lavenderblush' => 0xfff0f5,
|
||||
'lavender' => 0xe6e6fa,
|
||||
'lawngreen' => 0x7cfc00,
|
||||
'lemonchiffon' => 0xfffacd,
|
||||
'lightblue' => 0xadd8e6,
|
||||
'lightcoral' => 0xf08080,
|
||||
'lightcyan' => 0xe0ffff,
|
||||
'lightgoldenrodyellow' => 0xfafad2,
|
||||
'lightgray' => 0xd3d3d3,
|
||||
'lightgreen' => 0x90ee90,
|
||||
'lightgrey' => 0xd3d3d3,
|
||||
'lightpink' => 0xffb6c1,
|
||||
'lightsalmon' => 0xffa07a,
|
||||
'lightseagreen' => 0x20b2aa,
|
||||
'lightskyblue' => 0x87cefa,
|
||||
'lightslategray' => 0x778899,
|
||||
'lightslategrey' => 0x778899,
|
||||
'lightsteelblue' => 0xb0c4de,
|
||||
'lightyellow' => 0xffffe0,
|
||||
'lime' => 0x00ff00,
|
||||
'limegreen' => 0x32cd32,
|
||||
'linen' => 0xfaf0e6,
|
||||
'magenta' => 0xff00ff,
|
||||
'maroon' => 0x800000,
|
||||
'mediumaquamarine' => 0x66cdaa,
|
||||
'mediumblue' => 0x0000cd,
|
||||
'mediumorchid' => 0xba55d3,
|
||||
'mediumpurple' => 0x9370db,
|
||||
'mediumseagreen' => 0x3cb371,
|
||||
'mediumslateblue' => 0x7b68ee,
|
||||
'mediumspringgreen' => 0x00fa9a,
|
||||
'mediumturquoise' => 0x48d1cc,
|
||||
'mediumvioletred' => 0xc71585,
|
||||
'midnightblue' => 0x191970,
|
||||
'mintcream' => 0xf5fffa,
|
||||
'mistyrose' => 0xffe4e1,
|
||||
'moccasin' => 0xffe4b5,
|
||||
'navajowhite' => 0xffdead,
|
||||
'navy' => 0x000080,
|
||||
'oldlace' => 0xfdf5e6,
|
||||
'olive' => 0x808000,
|
||||
'olivedrab' => 0x6b8e23,
|
||||
'orange' => 0xffa500,
|
||||
'orangered' => 0xff4500,
|
||||
'orchid' => 0xda70d6,
|
||||
'palegoldenrod' => 0xeee8aa,
|
||||
'palegreen' => 0x98fb98,
|
||||
'paleturquoise' => 0xafeeee,
|
||||
'palevioletred' => 0xdb7093,
|
||||
'papayawhip' => 0xffefd5,
|
||||
'peachpuff' => 0xffdab9,
|
||||
'peru' => 0xcd853f,
|
||||
'pink' => 0xffc0cb,
|
||||
'plum' => 0xdda0dd,
|
||||
'powderblue' => 0xb0e0e6,
|
||||
'purple' => 0x800080,
|
||||
'rebeccapurple' => 0x663399,
|
||||
'red' => 0xff0000,
|
||||
'rosybrown' => 0xbc8f8f,
|
||||
'royalblue' => 0x4169e1,
|
||||
'saddlebrown' => 0x8b4513,
|
||||
'salmon' => 0xfa8072,
|
||||
'sandybrown' => 0xf4a460,
|
||||
'seagreen' => 0x2e8b57,
|
||||
'seashell' => 0xfff5ee,
|
||||
'sienna' => 0xa0522d,
|
||||
'silver' => 0xc0c0c0,
|
||||
'skyblue' => 0x87ceeb,
|
||||
'slateblue' => 0x6a5acd,
|
||||
'slategray' => 0x708090,
|
||||
'slategrey' => 0x708090,
|
||||
'snow' => 0xfffafa,
|
||||
'springgreen' => 0x00ff7f,
|
||||
'steelblue' => 0x4682b4,
|
||||
'tan' => 0xd2b48c,
|
||||
'teal' => 0x008080,
|
||||
'thistle' => 0xd8bfd8,
|
||||
'tomato' => 0xff6347,
|
||||
'turquoise' => 0x40e0d0,
|
||||
'violet' => 0xee82ee,
|
||||
'wheat' => 0xf5deb3,
|
||||
'white' => 0xffffff,
|
||||
'whitesmoke' => 0xf5f5f5,
|
||||
'yellow' => 0xffff00,
|
||||
'yellowgreen' => 0x9acd32
|
||||
];
|
||||
|
||||
/**
|
||||
* Parse color and return integer value of it. Transparent will be converted to null.
|
||||
*
|
||||
* @param string|int|null $color
|
||||
* @return int|null
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function parse($color): ?int
|
||||
{
|
||||
// Direct color representation (0xff0000 or null).
|
||||
if (null === $color || is_int($color)) {
|
||||
return $color;
|
||||
}
|
||||
|
||||
if (is_string($color)) {
|
||||
$color = strtolower($color);
|
||||
|
||||
// Transparent becomes null.
|
||||
if ($color === 'transparent') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Normalize string.
|
||||
$color = str_replace(' ', '', $color);
|
||||
|
||||
// Lookup named colors.
|
||||
if (isset(self::$colors[$color])) {
|
||||
return self::$colors[$color];
|
||||
}
|
||||
|
||||
// Color string ('ff0000', '#ff0000' or '0xfff').
|
||||
if (preg_match('/^(#|0x|)([0-9a-f]{3,6})/u', $color, $matches)) {
|
||||
$col = $matches[2];
|
||||
|
||||
if (strlen($col) === 3) {
|
||||
$r = '';
|
||||
for ($i = 0; $i < 3; ++$i) {
|
||||
$r .= $col[$i] . $col[$i];
|
||||
}
|
||||
|
||||
$col = $r;
|
||||
}
|
||||
|
||||
return (int)hexdec($col);
|
||||
}
|
||||
|
||||
// Colors like 'rgb(255, 0, 0)'
|
||||
if (preg_match('/^rgb\((\d+),(\d+),(\d+)\)/', $color, $matches)) {
|
||||
[,$r,$g,$b] = $matches;
|
||||
if ($r >= 0 && $r <= 0xff && $g >= 0 && $g <= 0xff && $b >= 0 && $b <= 0xff) {
|
||||
return ($r << 16) | ($g << 8) | $b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid color: ' . $color);
|
||||
}
|
||||
}
|
||||
676
system/src/Grav/Framework/Image/Traits/ImageOperationsTrait.php
Normal file
676
system/src/Grav/Framework/Image/Traits/ImageOperationsTrait.php
Normal file
@@ -0,0 +1,676 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\Image\Traits;
|
||||
|
||||
use Grav\Framework\Image\Image;
|
||||
use Grav\Framework\Image\ImageColor;
|
||||
|
||||
/**
|
||||
* Trait to record image operations.
|
||||
*/
|
||||
trait ImageOperationsTrait
|
||||
{
|
||||
/** @var int */
|
||||
protected $width = 0;
|
||||
/** @var int */
|
||||
protected $height = 0;
|
||||
/** @var int|null */
|
||||
protected $orientation = null;
|
||||
/** @var array */
|
||||
protected $operations = [];
|
||||
|
||||
/**
|
||||
* Image width.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function width(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Image height.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function height(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Works as resize() excepts that the layout will be cropped.
|
||||
*
|
||||
* @param string|int|null $width the width
|
||||
* @param string|int|null $height he height
|
||||
* @param string|int $background the background
|
||||
* @return $this
|
||||
*/
|
||||
public function cropResize($width = null, $height = null, $background = 0xffffff)
|
||||
{
|
||||
return $this->resize($width, $height, $background, false, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the image preserving scale. Can enlarge it.
|
||||
*
|
||||
* @param string|int|null $width the width
|
||||
* @param string|int|null $height the height
|
||||
* @param string|int $background the background
|
||||
* @param bool $crop
|
||||
* @return $this
|
||||
*/
|
||||
public function scaleResize($width = null, $height = null, $background = 0xffffff, bool $crop = false)
|
||||
{
|
||||
return $this->resize($width, $height, $background, false, true, $crop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the image forcing the destination to have exactly the given width and the height.
|
||||
*
|
||||
* @param string|int|null $width the width
|
||||
* @param string|int|null $height the height
|
||||
* @param string|int $background the background
|
||||
* @return $this
|
||||
*/
|
||||
public function forceResize($width = null, $height = null, $background = 0xffffff)
|
||||
{
|
||||
return $this->resize($width, $height, $background, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the image. It will never be enlarged.
|
||||
*
|
||||
* @param string|int|null $width the width
|
||||
* @param string|int|null $height the height
|
||||
* @param string|int $background the background
|
||||
* @param bool $force
|
||||
* @param bool $rescale
|
||||
* @param bool $crop
|
||||
* @return $this
|
||||
*/
|
||||
public function resize($width = null, $height = null, $background = 0xffffff, bool $force = false, bool $rescale = false, bool $crop = false)
|
||||
{
|
||||
[$width, $height] = $this->getSize($width, $height);
|
||||
if ($width < 0 || $height < 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$bg = ImageColor::parse($background);
|
||||
|
||||
$current_width = $this->width();
|
||||
$current_height = $this->height();
|
||||
$new_width = 0;
|
||||
$new_height = 0;
|
||||
$scale = 1.0;
|
||||
|
||||
if (!$rescale && (!$force || $crop)) {
|
||||
if ($width !== 0 && $current_width > $width) {
|
||||
$scale = $current_width / $width;
|
||||
}
|
||||
|
||||
if ($height !== 0 && $current_height > $height && $current_height / $height > $scale) {
|
||||
$scale = $current_height / $height;
|
||||
}
|
||||
} else {
|
||||
if ($width !== 0) {
|
||||
$scale = $current_width / $width;
|
||||
$new_width = $width;
|
||||
}
|
||||
|
||||
if ($height !== 0) {
|
||||
if ($width !== 0 && $rescale) {
|
||||
$scale = max($scale, $current_height / $height);
|
||||
} else {
|
||||
$scale = $current_height / $height;
|
||||
}
|
||||
$new_height = $height;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$force || $width === 0 || $rescale) {
|
||||
$new_width = (int)round($current_width / $scale);
|
||||
}
|
||||
|
||||
if (!$force || $height === 0 || $rescale) {
|
||||
$new_height = (int)round($current_height / $scale);
|
||||
}
|
||||
|
||||
if ($width === 0 || $crop) {
|
||||
$width = $new_width;
|
||||
}
|
||||
|
||||
if ($height === 0 || $crop) {
|
||||
$height = $new_height;
|
||||
}
|
||||
|
||||
$this->operations[] = ['resize', [$bg, $width, $height, $new_width, $new_height]];
|
||||
|
||||
// Update image size.
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a zoom crop of the image to desired width and height.
|
||||
*
|
||||
* @param string|int|null $width Desired width
|
||||
* @param string|int|null $height Desired height
|
||||
* @param string|int $background
|
||||
* @param string|int $xPosLetter
|
||||
* @param string|int $yPosLetter
|
||||
* @return $this
|
||||
*/
|
||||
public function zoomCrop($width, $height, $background = 0xffffff, $xPosLetter = 'center', $yPosLetter = 'center')
|
||||
{
|
||||
[$width, $height] = $this->getSize($width, $height);
|
||||
if ($width <= 0 || $height <= 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$bg = ImageColor::parse($background);
|
||||
|
||||
$originalWidth = $this->width();
|
||||
$originalHeight = $this->height();
|
||||
|
||||
// Calculate the different ratios
|
||||
$originalRatio = $originalWidth / $originalHeight;
|
||||
$newRatio = $width / $height;
|
||||
|
||||
// Compare ratios
|
||||
if ($originalRatio > $newRatio) {
|
||||
// Original image is wider
|
||||
$newHeight = $height;
|
||||
$newWidth = (int)($height * $originalRatio);
|
||||
} else {
|
||||
// Equal width or smaller
|
||||
$newHeight = (int)($width / $originalRatio);
|
||||
$newWidth = $width;
|
||||
}
|
||||
|
||||
// Perform resize
|
||||
$this->resize($newWidth, $newHeight, $bg, true);
|
||||
|
||||
// Define x position
|
||||
switch ($xPosLetter) {
|
||||
case 'L':
|
||||
case 'left':
|
||||
$xPos = 0;
|
||||
break;
|
||||
case 'R':
|
||||
case 'right':
|
||||
$xPos = $newWidth - $width;
|
||||
break;
|
||||
case 'C':
|
||||
case 'center':
|
||||
$xPos = (int)(($newWidth - $width) / 2);
|
||||
break;
|
||||
default:
|
||||
$factorW = $newWidth / $originalWidth;
|
||||
$xPos = (int)((int)$xPosLetter * $factorW);
|
||||
|
||||
// If the desired cropping position goes beyond the width then
|
||||
// set the crop to be within the correct bounds.
|
||||
if ($xPos + $width > $newWidth) {
|
||||
$xPos = $newWidth - $width;
|
||||
}
|
||||
}
|
||||
|
||||
// Define y position
|
||||
switch ($yPosLetter) {
|
||||
case 'T':
|
||||
case 'top':
|
||||
$yPos = 0;
|
||||
break;
|
||||
case 'B':
|
||||
case 'bottom':
|
||||
$yPos = $newHeight - $height;
|
||||
break;
|
||||
case 'C':
|
||||
case 'center':
|
||||
$yPos = (int)(($newHeight - $height) / 2);
|
||||
break;
|
||||
default:
|
||||
$factorH = $newHeight / $originalHeight;
|
||||
$yPos = (int)((int)$yPosLetter * $factorH);
|
||||
|
||||
// If the desired cropping position goes beyond the height then
|
||||
// set the crop to be within the correct bounds.
|
||||
if ($yPos + $height > $newHeight) {
|
||||
$yPos = $newHeight - $height;
|
||||
}
|
||||
}
|
||||
|
||||
// Crop image to reach desired size
|
||||
$this->crop($xPos, $yPos, $width, $height);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crops the image.
|
||||
*
|
||||
* @param int $x the top-left x position of the crop box
|
||||
* @param int $y the top-left y position of the crop box
|
||||
* @param int $width the width of the crop box
|
||||
* @param int $height the height of the crop box
|
||||
* @return $this
|
||||
*/
|
||||
public function crop(int $x, int $y, int $width, int $height)
|
||||
{
|
||||
$this->operations[] = ['crop', [$x, $y, $width, $height]];
|
||||
|
||||
// Update image size.
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read exif rotation from file and apply it.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function fixOrientation()
|
||||
{
|
||||
if (null !== $this->orientation) {
|
||||
return $this->applyExifOrientation($this->orientation);
|
||||
}
|
||||
|
||||
$this->operations[] = ['fixOrientation', []];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply orientation using Exif orientation value.
|
||||
*
|
||||
* @param int $exif_orientation
|
||||
* @return $this
|
||||
*/
|
||||
public function applyExifOrientation(int $exif_orientation)
|
||||
{
|
||||
$this->operations[] = ['applyExifOrientation', [$exif_orientation]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* enable progressive image loading.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function enableProgressive()
|
||||
{
|
||||
$this->operations[] = ['enableProgressive', []];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the image background to $bg if the image is transparent.
|
||||
*
|
||||
* @param string|int $background background color
|
||||
* @return $this
|
||||
*/
|
||||
public function fillBackground($background = 0xffffff)
|
||||
{
|
||||
$bg = ImageColor::parse($background);
|
||||
|
||||
$this->operations[] = ['fillBackground', [$bg]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Negates the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function negate()
|
||||
{
|
||||
$this->operations[] = ['negate', []];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the brightness of the image.
|
||||
*
|
||||
* @param int $brightness the brightness
|
||||
* @return $this
|
||||
*/
|
||||
public function brightness(int $brightness)
|
||||
{
|
||||
$this->operations[] = ['brightness', [$brightness]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contrasts the image.
|
||||
*
|
||||
* @param int $contrast the contrast [-100, 100]
|
||||
* @return $this
|
||||
*/
|
||||
public function contrast(int $contrast)
|
||||
{
|
||||
$this->operations[] = ['contrast', [$contrast]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a grayscale level effect on the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function grayscale()
|
||||
{
|
||||
$this->operations[] = ['grayscale', []];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emboss the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function emboss()
|
||||
{
|
||||
$this->operations[] = ['emboss', []];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Smooth the image.
|
||||
*
|
||||
* @param int $p value between [-10,10]
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function smooth(int $p)
|
||||
{
|
||||
$this->operations[] = ['smooth', [$p]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sharpens the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function sharp()
|
||||
{
|
||||
$this->operations[] = ['sharp', []];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edges the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function edge()
|
||||
{
|
||||
$this->operations[] = ['edge', []];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorize the image.
|
||||
*
|
||||
* @param int $red value in range [-255, 255]
|
||||
* @param int $green value in range [-255, 255]
|
||||
* @param int $blue value in range [-255, 255]
|
||||
* @return $this
|
||||
*/
|
||||
public function colorize(int $red, int $green, int $blue)
|
||||
{
|
||||
$this->operations[] = ['colorize', [$red, $green, $blue]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* apply sepia to the image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function sepia()
|
||||
{
|
||||
$this->operations[] = ['sepia', []];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge with another image.
|
||||
*
|
||||
* @param Image $other
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return $this
|
||||
*/
|
||||
public function merge(Image $other, int $x = 0, int $y = 0, int $width = 0, int $height = 0)
|
||||
{
|
||||
$serialized = $other->__serialize();
|
||||
|
||||
$this->operations[] = ['merge', [$serialized, $x, $y, $width, $height]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the image.
|
||||
*
|
||||
* @param float $angle
|
||||
* @param string|int $background
|
||||
* @return $this
|
||||
*/
|
||||
public function rotate(float $angle, $background = 0xffffff)
|
||||
{
|
||||
$bg = ImageColor::parse($background);
|
||||
|
||||
$this->operations[] = ['rotate', [$angle, $bg]];
|
||||
|
||||
// FIXME: Image size may change?
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the image.
|
||||
*
|
||||
* @param string|int $color
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @return $this
|
||||
*/
|
||||
public function fill($color = 0xffffff, int $x = 0, int $y = 0)
|
||||
{
|
||||
$c = (int)ImageColor::parse($color);
|
||||
|
||||
$this->operations[] = ['fill', [$c, $x, $y]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* write text to the image.
|
||||
*
|
||||
* @param string $font
|
||||
* @param string $text
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param float $size
|
||||
* @param float $angle
|
||||
* @param string|int $color
|
||||
* @param string $align
|
||||
* @return $this
|
||||
*/
|
||||
public function write(string $font, string $text, int $x = 0, int $y = 0, float $size = 12.0, float $angle = 0.0, $color = 0x000000, string $align = 'left')
|
||||
{
|
||||
$c = (int)ImageColor::parse($color);
|
||||
|
||||
$this->operations[] = ['write', [$font, $text, $x, $y, $size, $angle, $c, $align]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a rectangle.
|
||||
*
|
||||
* @param int $x1
|
||||
* @param int $y1
|
||||
* @param int $x2
|
||||
* @param int $y2
|
||||
* @param string|int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function rectangle(int $x1, int $y1, int $x2, int $y2, $color, bool $filled = false)
|
||||
{
|
||||
$c = (int)ImageColor::parse($color);
|
||||
|
||||
$this->operations[] = ['rectangle', [$x1, $y1, $x2, $y2, $c, $filled]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a rounded rectangle.
|
||||
*
|
||||
* @param int $x1
|
||||
* @param int $y1
|
||||
* @param int $x2
|
||||
* @param int $y2
|
||||
* @param int $radius
|
||||
* @param string|int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function roundedRectangle(int $x1, int $y1, int $x2, int $y2, int $radius, $color, bool $filled = false)
|
||||
{
|
||||
$c = (int)ImageColor::parse($color);
|
||||
|
||||
$this->operations[] = ['roundedRectangle', [$x1, $y1, $x2, $y2, $radius, $c, $filled]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a line.
|
||||
*
|
||||
* @param int $x1
|
||||
* @param int $y1
|
||||
* @param int $x2
|
||||
* @param int $y2
|
||||
* @param string|int $color
|
||||
* @return $this
|
||||
*/
|
||||
public function line(int $x1, int $y1, int $x2, int $y2, $color = 0x000000)
|
||||
{
|
||||
$c = (int)ImageColor::parse($color);
|
||||
|
||||
$this->operations[] = ['line', [$x1, $y1, $x2, $y2, $c]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws an ellipse.
|
||||
*
|
||||
* @param int $cx
|
||||
* @param int $cy
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param string|int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function ellipse(int $cx, int $cy, int $width, int $height, $color = 0x000000, bool $filled = false)
|
||||
{
|
||||
$c = (int)ImageColor::parse($color);
|
||||
|
||||
$this->operations[] = ['ellipse', [$cx, $cy, $width, $height, $c, $filled]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a circle.
|
||||
*
|
||||
* @param int $cx
|
||||
* @param int $cy
|
||||
* @param int $r
|
||||
* @param string|int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function circle(int $cx, int $cy, int $r, $color = 0x000000, bool $filled = false)
|
||||
{
|
||||
$c = (int)ImageColor::parse($color);
|
||||
|
||||
$this->operations[] = ['circle', [$cx, $cy, $r, $c, $filled]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a polygon.
|
||||
*
|
||||
* @param array $points
|
||||
* @param string|int $color
|
||||
* @param bool $filled
|
||||
* @return $this
|
||||
*/
|
||||
public function polygon(array $points, $color, bool $filled = false)
|
||||
{
|
||||
$c = (int)ImageColor::parse($color);
|
||||
|
||||
$this->operations[] = ['polygon', [$points, $c, $filled]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips the image.
|
||||
*
|
||||
* @param bool $flipVertical
|
||||
* @param bool $flipHorizontal
|
||||
* @return $this
|
||||
*/
|
||||
public function flip(bool $flipVertical, bool $flipHorizontal)
|
||||
{
|
||||
$this->operations[] = ['flip', [$flipVertical, $flipHorizontal]];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|float|int|null $width
|
||||
* @param string|float|int|null $height
|
||||
* @return int[]
|
||||
*/
|
||||
protected function getSize($width, $height): array
|
||||
{
|
||||
if ($height === null && is_string($width) && preg_match('#^(.+)%$#mUs', $width, $matches)) {
|
||||
$width = round($this->width() * ((float)$matches[1] / 100.0));
|
||||
$height = round($this->height() * ((float)$matches[1] / 100.0));
|
||||
}
|
||||
|
||||
return [(int)$width, (int)$height];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user