電商商品圖、用戶頭像、文章配圖等場景常需生成多種尺寸縮略圖。既要支援「按原圖百分比縮放」,也要支援「指定最大寬高、等比縮放不變形」,且優先級明確。
<?php
/**
* 生成縮略圖
*
* @param string $srcPath 原圖路徑
* @param string $dstPath 目標儲存路徑
* @param int $percent 百分比模式:1-100,優先級最高
* @param int $maxWidth 最大寬度(像素)
* @param int $maxHeight 最大高度(像素)
* @param int $quality JPEG 輸出品質 0-100
* @return bool 成功返回 true,失敗拋出異常
*/
function makeThumbnail(
string $srcPath,
string $dstPath,
int $percent = 0,
int $maxWidth = 0,
int $maxHeight = 0,
int $quality = 85
): bool {
// 1. 讀取原圖資訊
if (!is_file($srcPath) || !is_readable($srcPath)) {
throw new InvalidArgumentException("原圖不存在或不可讀: {$srcPath}");
}
$info = getimagesize($srcPath);
if ($info === false) {
throw new RuntimeException("無法識別圖片格式: {$srcPath}");
}
[$srcW, $srcH, $type] = $info;
// 2. 根據類型建立 GD 資源
$createFrom = match ($type) {
IMAGETYPE_GIF => 'imagecreatefromgif',
IMAGETYPE_JPEG => 'imagecreatefromjpeg',
IMAGETYPE_PNG => 'imagecreatefrompng',
IMAGETYPE_WEBP => 'imagecreatefromwebp',
default => throw new RuntimeException("不支援的圖片類型: {$type}"),
};
$srcImg = $createFrom($srcPath);
if (!$srcImg) {
throw new RuntimeException("GD 建立圖片資源失敗: {$srcPath}");
}
// 3. 計算目標尺寸
if ($percent > 0 && $percent <= 100) {
// 百分比模式優先
$dstW = max(1, (int)round($srcW * $percent / 100));
$dstH = max(1, (int)round($srcH * $percent / 100));
} elseif ($maxWidth > 0 || $maxHeight > 0) {
// 等比縮放至限制框內
$ratioW = $maxWidth > 0 ? $maxWidth / $srcW : INF;
$ratioH = $maxHeight > 0 ? $maxHeight / $srcH : INF;
$ratio = min($ratioW, $ratioH, 1); // 不放大,僅縮小
$dstW = max(1, (int)round($srcW * $ratio));
$dstH = max(1, (int)round($srcH * $ratio));
} else {
throw new InvalidArgumentException('必須指定 percent 或 maxWidth/maxHeight 至少一項');
}
// 4. 建立真彩色畫布並重採樣
$dstImg = imagecreatetruecolor($dstW, $dstH);
// 保持 PNG/GIF/WEBP 透明度
if (in_array($type, [IMAGETYPE_PNG, IMAGETYPE_GIF, IMAGETYPE_WEBP], true)) {
imagealphablending($dstImg, false);
imagesavealpha($dstImg, true);
$transparent = imagecolorallocatealpha($dstImg, 255, 255, 255, 127);
imagefill($dstImg, 0, 0, $transparent);
}
// 使用 imagecopyresampled 而非 imagecopyresized,品質更高
imagecopyresampled($dstImg, $srcImg, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH);
// 5. 輸出儲存
$saveFunc = match ($type) {
IMAGETYPE_GIF => fn() => imagegif($dstImg, $dstPath),
IMAGETYPE_JPEG => fn() => imagejpeg($dstImg, $dstPath, $quality),
IMAGETYPE_PNG => fn() => imagepng($dstImg, $dstPath, 9), // PNG 壓縮級別 0-9
IMAGETYPE_WEBP => fn() => imagewebp($dstImg, $dstPath, $quality),
default => throw new RuntimeException("不支援的輸出類型"),
};
$result = $saveFunc();
// 6. 釋放資源
imagedestroy($srcImg);
imagedestroy($dstImg);
return $result;
}
?>
<?php
require_once 'thumbnail.php';
try {
// 1. 生成原圖 15% 縮略圖
makeThumbnail('uploads/photo.jpg', 'thumbs/photo_15pct.jpg', 15);
// 2. 生成寬不超過 200px、高不超過 120px 的等比縮略圖
makeThumbnail('uploads/photo.jpg', 'thumbs/photo_200x120.jpg', 0, 200, 120);
// 3. 同時指定百分比與尺寸,百分比優先
makeThumbnail('uploads/photo.jpg', 'thumbs/photo_mixed.jpg', 15, 200, 120);
// 4. PNG 保持透明背景
makeThumbnail('uploads/logo.png', 'thumbs/logo_thumb.png', 0, 100, 100);
echo "所有縮略圖生成成功!\n";
} catch (Throwable $e) {
error_log("縮略圖生成失敗: " . $e->getMessage());
echo "錯誤: " . $e->getMessage() . "\n";
}
?>
imagick 擴展,效能與品質更優。<picture> 標籤回退。memory_limit,或分塊處理。php.ini 必須啟用 extension=gd 並編譯支援 JPEG/PNG/FreeType。imagecopyresized 為最近鄰插值,鋸齒嚴重,務必用 imagecopyresampled(雙線性插值)。