針對圖片較大、需多種尺寸縮略圖的場景,動態生成可避免預先儲存大量規格檔案。本文實作一個 thumbs.php,透過 URL 參數指定原圖與目標尺寸,即時縮放並直接輸出二進位圖片流。
thumbs.php<?php
// 1. 參數驗證與清理
$filename = basename($_GET['filename'] ?? ''); // 防止目錄遍歷
$width = (int)($_GET['width'] ?? 0);
$height = (int)($_GET['height'] ?? 0);
if (!$filename || $width <= 0 || $height <= 0) {
http_response_code(400);
exit('Invalid parameters');
}
// 2. 原圖路徑 (建議放在 Web 根目錄外)
$baseDir = __DIR__ . '/../images/'; // 實體路徑,不可直接瀏覽
$sourcePath = $baseDir . $filename;
if (!is_file($sourcePath)) {
http_response_code(404);
exit('Image not found');
}
// 3. 取得原圖資訊
$info = getimagesize($sourcePath);
if (!$info) {
http_response_code(500);
exit('Invalid image file');
}
list($srcW, $srcH, $mime) = $info;
// 僅支援常見格式
$allowedMime = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($mime, $allowedMime, true)) {
http_response_code(415);
exit('Unsupported image type');
}
// 4. 計算目標尺寸 (保持比例)
if ($width && $height) {
// 兩者皆指定:縮放填滿,再裁切置中 (或依需求調整)
$ratio = max($width / $srcW, $height / $srcH);
$newW = (int)round($srcW * $ratio);
$newH = (int)round($srcH * $ratio);
} elseif ($width) {
$ratio = $width / $srcW;
$newW = $width;
$newH = (int)round($srcH * $ratio);
} else {
$ratio = $height / $srcH;
$newH = $height;
$newW = (int)round($srcW * $ratio);
}
// 5. 建立真彩畫布並重採樣
$dstImg = imagecreatetruecolor($newW, $newH);
// 保持透明度 (PNG/GIF/WEBP)
if (in_array($mime, ['image/png', 'image/gif', 'image/webp'])) {
imagealphablending($dstImg, false);
imagesavealpha($dstImg, true);
$transparent = imagecolorallocatealpha($dstImg, 255, 255, 255, 127);
imagefilledrectangle($dstImg, 0, 0, $newW, $newH, $transparent);
}
// 根據 MIME 載入原圖資源
switch ($mime) {
case 'image/jpeg': $srcImg = imagecreatefromjpeg($sourcePath); break;
case 'image/png': $srcImg = imagecreatefrompng($sourcePath); break;
case 'image/gif': $srcImg = imagecreatefromgif($sourcePath); break;
case 'image/webp': $srcImg = imagecreatefromwebp($sourcePath); break;
}
// 重採樣縮放 (高品質)
imagecopyresampled($dstImg, $srcImg, 0, 0, 0, 0, $newW, $newH, $srcW, $srcH);
// 6. 輸出圖片二進位流
header('Content-Type: ' . $mime);
header('Cache-Control: public, max-age=31536000'); // 1 年快取
header('ETag: "' . md5_file($sourcePath) . '"');
switch ($mime) {
case 'image/jpeg': imagejpeg($dstImg, null, 90); break; // 品質 90
case 'image/png': imagepng($dstImg, null, 6); break; // 壓縮等級 6
case 'image/gif': imagegif($dstImg); break;
case 'image/webp': imagewebp($dstImg, null, 90); break;
}
// 7. 釋放資源
imagedestroy($srcImg);
imagedestroy($dstImg);
?>
<!-- 固定寬度,高度自適應 -->
<img src="/thumbs.php?filename=photo.jpg&width=300" alt="...">
<!-- 固定寬高 (會裁切/拉伸,依計算邏輯) -->
<img src="/thumbs.php?filename=photo.jpg&width=200&height=200" alt="...">
<!-- 響應式圖片 (搭配 srcset) -->
<img src="/thumbs.php?filename=photo.jpg&width=800"
srcset="/thumbs.php?filename=photo.jpg&width=400 400w,
/thumbs.php?filename=photo.jpg&width=800 800w,
/thumbs.php?filename=photo.jpg&width=1200 1200w"
sizes="(max-width: 600px) 400px, 800px" alt="...">
ETag、Cache-Control,瀏覽器/CDN 可快取,減少重複生成。/cache/thumbs/{width}x{height}/{filename},下次請求直接 readfile() 或 Nginx try_files 靜態服務。$width, $height <= 2000,防止惡意請求大圖耗盡記憶體。imagick 擴充,效能與品質優於 GD,支援更多格式 (HEIC, AVIF, TIFF 等)。basename() 防止 ../../etc/passwd 遍歷ini_set('display_errors', 0),避免洩露路徑資訊location /thumbs.php {
# 嘗試直接服務已生成的快取檔
try_files /cache/thumbs$uri =404;
# 快取不存在時代理到 PHP 生成
fastcgi_pass php-fpm;
fastcgi_param SCRIPT_FILENAME $document_root/thumbs.php;
include fastcgi_params;
}
# 由 PHP 生成快取檔至 /var/www/cache/thumbs/...
動態縮略圖方案靈活高效,配合快取機制可支撐高併發圖片服務。根據流量規模,可漸進式升級為「上傳時預生成 + CDN 分發」架構。