核心函數
<?php
/**
* 給 JPEG 圖片加 PNG 水印 (右下角)
* @param string $target 源圖路徑 (JPG)
* @param string $wtrmrk_file 水印圖路徑 (PNG,帶透明通道)
* @param string $newcopy 輸出路徑
* @return bool
*/
function watermark_image(string $target, string $wtrmrk_file, string $newcopy): bool
{
// 1. 載入水印 (保留 Alpha)
$watermark = imagecreatefrompng($wtrmrk_file);
imagealphablending($watermark, false);
imagesavealpha($watermark, true);
// 2. 載入目標圖
$img = imagecreatefromjpeg($target);
if (!$img || !$watermark) return false;
// 3. 計算座標 (右下角,留 10px 邊距)
$img_w = imagesx($img);
$img_h = imagesy($img);
$w_w = imagesx($watermark);
$w_h = imagesy($watermark);
$dst_x = $img_w - $w_w - 10;
$dst_y = $img_h - $w_h - 10;
// 4. 疊加
imagecopy($img, $watermark, $dst_x, $dst_y, 0, 0, $w_w, $w_h);
// 5. 輸出
$ok = imagejpeg($img, $newcopy, 90); // 品質 90
// 6. 釋放資源
imagedestroy($img);
imagedestroy($watermark);
return $ok;
}
// 使用
watermark_image('photo.jpg', 'logo.png', 'photo_wm.jpg');
?>
進階:支援多格式、多位置、文字水印
<?php
class Watermarker
{
public function __construct(
public int $margin = 10,
public int $quality = 85,
public int $position = 9 // 1-9 九宮格,9=右下
) {}
public function add(string $src, string $water, string $dst): bool
{
$srcInfo = getimagesize($src);
$waterInfo = getimagesize($water);
if (!$srcInfo || !$waterInfo) return false;
$createSrc = match ($srcInfo[2]) {
IMAGETYPE_JPEG => 'imagecreatefromjpeg',
IMAGETYPE_PNG => 'imagecreatefrompng',
IMAGETYPE_GIF => 'imagecreatefromgif',
default => null,
};
$createWater = match ($waterInfo[2]) {
IMAGETYPE_PNG => 'imagecreatefrompng', // 水印建議 PNG
default => null,
};
if (!$createSrc || !$createWater) return false;
$img = $createSrc($src);
$wm = $createWater($water);
imagealphablending($wm, false);
imagesavealpha($wm, true);
[$sx, $sy] = [$srcInfo[0], $srcInfo[1]];
[$wx, $wy] = [$waterInfo[0], $waterInfo[1]];
// 九宮格座標
$positions = [
1 => [$this->margin, $this->margin],
2 => [($sx-$wx)/2, $this->margin],
3 => [$sx-$wx-$this->margin, $this->margin],
4 => [$this->margin, ($sy-$wy)/2],
5 => [($sx-$wx)/2, ($sy-$wy)/2],
6 => [$sx-$wx-$this->margin, ($sy-$wy)/2],
7 => [$this->margin, $sy-$wy-$this->margin],
8 => [($sx-$wx)/2, $sy-$wy-$this->margin],
9 => [$sx-$wx-$this->margin, $sy-$wy-$this->margin],
];
[$dx, $dy] = $positions[$this->position] ?? $positions[9];
imagecopy($img, $wm, $dx, $dy, 0, 0, $wx, $wy);
$save = match ($srcInfo[2]) {
IMAGETYPE_JPEG => imagejpeg($img, $dst, $this->quality),
IMAGETYPE_PNG => imagepng($img, $dst, 9),
IMAGETYPE_GIF => imagegif($img, $dst),
default => false,
};
imagedestroy($img);
imagedestroy($wm);
return $save;
}
/** 文字水印 */
public function addText(string $src, string $text, string $dst, int $fontSize = 20, string $color = 'FFFFFF', int $alpha = 50): bool
{
// ... 類似邏輯,使用 imagettftext ...
}
}
// 用法
$wm = new Watermarker(margin: 20, position: 9);
$wm->add('photo.jpg', 'logo.png', 'out.jpg');
?>
常見問題
| 問題 | 解決 |
| 水印背景變黑 | 必須 imagealphablending($wm, false); imagesavealpha($wm, true); |
| 輸出圖變模糊 | JPEG 品質參數調高 (80-95),或輸出 PNG |
| 記憶體溢出 | 大圖先 imagescale 縮小再加水印,或用 Imagick 流式處理 |
| 透明 PNG 輸出背景黑 | 目標圖若為 PNG,同樣需開啟 alphablending/savealpha |
效能建議
- 高併發場景改用 Imagick (C 底層,多線程、流式、記憶體友好)
- CDN 邊緣節點加水印 (如 Cloudflare Workers、阿里雲 CDN 圖片處理參數)
- 預生成多種尺寸水印圖,避免運行時縮放