PHP imagecopyresized() / imagecopyresampled() 縮放 GIF 時,透明色會變成黑色背景。原因:真彩色畫布預設背景為黑,且未正確複製調色板透明索引。
imagecolortransparent($src)>= 0 且 < imagecolorstotal($src)),讀取其 RGB:imagecolorsforindex()imagecolorallocate($dst, $r, $g, $b)imagefill($dst, 0, 0, $transparentColor)imagecolortransparent($dst, $transparentColor)imagegif() 會自動保留透明色<?php
declare(strict_types=1);
class GifResizer {
private float $scale = 0.5; // 縮放比例 0.1~1.0
public function __construct(float $scale = 0.5) {
$this->scale = max(0.01, min(1.0, $scale));
}
public function resize(string $sourcePath, string $destPath): bool {
if (!is_file($sourcePath) || !is_readable($sourcePath)) {
throw new \InvalidArgumentException("Source file not readable: {$sourcePath}");
}
// 僅處理 GIF
$info = getimagesize($sourcePath);
if ($info[2] !== IMAGETYPE_GIF) {
throw new \InvalidArgumentException('Only GIF images are supported');
}
$src = imagecreatefromgif($sourcePath);
if (!$src) throw new \RuntimeException('Failed to create GD resource from GIF');
$srcW = imagesx($src);
$srcH = imagesy($src);
$dstW = (int)round($srcW * $this->scale);
$dstH = (int)round($srcH * $this->scale);
// 等比修正(防止長寬比失真)
if ($dstW && $srcW < $srcH) {
$dstW = (int)round($dstH / $srcH * $srcW);
} else {
$dstH = (int)round($dstW / $srcW * $srcH);
}
// 建立真彩色目標畫布
$dst = imagecreatetruecolor($dstW, $dstH);
if (!$dst) {
imagedestroy($src);
throw new \RuntimeException('Failed to create truecolor canvas');
}
// === 關鍵:透明色處理 ===
$transparentIndex = imagecolortransparent($src);
if ($transparentIndex >= 0 && $transparentIndex < imagecolorstotal($src)) {
$rgb = imagecolorsforindex($src, $transparentIndex);
$transparentColor = imagecolorallocate($dst, $rgb['red'], $rgb['green'], $rgb['blue']);
imagefill($dst, 0, 0, $transparentColor);
imagecolortransparent($dst, $transparentColor);
} else {
// 無透明色:關閉 alpha 混合,避免黑背景
imagealphablending($dst, false);
$transparent = imagecolorallocatealpha($dst, 0, 0, 0, 127);
imagefill($dst, 0, 0, $transparent);
imagesavealpha($dst, true);
}
// 高質量重採樣
imagecopyresampled($dst, $src, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH);
// 輸出
$result = imagegif($dst, $destPath);
// 清理
imagedestroy($src);
imagedestroy($dst);
return $result;
}
// 批次處理
public function resizeBatch(array $pairs): array {
$results = [];
foreach ($pairs as $src => $dest) {
try {
$results[$src] = $this->resize($src, $dest) ? 'success' : 'failed';
} catch (\Throwable $e) {
$results[$src] = 'error: ' . $e->getMessage();
}
}
return $results;
}
}
// 使用示例
$resizer = new GifResizer(0.5); // 50% 縮放
$resizer->resize('/path/source.gif', '/path/thumb.gif');
// 批次
$results = $resizer->resizeBatch([
'/img/a.gif' => '/thumb/a.gif',
'/img/b.gif' => '/thumb/b.gif',
]);
print_r($results);
| 步驟 | 錯誤做法(黑背景) | 正確做法 |
|---|---|---|
| 目標畫布建立 | imagecreate() (調色板模式) | imagecreatetruecolor() (真彩色) |
| 透明色索引獲取 | 忽略 | imagecolortransparent($src) |
| RGB 分配 | 不分配 | imagecolorallocate($dst, r, g, b) |
| 背景填充 | 不填充或填充黑 | imagefill($dst, 0, 0, $transparentColor) |
| 設定目標透明色 | 忽略 | imagecolortransparent($dst, $transparentColor) |
| 縮放函式 | imagecopyresized() | imagecopyresampled() (高質量) |
| 無透明色 GIF | 黑背景 | imagealphablending(false) + imagesavealpha(true) |
imagecopyresampled:雙三次插值,品質最好,CPU 較高imagecopyresized:最近鄰插值,速度快,鋸齒明顯imagick 擴展(多線程、更優演算法)或 gifski / gifsicle 命令列工具<?php
$imagick = new Imagick('/path/source.gif');
$imagick->setIteratorIndex(0); // 僅第一幀,或遍歷所有幀
$imagick->resizeImage(
$imagick->getImageWidth() * 0.5,
$imagick->getImageHeight() * 0.5,
Imagick::FILTER_LANCZOS,
1
);
// 保留動畫需遍歷幀,略
$imagick->writeImage('/path/thumb.gif');
Imagick 原生支援動畫 GIF 多幀縮放、更優壓縮、無黑背景問題,生產環境首選。
GIF 縮放黑背景的根因是 真彩色畫布預設黑背景 + 未正確複製透明色索引。核心修復只需 5 行代碼:獲取索引 → 讀取 RGB → 分配色彩 → 填充背景 → 設定透明。封裝為 GifResizer 類即可復用。新專案建議直接使用 Imagick 或外部工具 gifsicle,徹底規避 GD 歷史包袱。