thats-me/backend/app/Services/EventMediaImageProcessor.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;
}
}