125 lines
3.9 KiB
PHP
125 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Http\UploadedFile;
|
|
use RuntimeException;
|
|
|
|
class EventMediaImageProcessor
|
|
{
|
|
private const MAX_ORIGINAL_SIDE = 3508;
|
|
|
|
private const PREVIEW_MAX_SIDE = 900;
|
|
|
|
private const THUMBNAIL_SIZE = 320;
|
|
|
|
/**
|
|
* @return array{
|
|
* original: string,
|
|
* preview: string,
|
|
* thumbnail: string,
|
|
* width: int,
|
|
* height: int,
|
|
* preview_width: int,
|
|
* preview_height: int,
|
|
* thumbnail_width: int,
|
|
* thumbnail_height: int,
|
|
* mime_type: string,
|
|
* size: int
|
|
* }
|
|
*/
|
|
public function process(UploadedFile $file): array
|
|
{
|
|
$source = imagecreatefromstring((string) file_get_contents($file->getRealPath()));
|
|
|
|
if (! $source) {
|
|
throw new RuntimeException('The uploaded image could not be processed.');
|
|
}
|
|
|
|
$sourceWidth = imagesx($source);
|
|
$sourceHeight = imagesy($source);
|
|
|
|
$original = $this->resizeContain($source, $sourceWidth, $sourceHeight, self::MAX_ORIGINAL_SIDE);
|
|
$preview = $this->resizeContain($source, $sourceWidth, $sourceHeight, self::PREVIEW_MAX_SIDE, 84);
|
|
$thumbnail = $this->resizeCover($source, $sourceWidth, $sourceHeight, self::THUMBNAIL_SIZE);
|
|
|
|
imagedestroy($source);
|
|
|
|
return [
|
|
'original' => $original['contents'],
|
|
'preview' => $preview['contents'],
|
|
'thumbnail' => $thumbnail['contents'],
|
|
'width' => $original['width'],
|
|
'height' => $original['height'],
|
|
'preview_width' => $preview['width'],
|
|
'preview_height' => $preview['height'],
|
|
'thumbnail_width' => self::THUMBNAIL_SIZE,
|
|
'thumbnail_height' => self::THUMBNAIL_SIZE,
|
|
'mime_type' => 'image/jpeg',
|
|
'size' => strlen($original['contents']),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{contents: string, width: int, height: int}
|
|
*/
|
|
private function resizeContain(
|
|
\GdImage $source,
|
|
int $sourceWidth,
|
|
int $sourceHeight,
|
|
int $maxSide,
|
|
int $quality = 90
|
|
): array {
|
|
$scale = min(1, $maxSide / max($sourceWidth, $sourceHeight));
|
|
$width = max(1, (int) round($sourceWidth * $scale));
|
|
$height = max(1, (int) round($sourceHeight * $scale));
|
|
|
|
$canvas = imagecreatetruecolor($width, $height);
|
|
$this->fillCanvas($canvas, $width, $height);
|
|
imagecopyresampled($canvas, $source, 0, 0, 0, 0, $width, $height, $sourceWidth, $sourceHeight);
|
|
|
|
return [
|
|
'contents' => $this->encodeJpeg($canvas, $quality),
|
|
'width' => $width,
|
|
'height' => $height,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array{contents: string, width: int, height: int}
|
|
*/
|
|
private function resizeCover(\GdImage $source, int $sourceWidth, int $sourceHeight, int $size): array
|
|
{
|
|
$scale = max($size / $sourceWidth, $size / $sourceHeight);
|
|
$width = (int) round($sourceWidth * $scale);
|
|
$height = (int) round($sourceHeight * $scale);
|
|
$x = (int) round(($size - $width) / 2);
|
|
$y = (int) round(($size - $height) / 2);
|
|
|
|
$canvas = imagecreatetruecolor($size, $size);
|
|
$this->fillCanvas($canvas, $size, $size);
|
|
imagecopyresampled($canvas, $source, $x, $y, 0, 0, $width, $height, $sourceWidth, $sourceHeight);
|
|
|
|
return [
|
|
'contents' => $this->encodeJpeg($canvas, 80),
|
|
'width' => $size,
|
|
'height' => $size,
|
|
];
|
|
}
|
|
|
|
private function fillCanvas(\GdImage $canvas, int $width, int $height): void
|
|
{
|
|
$background = imagecolorallocate($canvas, 255, 255, 255);
|
|
imagefilledrectangle($canvas, 0, 0, $width, $height, $background);
|
|
}
|
|
|
|
private function encodeJpeg(\GdImage $image, int $quality): string
|
|
{
|
|
ob_start();
|
|
imagejpeg($image, null, $quality);
|
|
$contents = (string) ob_get_clean();
|
|
imagedestroy($image);
|
|
|
|
return $contents;
|
|
}
|
|
}
|