GD 庫是 PHP 內建圖形處理擴展,無需額外安裝即可實現圖片生成、縮放、裁剪、水印、統計圖繪製等核心功能。本文提供五大經典場景的**可直接落地的封裝函數**,涵蓋參數驗證、資源釋放、錯誤處理等工程化細節。
<?php
// 確認 GD 支援
if (!extension_loaded('gd')) die('GD extension not loaded');
$info = gd_info();
echo "GD Version: {$info['GD Version']}\n";
echo "FreeType: " . ($info['FreeType Support'] ? 'Yes' : 'No') . "\n";
echo "PNG/JPG/GIF: " . ($info['PNG Support'] && $info['JPEG Support'] && $info['GIF Read Support'] ? 'Yes' : 'No') . "\n";
?>
利用多層偏移繪製偽 3D 效果,適合簡單報表嵌入。
<?php
function draw3DPieChart(array $data, int $width = 400, int $height = 300): void {
// $data = ['標籤' => ['value' => 數值, 'color' => '十六進制顏色', 'dark' => '陰影色']]
$img = imagecreatetruecolor($width, $height);
$white = imagecolorallocate($img, 0xFF, 0xFF, 0xFF);
imagefill($img, 0, 0, $white);
$cx = $width / 2;
$cy = $height / 2;
$rx = min($width, $height) * 0.4; // x 半徑
$ry = $rx * 0.5; // y 半徑 (橢圓)
$total = array_sum(array_column($data, 'value'));
$startAngle = -90; // 從頂部開始
// 1. 繪製 3D 側面 (向下疊加 10 層)
for ($depth = 10; $depth > 0; $depth--) {
$angle = $startAngle;
foreach ($data as $item) {
$endAngle = $angle + ($item['value'] / $total) * 360;
$darkColor = imagecolorallocate($img,
hexdec(substr($item['dark'], 1, 2)),
hexdec(substr($item['dark'], 3, 2)),
hexdec(substr($item['dark'], 5, 2))
);
imagefilledarc($img, $cx, $cy + $depth, $rx * 2, $ry * 2, $angle, $endAngle, $darkColor, IMG_ARC_PIE);
$angle = $endAngle;
}
}
// 2. 繪製頂面 (實際數據層)
$angle = $startAngle;
foreach ($data as $label => $item) {
$endAngle = $angle + ($item['value'] / $total) * 360;
$color = imagecolorallocate($img,
hexdec(substr($item['color'], 1, 2)),
hexdec(substr($item['color'], 3, 2)),
hexdec(substr($item['color'], 5, 2))
);
imagefilledarc($img, $cx, $cy, $rx * 2, $ry * 2, $angle, $endAngle, $color, IMG_ARC_PIE);
// 繪製標籤 (簡易版,生產建議計算文本位置避免重疊)
$midAngle = deg2rad(($angle + $endAngle) / 2);
$tx = $cx + cos($midAngle) * $rx * 0.6;
$ty = $cy + sin($midAngle) * $ry * 0.6;
imagestring($img, 3, $tx, $ty, "{$label} " . round($item['value']/$total*100) . "%", $white);
$angle = $endAngle;
}
header('Content-Type: image/png');
imagepng($img);
imagedestroy($img);
}
// 使用範例
$data = [
'Chrome' => ['value' => 60, 'color' => '#4285F4', 'dark' => '#1a5fd8'],
'Firefox' => ['value' => 20, 'color' => '#FF9500', 'dark' => '#cc7700'],
'Safari' => ['value' => 10, 'color' => '#34A853', 'dark' => '#25853f'],
'Edge' => ['value' => 5, 'color' => '#0078D7', 'dark' => '#005fa3'],
'Other' => ['value' => 5, 'color' => '#9AA0A6', 'dark' => '#787e84'],
];
draw3DPieChart($data);
?>
<?php
/**
* 等比縮放並保存
* @param string $srcPath 來源圖路徑
* @param string $dstPath 目標路徑
* @param int $maxW 最大寬度
* @param int $maxH 最大高度
* @param int $quality JPEG 品質 0-100
* @return bool
*/
function resizeImage(string $srcPath, string $dstPath, int $maxW = 800, int $maxH = 600, int $quality = 85): bool {
if (!file_exists($srcPath)) return false;
list($srcW, $srcH, $type) = getimagesize($srcPath);
if ($srcW <= $maxW && $srcH <= $maxH) {
// 無需縮放,直接複製或返回 true
return copy($srcPath, $dstPath);
}
// 計算目標尺寸
$ratio = min($maxW / $srcW, $maxH / $srcH);
$dstW = (int) round($srcW * $ratio);
$dstH = (int) round($srcH * $ratio);
// 建立來源資源
$createFn = match ($type) {
IMAGETYPE_JPEG => 'imagecreatefromjpeg',
IMAGETYPE_PNG => 'imagecreatefrompng',
IMAGETYPE_GIF => 'imagecreatefromgif',
IMAGETYPE_WEBP => 'imagecreatefromwebp',
default => null,
};
if (!$createFn) return false;
$srcImg = $createFn($srcPath);
if (!$srcImg) return false;
// 建立目標真彩色畫布
$dstImg = imagecreatetruecolor($dstW, $dstH);
// **關鍵:保持 PNG/GIF 透明度**
if ($type === IMAGETYPE_PNG || $type === IMAGETYPE_GIF) {
imagealphablending($dstImg, false);
imagesavealpha($dstImg, true);
$transparent = imagecolorallocatealpha($dstImg, 255, 255, 255, 127);
imagefilledrectangle($dstImg, 0, 0, $dstW, $dstH, $transparent);
}
// 高質量重採樣縮放
imagecopyresampled($dstImg, $srcImg, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH);
// 輸出
$result = match ($type) {
IMAGETYPE_JPEG => imagejpeg($dstImg, $dstPath, $quality),
IMAGETYPE_PNG => imagepng($dstImg, $dstPath, 9), // PNG 壓縮級別 0-9
IMAGETYPE_GIF => imagegif($dstImg, $dstPath),
IMAGETYPE_WEBP => imagewebp($dstImg, $dstPath, $quality),
default => false,
};
imagedestroy($srcImg);
imagedestroy($dstImg);
return $result;
}
?>
<?php
/**
* 裁剪圖片
* @param string $srcPath
* @param string $dstPath
* @param int $x 裁剪區左上角 X (相對原圖)
* @param int $y 裁剪區左上角 Y
* @param int $w 裁剪寬度
* @param int $h 裁剪高度
* @param int|null $dstW 輸出寬度 (若指定則縮放至該尺寸)
* @param int|null $dstH 輸出高度
*/
function cropImage(string $srcPath, string $dstPath, int $x, int $y, int $w, int $h, ?int $dstW = null, ?int $dstH = null): bool {
list($srcW, $srcH, $type) = getimagesize($srcPath);
if ($x < 0 || $y < 0 || $x + $w > $srcW || $y + $h > $srcH) return false;
$dstW = $dstW ?? $w;
$dstH = $dstH ?? $h;
$createFn = [IMAGETYPE_JPEG=>'imagecreatefromjpeg', IMAGETYPE_PNG=>'imagecreatefrompng', IMAGETYPE_GIF=>'imagecreatefromgif', IMAGETYPE_WEBP=>'imagecreatefromwebp'][$type] ?? null;
if (!$createFn) return false;
$srcImg = $createFn($srcPath);
if (!$srcImg) return false;
$dstImg = imagecreatetruecolor($dstW, $dstH);
// 透明度處理同 resizeImage
if (in_array($type, [IMAGETYPE_PNG, IMAGETYPE_GIF])) {
imagealphablending($dstImg, false);
imagesavealpha($dstImg, true);
$tr = imagecolorallocatealpha($dstImg, 255, 255, 255, 127);
imagefilledrectangle($dstImg, 0, 0, $dstW, $dstH, $tr);
}
// 裁剪並可選縮放
imagecopyresampled($dstImg, $srcImg, 0, 0, $x, $y, $dstW, $dstH, $w, $h);
$saveFn = [IMAGETYPE_JPEG=>'imagejpeg', IMAGETYPE_PNG=>'imagepng', IMAGETYPE_GIF=>'imagegif', IMAGETYPE_WEBP=>'imagewebp'][$type];
$result = $saveFn($dstImg, $dstPath, $type === IMAGETYPE_JPEG || $type === IMAGETYPE_WEBP ? 85 : 9);
imagedestroy($srcImg);
imagedestroy($dstImg);
return $result;
}
?>
<?php
/**
* 旋轉圖片
* @param string $srcPath
* @param string $dstPath
* @param int $angle 旋轉角度 (順時針),建議 90/180/270
* @param array $bgColor 背景色 RGB [255,255,255]
*/
function rotateImage(string $srcPath, string $dstPath, int $angle, array $bgColor = [255,255,255]): bool {
$angle = $angle % 360;
if ($angle === 0) return copy($srcPath, $dstPath);
list(,,$type) = getimagesize($srcPath);
$createFn = [IMAGETYPE_JPEG=>'imagecreatefromjpeg', IMAGETYPE_PNG=>'imagecreatefrompng', IMAGETYPE_GIF=>'imagecreatefromgif'][$type] ?? null;
if (!$createFn) return false;
$srcImg = $createFn($srcPath);
if (!$srcImg) return false;
// imagerotate 角度為逆時針,需轉換
$rotated = imagerotate($srcImg, 360 - $angle, imagecolorallocate($srcImg, ...$bgColor));
if (!$rotated) return false;
// 透明度處理
if (in_array($type, [IMAGETYPE_PNG, IMAGETYPE_GIF])) {
imagealphablending($rotated, false);
imagesavealpha($rotated, true);
}
$saveFn = [IMAGETYPE_JPEG=>'imagejpeg', IMAGETYPE_PNG=>'imagepng', IMAGETYPE_GIF=>'imagegif'][$type];
$result = $saveFn($rotated, $dstPath, 90);
imagedestroy($srcImg);
imagedestroy($rotated);
return $result;
}
?>
<?php
/**
* 添加水印
* @param string $srcPath 背景圖
* @param string $waterPath 水印圖 (建議 PNG 透明)
* @param string $dstPath
* @param int $position 位置: 1-9 (數字鍵盤對應), 0=隨機, 10=平鋪
* @param int $alpha 水印透明度 0-100 (100 不透明)
* @param int $margin 邊距
*/
function watermarkImage(string $srcPath, string $waterPath, string $dstPath, int $position = 9, int $alpha = 50, int $margin = 10): bool {
list($srcW, $srcH, $srcType) = getimagesize($srcPath);
list($wW, $wH, $wType) = getimagesize($waterPath);
if ($wW > $srcW || $wH > $srcH) return false; // 水印大於背景
$srcCreate = [IMAGETYPE_JPEG=>'imagecreatefromjpeg', IMAGETYPE_PNG=>'imagecreatefrompng', IMAGETYPE_GIF=>'imagecreatefromgif', IMAGETYPE_WEBP=>'imagecreatefromwebp'][$srcType] ?? null;
$waterCreate = [IMAGETYPE_PNG=>'imagecreatefrompng', IMAGETYPE_GIF=>'imagecreatefromgif', IMAGETYPE_JPEG=>'imagecreatefromjpeg'][$wType] ?? null;
if (!$srcCreate || !$waterCreate) return false;
$srcImg = $srcCreate($srcPath);
$waterImg = $waterCreate($waterPath);
if (!$srcImg || !$waterImg) return false;
// 設定水印透明度 (需 GD 2.0.1+)
imagefilter($waterImg, IMG_FILTER_COLORIZE, 0, 0, 0, (100 - $alpha) * 2.55); // 近似處理
// 更精確:遍歷像素修改 alpha 通道 (略)
// 計算位置
$positions = [
1 => [$margin, $srcH - $wH - $margin], // 左下
2 => [($srcW - $wW) / 2, $srcH - $wH - $margin], // 下中
3 => [$srcW - $wW - $margin, $srcH - $wH - $margin], // 右下
4 => [$margin, ($srcH - $wH) / 2], // 左中
5 => [($srcW - $wW) / 2, ($srcH - $wH) / 2], // 正中
6 => [$srcW - $wW - $margin, ($srcH - $wH) / 2], // 右中
7 => [$margin, $margin], // 左上
8 => [($srcW - $wW) / 2, $margin], // 上中
9 => [$srcW - $wW - $margin, $margin], // 右上 (預設)
];
if ($position === 0) { // 隨機
$posX = rand($margin, $srcW - $wW - $margin);
$posY = rand($margin, $srcH - $wH - $margin);
} elseif ($position === 10) { // 平鋪
for ($x = 0; $x < $srcW; $x += $wW + $margin) {
for ($y = 0; $y < $srcH; $y += $wH + $margin) {
imagecopy($srcImg, $waterImg, $x, $y, 0, 0, $wW, $wH);
}
}
goto save_output;
} else {
[$posX, $posY] = $positions[$position] ?? $positions[9];
}
imagecopy($srcImg, $waterImg, (int)$posX, (int)$posY, 0, 0, $wW, $wH);
save_output:
$saveFn = [IMAGETYPE_JPEG=>'imagejpeg', IMAGETYPE_PNG=>'imagepng', IMAGETYPE_GIF=>'imagegif', IMAGETYPE_WEBP=>'imagewebp'][$srcType];
$result = $saveFn($srcImg, $dstPath, $srcType === IMAGETYPE_JPEG || $srcType === IMAGETYPE_WEBP ? 90 : 9);
imagedestroy($srcImg);
imagedestroy($waterImg);
return $result;
}
?>
掌握這五大核心操作與工程化細節,PHP GD 即可滿足 90% 以上的圖片處理業務需求。