events-venues/app/Services/Image/ImageCropperTrait.php

155 lines
4.3 KiB
PHP

<?php
/**
* @package: events-venues-task
* @author: Yevhen Odynets
* @date: 2024-07-27
* @time: 12:42
*/
declare(strict_types=1);
namespace App\Services\Image;
use Illuminate\Support\Facades\File;
use ReflectionException;
use RuntimeException;
trait ImageCropperTrait
{
public static int $counter = 1;
/**
* @throws ReflectionException
*/
public function cropAlign(
string $srcPath,
string $dbPath,
?int $cropWidth = null,
?int $cropHeight = null,
?string $horizontalAlign = 'center',
?string $verticalAlign = 'middle'
): string {
$resource = $this->imageResource($srcPath);
if ($resource === false) {
appNotice("could not get image resource: $srcPath");
return $dbPath;
}
[$image, $saveMethod, $quality] = $resource;
$width = imagesx($image);
$height = imagesy($image);
$cropWidth ??= $width;
$cropHeight ??= $height;
if (($width > $cropWidth || $height > $cropHeight) === false) {
$destPath = $this->destPath($srcPath, $dbPath, $width, $height);
appInfo(self::$counter++ . ". skip cropping ($width x $height): " . basename($destPath[0]) . " saved");
return File::move($srcPath, $destPath[0]) ? $destPath[1] : $dbPath;
}
$horizontalAlignPixels = $this->calculatePixelsForAlign($width, $cropWidth, $horizontalAlign);
$verticalAlignPixels = $this->calculatePixelsForAlign($height, $cropHeight, $verticalAlign);
$destPath = $this->destPath($srcPath, $dbPath, $horizontalAlignPixels[1], $verticalAlignPixels[1]);
$croppedImage = imagecrop($image, [
'x' => $horizontalAlignPixels[0],
'y' => $verticalAlignPixels[0],
'width' => $horizontalAlignPixels[1],
'height' => $verticalAlignPixels[1],
]);
if ($croppedImage !== false) {
$saveMethod($croppedImage, $destPath[0], $quality);
imagedestroy($croppedImage);
appSuccess(
self::$counter++ . ". cropped successfully from $width x $height: " . basename($destPath[0]) . " saved"
);
}
imagedestroy($image);
if (File::delete($srcPath) === false) {
$e = new RuntimeException("could not delete image: $srcPath");
appWarning($e);
return $dbPath;
}
return $destPath[1];
}
/**
* @param string $srcPath
*
* @return false|array
*/
private function imageResource(string $srcPath): false|array
{
return match (mime_content_type($srcPath)) {
'image/jpeg' => [imagecreatefromjpeg($srcPath), 'imagejpeg', 84],
'image/png' => [imagecreatefrompng($srcPath), 'imagepng', 7],
'image/gif' => [imagecreatefromgif($srcPath), 'imagegif', null],
default => false,
};
}
/**
* @param string $srcPath
* @param string $dbPath
* @param int $width
* @param int $height
*
* @return string[]
*/
private function destPath(string $srcPath, string $dbPath, int $width, int $height): array
{
$pi = pathinfo($srcPath);
$fs_path = sprintf(
"%s\%s-%dx%d.%s",
$pi['dirname'],
$pi['filename'],
$width,
$height,
$pi['extension']
);
$db_path = sprintf(
"%s/%s-%dx%d.%s",
dirname($dbPath),
$pi['filename'],
$width,
$height,
$pi['extension']
);
return [$fs_path, $db_path];
}
/**
* @param int $imageSize
* @param int $cropSize
* @param string $align
*
* @return array|int[]
*/
private function calculatePixelsForAlign(int $imageSize, int $cropSize, string $align): array
{
return match ($align) {
'left', 'top' => [0, min($cropSize, $imageSize)],
'right', 'bottom' => [max(0, $imageSize - $cropSize), min($cropSize, $imageSize)],
'center', 'middle' => [
max(0, floor(($imageSize / 2) - ($cropSize / 2))),
min($cropSize, $imageSize),
],
default => [0, $imageSize],
};
}
}