核心 GD 函式速查
| 函式 | 用途 |
imagecreatefromjpeg/gif/png/webp/bmp | 載入不同格式圖片 |
getimagesize() | 取得寬高、MIME |
imagecopymerge() | 疊加並混合透明度 (0-100) |
imagecopy() | 直接複製像素(配合 Alpha 通道) |
imagejpeg/gif/png/webp/bmp() | 輸出/儲存 |
通用水印函式 img_water_mark()
<?php
/**
* 圖片加水印(支援 PNG/JPG/GIF)
*
* @param string $srcImg 來源圖片路徑
* @param string $waterImg 水印圖片路徑
* @param string|null $savePath 存放資料夾(null=同目錄)
* @param string|null $saveName 存檔名稱(null=同檔名)
* @param int $position 1=左上 2=右上 3=置中 4=左下 5=右下
* @param int $alpha 透明度 0-100
* @return string|int 成功回傳新檔路徑,失敗回傳負數錯誤碼
*/
function img_water_mark(string $srcImg, string $waterImg, ?string $savePath = null, ?string $saveName = null, int $position = 5, int $alpha = 30) {
$srcInfo = @getimagesize($srcImg);
if (!$srcInfo) return -1; // 來源不存在
$waterInfo = @getimagesize($waterImg);
if (!$waterInfo) return -2; // 水印不存在
$srcIm = loadImage($srcImg, $srcInfo[2]);
if (!$srcIm) return -3;
$waterIm = loadImage($waterImg, $waterInfo[2]);
if (!$waterIm) return -4;
// 計算座標
$positions = [
1 => [0, 0],
2 => [$srcInfo[0]-$waterInfo[0], 0],
3 => [($srcInfo[0]-$waterInfo[0])/2, ($srcInfo[1]-$waterInfo[1])/2],
4 => [0, $srcInfo[1]-$waterInfo[1]],
5 => [$srcInfo[0]-$waterInfo[0], $srcInfo[1]-$waterInfo[1]],
];
[$x, $y] = $positions[$position] ?? $positions[5];
imagecopymerge($srcIm, $waterIm, $x, $y, 0, 0, $waterInfo[0], $waterInfo[1], $alpha);
// 輸出路徑
$savePath = $savePath ?? dirname($srcImg);
$saveName = $saveName ?? basename($srcImg);
$saveFile = rtrim($savePath, '/') . '/' . $saveName;
$outFunc = ['imagegif','imagejpeg','imagepng'][$srcInfo[2] - 1] ?? null;
if (!$outFunc || !$outFunc($srcIm, $saveFile)) return -5;
imagedestroy($srcIm);
imagedestroy($waterIm);
return $saveFile;
}
function loadImage(string $file, int $type) {
$map = [1=>'imagecreatefromgif', 2=>'imagecreatefromjpeg', 3=>'imagecreatefrompng'];
return ($map[$type] ?? false) ? $map[$type]($file) : false;
}
// 使用範例
$result = img_water_mark('1.jpg', 'walter.gif', null, '2.jpg', 5, 80);
if (is_string($result)) echo "<img src='$result'>";
完整上傳+水印整合範例
<?php
// 設定
$maxSize = 2_000_000;
$upDir = 'uploadimg/';
$watermark = 1; // 1=開啟
$waterType = 2; // 1=文字 2=圖片
$waterPos = 1; // 1=左下 2=右下 3=左上 4=右上 5=置中
$waterStr = 'https://example.com';
$waterImg = 'xplore.gif';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upfile'])) {
$file = $_FILES['upfile'];
$allowed = ['image/jpeg','image/pjpeg','image/png','image/gif','image/bmp','image/x-png'];
if (!in_array($file['type'], $allowed)) die('類型不允許');
if ($file['size'] > $maxSize) die('檔案過大');
if (!is_dir($upDir)) mkdir($upDir, 0755, true);
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$dest = $upDir . time() . ".$ext";
if (!move_uploaded_file($file['tmp_name'], $dest)) die('移動失敗');
if ($watermark) {
$info = getimagesize($dest);
$im = loadImage($dest, $info[2]);
$canvas = imagecreatetruecolor($info[0], $info[1]);
$white = imagecolorallocate($canvas, 255, 255, 255);
imagefill($canvas, 0, 0, $white);
imagecopy($canvas, $im, 0, 0, 0, 0, $info[0], $info[1]);
if ($waterType === 1) {
$black = imagecolorallocate($canvas, 0, 0, 0);
imagestring($canvas, 2, 3, $info[1]-15, $waterStr, $black);
} elseif ($waterType === 2) {
$wm = imagecreatefromgif($waterImg);
$wx = $info[0] - imagesx($wm);
$wy = $info[1] - imagesy($wm);
imagecopy($canvas, $wm, $wx, $wy, 0, 0, imagesx($wm), imagesy($wm));
imagedestroy($wm);
}
$outFunc = ['imagegif','imagejpeg','imagepng'][$info[2]-1];
$outFunc($canvas, $dest);
imagedestroy($canvas);
imagedestroy($im);
}
echo "<p>上傳成功:<img src='$dest'></p>";
}
?>
<form method="post" enctype="multipart/form-data">
<input type="file" name="upfile" required>
<button type="submit">上傳加水印</button>
</form>
PNG 透明水印進階版(保留 Alpha)
function pngWatermark(string $src, string $dst, string $wm, int $pos = 2): bool {
$srcIm = imagecreatefromjpeg($src); // 假設背景為 JPG
$wmIm = imagecreatefrompng($wm); // 水印為透明 PNG
imagealphablending($srcIm, true);
imagesavealpha($srcIm, true);
$sw = imagesx($srcIm); $sh = imagesy($srcIm);
$ww = imagesx($wmIm); $wh = imagesy($wmIm);
$positions = [
0 => [0, 0],
1 => [$sw-$ww, 0],
2 => [$sw-$ww, $sh-$wh],
3 => [0, $sh-$wh],
];
[$x, $y] = $positions[$pos] ?? $positions[2];
imagecopy($srcIm, $wmIm, $x, $y, 0, 0, $ww, $wh);
imagejpeg($srcIm, $dst, 90);
imagedestroy($srcIm); imagedestroy($wmIm);
return true;
}
常見問題與解決
| 問題 | 原因 | 解決 |
| 水印位置偏移 | 座標計算錯誤 | 確認 $x, $y 為目標畫布左上角座標,水印右下角不超過邊界 |
| 透明水印變黑底 | 未開啟 Alpha 混色 | 在目標畫布呼叫 imagealphablending($im, true); imagesavealpha($im, true); |
| 文字水印中文亂碼 | 字體不支援/編碼錯誤 | 使用支援中文的 TTF,原始碼存 UTF-8,直接傳入 UTF-8 字串 |
| 大圖記憶體不足 | GD 需解壓整張圖到記憶體 | 調大 memory_limit、分批處理、或改用 Imagick 串流處理 |
現代化建議
新專案請使用 Intervention/Image:
composer require intervention/image
// 文字水印
Image::make('photo.jpg')
->text('版權所有', 10, 10, function($font) {
$font->file('fonts/NotoSansTC-Regular.otf');
$font->size(24);
$font->color('#ff0000');
$font->align('center');
$font->valign('bottom');
})
->save('watermarked.jpg');
// 圖片水印
Image::make('photo.jpg')
->insert('logo.png', 'bottom-right', 10, 10)
->save('watermarked.jpg');