composed project, added packages, models, controllers, seeders, mirgations etc.
This commit is contained in:
17
app/Services/Image/Contracts/ImageInterface.php
Normal file
17
app/Services/Image/Contracts/ImageInterface.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-27
|
||||
* @time: 12:58
|
||||
*/
|
||||
|
||||
//phpcs:ignore
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Image\Contracts;
|
||||
|
||||
interface ImageInterface
|
||||
{
|
||||
}
|
||||
77
app/Services/Image/Image.php
Normal file
77
app/Services/Image/Image.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package: events-venues-task
|
||||
* @author: Yevhen Odynets
|
||||
* @date: 2024-07-27
|
||||
* @time: 07:35
|
||||
*/
|
||||
|
||||
//phpcs:ignore
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace App\Services\Image;
|
||||
|
||||
use App\ConsoleStyleInterfaceType;
|
||||
use App\LoggingSeverityLevelsType;
|
||||
use App\Services\Image\Contracts\ImageInterface;
|
||||
use App\Services\Image\ImageCropperTrait as Cropper;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\{App, Log, Storage};
|
||||
use Symfony\Component\Console\{Input\ArgvInput, Output\ConsoleOutput, Style\SymfonyStyle};
|
||||
|
||||
final class Image implements ImageInterface
|
||||
{
|
||||
use Cropper;
|
||||
|
||||
public static mixed $faker = null;
|
||||
private SymfonyStyle $io;
|
||||
private array $config;
|
||||
private bool $isConsole;
|
||||
private bool $isVerbosity;
|
||||
private bool $isIO;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = config('image');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $imagePath
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function localPath(string $imagePath): ?string
|
||||
{
|
||||
$fullPath = realpath(Storage::path($imagePath));
|
||||
|
||||
if (! $fullPath || ! file_exists($fullPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $fullPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fullPath
|
||||
* @param string|null $property
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function meta(string $fullPath, ?string $property = null): array|string
|
||||
{
|
||||
try {
|
||||
$meta = getimagesize($fullPath);
|
||||
|
||||
if (is_null($property) === false && array_key_exists($property, $this->config['meta'])) {
|
||||
return $meta[$this->config['meta'][$property]];
|
||||
}
|
||||
|
||||
return getimagesize($fullPath);
|
||||
} catch (Exception $e) {
|
||||
appWarning($e);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
154
app/Services/Image/ImageCropperTrait.php
Normal file
154
app/Services/Image/ImageCropperTrait.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?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],
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user