132 lines
4.1 KiB
PHP
132 lines
4.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
function thumbRelativePathForSource(string $sourceRelPath): string
|
|
{
|
|
$normalized = ltrim(str_replace('\\', '/', $sourceRelPath), '/');
|
|
$hash = sha1($normalized);
|
|
$base = (string)pathinfo($normalized, PATHINFO_FILENAME);
|
|
$safeBase = preg_replace('/[^A-Za-z0-9._-]+/', '_', $base) ?? 'photo';
|
|
$safeBase = trim($safeBase, '._-');
|
|
if ($safeBase === '') {
|
|
$safeBase = 'photo';
|
|
}
|
|
|
|
return 'thumbs/' . substr($hash, 0, 2) . '/' . substr($hash, 2, 2) . '/' . $safeBase . '_' . $hash . '.jpg';
|
|
}
|
|
|
|
function thumbAbsolutePathForSource(string $projectRoot, string $sourceRelPath): string
|
|
{
|
|
return rtrim($projectRoot, '/') . '/' . ltrim(thumbRelativePathForSource($sourceRelPath), '/');
|
|
}
|
|
|
|
function ensureThumbForSource(string $projectRoot, string $sourceRelPath, int $maxWidth = 520, int $maxHeight = 360, int $quality = 82): ?string
|
|
{
|
|
$normalized = ltrim(str_replace('\\', '/', $sourceRelPath), '/');
|
|
if ($normalized === '') {
|
|
return null;
|
|
}
|
|
|
|
$sourceAbs = rtrim($projectRoot, '/') . '/' . $normalized;
|
|
if (!is_file($sourceAbs)) {
|
|
return null;
|
|
}
|
|
|
|
$thumbRel = thumbRelativePathForSource($normalized);
|
|
$thumbAbs = rtrim($projectRoot, '/') . '/' . ltrim($thumbRel, '/');
|
|
$srcMtime = (int)(filemtime($sourceAbs) ?: 0);
|
|
$thumbMtime = (int)(filemtime($thumbAbs) ?: 0);
|
|
if ($thumbMtime > 0 && $thumbMtime >= $srcMtime) {
|
|
return $thumbRel;
|
|
}
|
|
|
|
$dir = dirname($thumbAbs);
|
|
if (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) {
|
|
return null;
|
|
}
|
|
|
|
if (extension_loaded('imagick') && createThumbWithImagick($sourceAbs, $thumbAbs, $maxWidth, $maxHeight, $quality)) {
|
|
return $thumbRel;
|
|
}
|
|
if (createThumbWithGd($sourceAbs, $thumbAbs, $maxWidth, $maxHeight, $quality)) {
|
|
return $thumbRel;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function deleteThumbBySourcePath(string $projectRoot, string $sourceRelPath): void
|
|
{
|
|
$normalized = ltrim(str_replace('\\', '/', $sourceRelPath), '/');
|
|
if ($normalized === '') {
|
|
return;
|
|
}
|
|
|
|
$abs = thumbAbsolutePathForSource($projectRoot, $normalized);
|
|
if (is_file($abs)) {
|
|
@unlink($abs);
|
|
}
|
|
}
|
|
|
|
function createThumbWithImagick(string $sourceAbs, string $thumbAbs, int $maxWidth, int $maxHeight, int $quality): bool
|
|
{
|
|
try {
|
|
$img = new Imagick($sourceAbs);
|
|
if (method_exists($img, 'autoOrient')) {
|
|
$img->autoOrient();
|
|
} else {
|
|
$img->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
|
|
}
|
|
$img->thumbnailImage(max(1, $maxWidth), max(1, $maxHeight), true, true);
|
|
$img->setImageFormat('jpeg');
|
|
$img->setImageCompressionQuality(max(30, min(95, $quality)));
|
|
$img->stripImage();
|
|
$ok = $img->writeImage($thumbAbs);
|
|
$img->clear();
|
|
$img->destroy();
|
|
return (bool)$ok;
|
|
} catch (Throwable) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function createThumbWithGd(string $sourceAbs, string $thumbAbs, int $maxWidth, int $maxHeight, int $quality): bool
|
|
{
|
|
[$srcW, $srcH, $type] = @getimagesize($sourceAbs) ?: [0, 0, 0];
|
|
if ($srcW < 1 || $srcH < 1) {
|
|
return false;
|
|
}
|
|
|
|
$src = match ($type) {
|
|
IMAGETYPE_JPEG => @imagecreatefromjpeg($sourceAbs),
|
|
IMAGETYPE_PNG => @imagecreatefrompng($sourceAbs),
|
|
IMAGETYPE_GIF => @imagecreatefromgif($sourceAbs),
|
|
IMAGETYPE_WEBP => function_exists('imagecreatefromwebp') ? @imagecreatefromwebp($sourceAbs) : false,
|
|
default => false,
|
|
};
|
|
if (!$src) {
|
|
return false;
|
|
}
|
|
|
|
$scale = min($maxWidth / $srcW, $maxHeight / $srcH, 1);
|
|
$dstW = max(1, (int)round($srcW * $scale));
|
|
$dstH = max(1, (int)round($srcH * $scale));
|
|
|
|
$dst = imagecreatetruecolor($dstW, $dstH);
|
|
if ($dst === false) {
|
|
imagedestroy($src);
|
|
return false;
|
|
}
|
|
|
|
$bg = imagecolorallocate($dst, 255, 255, 255);
|
|
imagefill($dst, 0, 0, $bg);
|
|
|
|
$okCopy = imagecopyresampled($dst, $src, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH);
|
|
$okSave = $okCopy && imagejpeg($dst, $thumbAbs, max(30, min(95, $quality)));
|
|
|
|
imagedestroy($src);
|
|
imagedestroy($dst);
|
|
return (bool)$okSave;
|
|
}
|