針對 GD2 生成 PNG 縮略圖時「透明變黑、格式強制轉 JPG」的經典痛點,本文提供改良版類,完整保留 Alpha 通道,支援 PNG/GIF/JPG 統一處理,代碼結構現代化(命名空間、類型聲明、異常處理)。
imagecreatetruecolor + imagefill(..., imagecolorallocatealpha(..., 127)) + imagesavealpha(true) 三步驟建立全透明畫布。private 封裝、__construct 初始化、異常拋出、類型提示。<?php
/**
* PNG 透明友好的縮略圖生成器
* @author 十年後的盧哥哥
* @date 2014-11-13 (重構優化版)
*/
class ResizeImage {
private string $srcPath;
private string $dstPath;
private int $targetW;
private int $targetH;
private bool $cut;
private string $type;
private $imgRes; // GD resource
private int $srcW, $srcH;
public function __construct(string $srcPath, int $width, int $height, bool $isCut, string $savePath) {
$this->srcPath = $srcPath;
$this->targetW = $width;
$this->targetH = $height;
$this->cut = $isCut;
$this->dstPath = $savePath;
$this->type = strtolower(pathinfo($srcPath, PATHINFO_EXTENSION));
$this->initImage();
$this->srcW = imagesx($this->imgRes);
$this->srcH = imagesy($this->imgRes);
$this->process();
imagedestroy($this->imgRes);
}
private function initImage(): void {
switch ($this->type) {
case 'jpg': case 'jpeg': $this->imgRes = imagecreatefromjpeg($this->srcPath); break;
case 'png': $this->imgRes = imagecreatefrompng($this->srcPath); break;
case 'gif': $this->imgRes = imagecreatefromgif($this->srcPath); break;
default: throw new \InvalidArgumentException("Unsupported image type: {$this->type}");
}
if (!$this->imgRes) throw new \RuntimeException("Failed to load image: {$this->srcPath}");
}
private function process(): void {
$srcRatio = $this->srcW / $this->srcH;
$dstRatio = $this->targetW / $this->targetH;
if ($this->cut) {
// 裁剪模式:目標尺寸固定
$canvas = $this->createTransparentCanvas($this->targetW, $this->targetH);
if ($srcRatio >= $dstRatio) {
// 寬度富餘,按高度縮放,左右裁剪
$cropW = $this->srcH * $dstRatio;
$srcX = ($this->srcW - $cropW) / 2;
imagecopyresampled($canvas, $this->imgRes, 0, 0, $srcX, 0, $this->targetW, $this->targetH, $cropW, $this->srcH);
} else {
// 高度富餘,按寬度縮放,上下裁剪
$cropH = $this->srcW / $dstRatio;
$srcY = ($this->srcH - $cropH) / 2;
imagecopyresampled($canvas, $this->imgRes, 0, 0, 0, $srcY, $this->targetW, $this->targetH, $this->srcW, $cropH);
}
} else {
// 等比縮放:畫布尺寸自適應
if ($srcRatio >= $dstRatio) {
$newW = $this->targetW;
$newH = $this->targetW / $srcRatio;
} else {
$newH = $this->targetH;
$newW = $this->targetH * $srcRatio;
}
$canvas = $this->createTransparentCanvas($newW, $newH);
imagecopyresampled($canvas, $this->imgRes, 0, 0, 0, 0, $newW, $newH, $this->srcW, $this->srcH);
}
$this->saveOutput($canvas);
imagedestroy($canvas);
}
/** 建立支援透明的真彩色畫布 */
private function createTransparentCanvas(int $w, int $h) {
$canvas = imagecreatetruecolor($w, $h);
if ($this->type === 'png' || $this->type === 'gif') {
// 關鍵三步:關閉混色、分配全透明色、填充、啟用 Alpha 保存
imagealphablending($canvas, false);
$transparent = imagecolorallocatealpha($canvas, 0, 0, 0, 127);
imagefill($canvas, 0, 0, $transparent);
imagesavealpha($canvas, true);
}
return $canvas;
}
private function saveOutput($canvas): void {
switch ($this->type) {
case 'png':
imagepng($canvas, $this->dstPath, 6); // 壓縮等級 6
break;
case 'gif':
imagegif($canvas, $this->dstPath);
break;
default: // jpg/jpeg
imagejpeg($canvas, $this->dstPath, 90);
}
}
}
?>
<?php
require 'ResizeImage.php';
// JPG 等比縮放
new ResizeImage("img/photo.jpg", 320, 240, false, "img/photo_320x240.jpg");
// PNG 裁剪模式,保留透明背景
new ResizeImage("img/logo.png", 200, 200, true, "img/logo_200x200.png");
?>
| 現象 | 原因 | 解決 |
|---|---|---|
| PNG 變黑底 | 未關閉 alpha blending、未 imagesavealpha | 參考 createTransparentCanvas() 實現 |
| GIF 動畫只剩第一幀 | GD 不支援多幀處理 | 改用 Imagick 或 ffmpeg |
| 記憶體耗盡 | 大圖解碼佔用巨大內存 | 分塊處理、增加 memory_limit、或用 Imagick 流式處理 |