File "ImagickDriver.php"
Full Path: /var/www/html/back/vendor/spatie/image/src/Drivers/Imagick/ImagickDriver.php
File size: 22 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace Spatie\Image\Drivers\Imagick;
use Imagick;
use ImagickDraw;
use ImagickPixel;
use Spatie\Image\Drivers\Concerns\AddsWatermark;
use Spatie\Image\Drivers\Concerns\CalculatesCropOffsets;
use Spatie\Image\Drivers\Concerns\CalculatesFocalCropAndResizeCoordinates;
use Spatie\Image\Drivers\Concerns\CalculatesFocalCropCoordinates;
use Spatie\Image\Drivers\Concerns\GetsOrientationFromExif;
use Spatie\Image\Drivers\Concerns\PerformsFitCrops;
use Spatie\Image\Drivers\Concerns\PerformsOptimizations;
use Spatie\Image\Drivers\Concerns\ValidatesArguments;
use Spatie\Image\Drivers\ImageDriver;
use Spatie\Image\Enums\AlignPosition;
use Spatie\Image\Enums\BorderType;
use Spatie\Image\Enums\ColorFormat;
use Spatie\Image\Enums\Constraint;
use Spatie\Image\Enums\CropPosition;
use Spatie\Image\Enums\Fit;
use Spatie\Image\Enums\FlipDirection;
use Spatie\Image\Enums\Orientation;
use Spatie\Image\Exceptions\InvalidFont;
use Spatie\Image\Exceptions\MissingParameter;
use Spatie\Image\Exceptions\UnsupportedImageFormat;
use Spatie\Image\Point;
use Spatie\Image\Size;
class ImagickDriver implements ImageDriver
{
use AddsWatermark;
use CalculatesCropOffsets;
use CalculatesFocalCropAndResizeCoordinates;
use CalculatesFocalCropCoordinates;
use GetsOrientationFromExif;
use PerformsFitCrops;
use PerformsOptimizations;
use ValidatesArguments;
protected Imagick $image;
protected ?string $format = null;
protected array $exif = [];
protected string $originalPath;
public function new(int $width, int $height, ?string $backgroundColor = null): static
{
$color = new ImagickColor($backgroundColor);
$image = new Imagick;
$image->newImage($width, $height, $color->getPixel(), 'png');
$image->setType(Imagick::IMGTYPE_UNDEFINED);
$image->setImageType(Imagick::IMGTYPE_UNDEFINED);
$image->setColorspace(Imagick::COLORSPACE_UNDEFINED);
return (new static)->setImage($image);
}
protected function setImage(Imagick $image): static
{
$this->image = $image;
return $this;
}
public function image(): Imagick
{
return $this->image;
}
public function loadFile(string $path, bool $autoRotate = true): static
{
$this->originalPath = $path;
$this->optimize = false;
$this->image = new Imagick($path);
$this->exif = $this->image->getImageProperties('exif:*');
if ($autoRotate) {
$this->autoRotate();
}
if ($this->isAnimated()) {
$this->image = $this->image->coalesceImages();
}
return $this;
}
protected function isAnimated(): bool
{
return count($this->image) > 1;
}
public function getWidth(): int
{
return $this->image->getImageWidth();
}
public function getHeight(): int
{
return $this->image->getImageHeight();
}
public function brightness(int $brightness): static
{
foreach ($this->image as $image) {
$image->modulateImage(100 + $brightness, 100, 100);
}
return $this;
}
public function blur(int $blur): static
{
foreach ($this->image as $image) {
$image->blurImage(0.5 * $blur, 0.1 * $blur);
}
return $this;
}
public function fit(
Fit $fit,
?int $desiredWidth = null,
?int $desiredHeight = null,
bool $relative = false,
?string $backgroundColor = null,
): static {
if ($fit === Fit::Crop) {
return $this->fitCrop($fit, $this->getWidth(), $this->getHeight(), $desiredWidth, $desiredHeight);
}
if ($fit === Fit::FillMax) {
if (is_null($desiredWidth) || is_null($desiredHeight)) {
throw new MissingParameter('Both desiredWidth and desiredHeight must be set when using Fit::FillMax');
}
if (is_null($backgroundColor)) {
throw new MissingParameter('backgroundColor must be set when using Fit::FillMax');
}
return $this->fitFillMax($desiredWidth, $desiredHeight, $backgroundColor);
}
$calculatedSize = $fit->calculateSize(
$this->getWidth(),
$this->getHeight(),
$desiredWidth,
$desiredHeight
);
foreach ($this->image as $image) {
$image->scaleImage($calculatedSize->width, $calculatedSize->height);
}
if ($fit->shouldResizeCanvas()) {
$this->resizeCanvas($desiredWidth, $desiredHeight, AlignPosition::Center, $relative, $backgroundColor);
}
return $this;
}
public function fitFillMax(int $desiredWidth, int $desiredHeight, string $backgroundColor, bool $relative = false): static
{
$this->resize($desiredWidth, $desiredHeight, [Constraint::PreserveAspectRatio]);
$this->resizeCanvas($desiredWidth, $desiredHeight, AlignPosition::Center, $relative, $backgroundColor);
return $this;
}
public function resizeCanvas(
?int $width = null,
?int $height = null,
?AlignPosition $position = null,
bool $relative = false,
?string $backgroundColor = null
): static {
$position ??= AlignPosition::Center;
$originalWidth = $this->getWidth();
$originalHeight = $this->getHeight();
$width ??= $originalWidth;
$height ??= $originalHeight;
if ($relative) {
$width = $originalWidth + $width;
$height = $originalHeight + $height;
}
$width = $width <= 0
? $width + $originalWidth
: $width;
$height = $height <= 0
? $height + $originalHeight
: $height;
$canvas = $this->new($width, $height, $backgroundColor);
$canvasSize = $canvas->getSize()->align($position);
$imageSize = $this->getSize()->align($position);
$canvasPosition = $imageSize->relativePosition($canvasSize);
$imagePosition = $canvasSize->relativePosition($imageSize);
if ($width <= $originalWidth) {
$destinationX = 0;
$sourceX = $canvasPosition->x;
$sourceWidth = $canvasSize->width;
} else {
$destinationX = $imagePosition->x;
$sourceX = 0;
$sourceWidth = $originalWidth;
}
if ($height <= $originalHeight) {
$destinationY = 0;
$sourceY = $canvasPosition->y;
$sourceHeight = $canvasSize->height;
} else {
$destinationY = $imagePosition->y;
$sourceY = 0;
$sourceHeight = $originalHeight;
}
// make image area transparent to keep transparency
// even if background-color is set
$rect = new ImagickDraw;
$fill = $canvas->pickColor(0, 0, ColorFormat::Hex);
$fill = $fill === '#ff0000' ? '#00ff00' : '#ff0000';
$rect->setFillColor($fill);
$rect->rectangle($destinationX, $destinationY, $destinationX + $sourceWidth - 1, $destinationY + $sourceHeight - 1);
$canvas->image->drawImage($rect);
$canvas->image->transparentPaintImage($fill, 0, 0, false);
foreach ($this->image->getImageProfiles() as $key => $value) {
$canvas->image->setImageProfile($key, $value);
}
$canvas->image->setImageColorspace($this->image->getImageColorspace());
// copy image into new canvas
$this->image->cropImage($sourceWidth, $sourceHeight, $sourceX, $sourceY);
$canvas->image->compositeImage($this->image, Imagick::COMPOSITE_DEFAULT, $destinationX, $destinationY);
$canvas->image->setImagePage(0, 0, 0, 0);
// set new core to canvas
$this->image = $canvas->image;
return $this;
}
public function pickColor(int $x, int $y, ColorFormat $colorFormat): mixed
{
$color = new ImagickColor($this->image->getImagePixelColor($x, $y));
return $color->format($colorFormat);
}
public function save(?string $path = null): static
{
if (! $path) {
$path = $this->originalPath;
}
if (is_null($this->format)) {
$format = pathinfo($path, PATHINFO_EXTENSION);
} else {
$format = $this->format;
}
$formats = Imagick::queryFormats('*');
if (in_array('JPEG', $formats)) {
$formats[] = 'JFIF';
}
if (! in_array(strtoupper($format), $formats)) {
throw UnsupportedImageFormat::make($format);
}
foreach ($this->image as $image) {
if (strtoupper($format) === 'JFIF') {
$image->setFormat('JPEG');
} else {
$image->setFormat($format);
}
}
if ($this->isAnimated()) {
$image = $this->image->deconstructImages();
$image->writeImages($path, true);
} else {
$this->image->writeImage($path);
}
if ($this->optimize) {
$this->optimizerChain->optimize($path);
}
$this->format = null;
return $this;
}
public function base64(string $imageFormat = 'jpeg', bool $prefixWithFormat = true): string
{
$image = clone $this->image;
$image->setFormat($imageFormat);
if ($prefixWithFormat) {
return 'data:image/'.$imageFormat.';base64,'.base64_encode($image->getImageBlob());
}
return base64_encode($image->getImageBlob());
}
public function driverName(): string
{
return 'imagick';
}
public function getSize(): Size
{
return new Size($this->getWidth(), $this->getHeight());
}
public function gamma(float $gamma): static
{
foreach ($this->image as $image) {
$image->gammaImage($gamma);
}
return $this;
}
public function contrast(float $level): static
{
foreach ($this->image as $image) {
$image->brightnessContrastImage(1, $level);
}
return $this;
}
public function colorize(int $red, int $green, int $blue): static
{
$quantumRange = $this->image->getQuantumRange();
$red = Helpers::normalizeColorizeLevel($red);
$green = Helpers::normalizeColorizeLevel($green);
$blue = Helpers::normalizeColorizeLevel($blue);
foreach ($this->image as $image) {
$image->levelImage(0, $red, $quantumRange['quantumRangeLong'], Imagick::CHANNEL_RED);
$image->levelImage(0, $green, $quantumRange['quantumRangeLong'], Imagick::CHANNEL_GREEN);
$image->levelImage(0, $blue, $quantumRange['quantumRangeLong'], Imagick::CHANNEL_BLUE);
}
return $this;
}
public function greyscale(): static
{
foreach ($this->image as $image) {
$image->modulateImage(100, 0, 100);
}
return $this;
}
public function manualCrop(int $width, int $height, ?int $x = null, ?int $y = null): static
{
$cropped = new Size($width, $height);
$position = new Point($x ?? 0, $y ?? 0);
if (is_null($x) && is_null($y)) {
$position = $this
->getSize()
->align(AlignPosition::Center)
->relativePosition($cropped->align(AlignPosition::Center));
}
foreach ($this->image as $image) {
$image->cropImage($cropped->width, $cropped->height, $position->x, $position->y);
$image->setImagePage(0, 0, 0, 0);
}
return $this;
}
public function crop(int $width, int $height, CropPosition $position = CropPosition::Center): static
{
[$offsetX, $offsetY] = $this->calculateCropOffsets($width, $height, $position);
return $this->manualCrop($width, $height, $offsetX, $offsetY);
}
public function focalCrop(int $width, int $height, ?int $cropCenterX = null, ?int $cropCenterY = null): static
{
[$width, $height, $cropCenterX, $cropCenterY] = $this->calculateFocalCropCoordinates(
$width,
$height,
$cropCenterX,
$cropCenterY
);
$this->manualCrop($width, $height, $cropCenterX, $cropCenterY);
return $this;
}
public function focalCropAndResize(int $width, int $height, ?int $cropCenterX = null, ?int $cropCenterY = null): static
{
[$cropWidth, $cropHeight, $cropX, $cropY] = $this->calculateFocalCropAndResizeCoordinates(
$width,
$height,
$cropCenterX,
$cropCenterY
);
$this->manualCrop($cropWidth, $cropHeight, $cropX, $cropY)
->width($width)
->height($height);
return $this;
}
public function sepia(): static
{
return $this
->greyscale()
->brightness(-40)
->contrast(20)
->colorize(50, 35, 20)
->brightness(-10)
->contrast(10);
}
public function sharpen(float $amount): static
{
foreach ($this->image as $image) {
$image->unsharpMaskImage(1, 1, $amount / 6.25, 0);
}
return $this;
}
public function background(string $color): static
{
$background = $this->new($this->getWidth(), $this->getHeight(), $color);
$this->overlay($background, $this, 0, 0);
return $this;
}
public function overlay(ImageDriver $bottomImage, ImageDriver $topImage, int $x = 0, int $y = 0): static
{
$bottomImage->insert($topImage, AlignPosition::Center, $x, $y);
$this->image = $bottomImage->image();
return $this;
}
public function orientation(?Orientation $orientation = null): static
{
if (is_null($orientation)) {
$orientation = $this->getOrientationFromExif($this->exif);
}
foreach ($this->image as $image) {
$image->rotateImage(new ImagickPixel('none'), $orientation->degrees());
}
return $this;
}
/**
* @return array<string, mixed>
*/
public function exif(): array
{
return $this->exif;
}
public function flip(FlipDirection $flip): static
{
foreach ($this->image as $image) {
switch ($flip) {
case FlipDirection::Vertical:
$image->flipImage();
break;
case FlipDirection::Horizontal:
$image->flopImage();
break;
case FlipDirection::Both:
$image->flipImage();
$image->flopImage();
break;
}
}
return $this;
}
public function pixelate(int $pixelate = 50): static
{
if ($pixelate === 0) {
return $this;
}
$width = $this->getWidth();
$height = $this->getHeight();
foreach ($this->image as $image) {
$image->scaleImage(max(1, (int) ($width / $pixelate)), max(1, (int) ($height / $pixelate)));
$image->scaleImage($width, $height);
}
return $this;
}
public function insert(
ImageDriver|string $otherImage,
AlignPosition $position = AlignPosition::Center,
int $x = 0,
int $y = 0,
int $alpha = 100
): static {
$this->ensureNumberBetween($alpha, 0, 100, 'alpha');
if (is_string($otherImage)) {
$otherImage = (new static)->loadFile($otherImage);
}
$otherImage->image()->setImageOrientation(Imagick::ORIENTATION_UNDEFINED);
$otherImage->image()->evaluateImage(Imagick::EVALUATE_DIVIDE, (1 / ($alpha / 100)), Imagick::CHANNEL_ALPHA);
$imageSize = $this->getSize()->align($position, $x, $y);
$watermarkSize = $otherImage->getSize()->align($position);
$target = $imageSize->relativePosition($watermarkSize);
foreach ($this->image as $image) {
$image->compositeImage(
$otherImage->image(),
Imagick::COMPOSITE_OVER,
$target->x,
$target->y
);
}
return $this;
}
public function resize(int $width, int $height, array $constraints = []): static
{
$resized = $this->getSize()->resize($width, $height, $constraints);
foreach ($this->image as $image) {
$image->scaleImage($resized->width, $resized->height);
}
return $this;
}
public function width(int $width, array $constraints = [Constraint::PreserveAspectRatio]): static
{
$newHeight = (int) round($width / $this->getSize()->aspectRatio());
$this->resize($width, $newHeight, $constraints);
return $this;
}
public function height(int $height, array $constraints = [Constraint::PreserveAspectRatio]): static
{
$newWidth = (int) round($height * $this->getSize()->aspectRatio());
$this->resize($newWidth, $height, $constraints);
return $this;
}
public function border(int $width, BorderType $type, string $color = '000000'): static
{
if ($type === BorderType::Shrink) {
$originalWidth = $this->getWidth();
$originalHeight = $this->getHeight();
$this
->resize(
(int) round($this->getWidth() - ($width * 2)),
(int) round($this->getHeight() - ($width * 2)),
[Constraint::PreserveAspectRatio],
)
->resizeCanvas(
$originalWidth,
$originalHeight,
AlignPosition::Center,
false,
$color,
);
return $this;
}
if ($type === BorderType::Expand) {
$this->resizeCanvas(
(int) round($width * 2),
(int) round($width * 2),
AlignPosition::Center,
true,
$color,
);
return $this;
}
if ($type === BorderType::Overlay) {
$shape = new ImagickDraw;
$backgroundColor = new ImagickColor;
$shape->setFillColor($backgroundColor->getPixel());
$borderColor = new ImagickColor($color);
$shape->setStrokeColor($borderColor->getPixel());
$shape->setStrokeWidth($width);
$shape->rectangle(
(int) round($width / 2),
(int) round($width / 2),
(int) round($this->getWidth() - ($width / 2)),
(int) round($this->getHeight() - ($width / 2)),
);
foreach ($this->image as $image) {
$image->drawImage($shape);
}
return $this;
}
}
public function quality(int $quality): static
{
foreach ($this->image as $image) {
$this->image->setImageCompressionQuality($quality);
$this->image->setCompressionQuality(100 - $quality); // For PNGs
}
return $this;
}
public function format(string $format): static
{
$this->format = $format;
return $this;
}
public function autoRotate(): void
{
switch ($this->image->getImageOrientation()) {
case Imagick::ORIENTATION_TOPLEFT:
break;
case Imagick::ORIENTATION_TOPRIGHT:
$this->image->flopImage();
break;
case Imagick::ORIENTATION_BOTTOMRIGHT:
$this->image->rotateImage('#000', 180);
break;
case Imagick::ORIENTATION_BOTTOMLEFT:
$this->image->flopImage();
$this->image->rotateImage('#000', 180);
break;
case Imagick::ORIENTATION_LEFTTOP:
$this->image->flopImage();
$this->image->rotateImage('#000', -90);
break;
case Imagick::ORIENTATION_RIGHTTOP:
$this->image->rotateImage('#000', 90);
break;
case Imagick::ORIENTATION_RIGHTBOTTOM:
$this->image->flopImage();
$this->image->rotateImage('#000', 90);
break;
case Imagick::ORIENTATION_LEFTBOTTOM:
$this->image->rotateImage('#000', -90);
break;
default: // Invalid orientation
break;
}
$this->image->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
}
public function text(
string $text,
int $fontSize,
string $color = '000000',
int $x = 0,
int $y = 0,
int $angle = 0,
string $fontPath = '',
int $width = 0,
): static {
if ($fontPath && ! file_exists($fontPath)) {
throw InvalidFont::make($fontPath);
}
$textColor = new ImagickColor($color);
$draw = new ImagickDraw;
$draw->setFillColor($textColor->getPixel());
$draw->setFontSize($fontSize);
if ($fontPath) {
$draw->setFont($fontPath);
}
$this->image->annotateImage(
$draw,
$x,
$y,
$angle,
$width > 0
? $this->wrapText($text, $fontSize, $fontPath, $angle, $width)
: $text,
);
return $this;
}
public function wrapText(string $text, int $fontSize, string $fontPath = '', int $angle = 0, int $width = 0): string
{
if ($fontPath && ! file_exists($fontPath)) {
throw InvalidFont::make($fontPath);
}
$wrapped = '';
$words = explode(' ', $text);
foreach ($words as $word) {
$teststring = "{$wrapped} {$word}";
$draw = new ImagickDraw;
if ($fontPath) {
$draw->setFont($fontPath);
}
$draw->setFontSize($fontSize);
$metrics = (new Imagick)->queryFontMetrics($draw, $teststring);
if ($metrics['textWidth'] > $width) {
$wrapped .= "\n".$word;
} else {
$wrapped .= ' '.$word;
}
}
return $wrapped;
}
}