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; }