利用 PHP GD 擴充庫的 imagettftext() 函式可繪製 TrueType 字型文字,配合中文字型檔 (如微軟雅黑、思源黑體) 即可實現中文水印。本文提供完整函式封裝、字型處理、透明度控制、平鋪水印與批量處理範例。
<?php
/**
* 給圖片添加中文文字水印
* @param string $srcImage 來源圖片路徑
* @param string $dstImage 輸出圖片路徑 (空字串則直接輸出瀏覽器)
* @param string $text 水印文字 (UTF-8)
* @param int $fontSize 字體大小 (pt)
* @param int $angle 旋轉角度 (度,順時針)
* @param int $x X 座標 (左上角為原點)
* @param int $y Y 座標
* @param string $fontFile TTF 字型檔絕對路徑
* @param array $color RGB 顏色 [r,g,b] 0-255
* @param int $alpha 透明度 0-127 (0不透明, 127全透明)
* @return bool 成功返回 true
*/
function addTextWatermark(
string $srcImage,
string $dstImage,
string $text,
int $fontSize = 20,
int $angle = 0,
int $x = 10,
int $y = 30,
string $fontFile = '',
array $color = [255, 255, 255],
int $alpha = 30
): bool {
// 預設字型搜尋路徑
if (!$fontFile) {
$fontPaths = [
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', // Linux 常見
'/System/Library/Fonts/PingFang.ttc', // macOS
'C:/Windows/Fonts/msyh.ttc', // Windows 微軟雅黑
__DIR__ . '/fonts/msyh.ttf', // 專案內置
];
foreach ($fontPaths as $path) {
if (file_exists($path)) { $fontFile = $path; break; }
}
}
if (!$fontFile || !file_exists($fontFile)) {
trigger_error("找不到可用中文字型檔,請指定 \$fontFile 參數", E_USER_WARNING);
return false;
}
// 載入圖片 (自動偵測類型)
$srcInfo = @getimagesize($srcImage);
if (!$srcInfo) return false;
$mime = $srcInfo['mime'];
$createFunc = [
'image/jpeg' => 'imagecreatefromjpeg',
'image/png' => 'imagecreatefrompng',
'image/gif' => 'imagecreatefromgif',
'image/webp' => 'imagecreatefromwebp',
];
if (!isset($createFunc[$mime])) return false;
$im = $createFunc[$mime]($srcImage);
if (!$im) return false;
// 開啟 Alpha 混合
imagealphablending($im, true);
imagesavealpha($im, true);
// 分配顏色 (含透明度)
$textColor = imagecolorallocatealpha($im, $color[0], $color[1], $color[2], $alpha);
// 繪製文字
// imagettftext($image, $size, $angle, $x, $y, $color, $fontfile, $text)
// 注意:$y 為基線位置,非文字頂部
$bbox = imagettfbbox($fontSize, $angle, $fontFile, $text);
$textWidth = $bbox[2] - $bbox[0];
$textHeight = $bbox[1] - $bbox[7];
// 如果座標為負數,表示從右下角偏移
if ($x < 0) $x = imagesx($im) + $x - $textWidth - 10;
if ($y < 0) $y = imagesy($im) + $y + $textHeight + 10;
imagettftext($im, $fontSize, $angle, $x, $y, $textColor, $fontFile, $text);
// 輸出
$outputFunc = [
'image/jpeg' => function($im, $path) { return imagejpeg($im, $path, 90); },
'image/png' => function($im, $path) { return imagepng($im, $path, 8); },
'image/gif' => function($im, $path) { return imagegif($im, $path); },
'image/webp' => function($im, $path) { return imagewebp($im, $path, 85); },
];
$success = false;
if ($dstImage === '') {
// 直接輸出瀏覽器
header('Content-Type: ' . $mime);
$success = $outputFunc[$mime]($im, null);
} else {
$success = $outputFunc[$mime]($im, $dstImage);
}
imagedestroy($im);
return $success;
}
?>
<?php
// 參數
$src = 'photo.jpg';
$dst = 'photo_watermarked.jpg';
$text = '© 2024 MySite.com - 版權所有';
// 執行 (右下角,白色半透明)
addTextWatermark($src, $dst, $text, 24, 0, -10, -10, '', [255,255,255], 40);
echo "水印添加完成: $dst";
?>
<?php
function batchWatermark(string $dir, string $text, array $options = []) {
$opts = array_merge([
'extensions' => ['jpg','jpeg','png','webp'],
'outputDir' => '', // 空則覆蓋原檔,建議指定輸出目錄
'fontSize' => 20,
'color' => [255,255,255],
'alpha' => 40,
'position' => 'bottom-right', // top-left, top-right, bottom-left, bottom-right, center, tile
], $options);
$outDir = $opts['outputDir'] ?: $dir;
if ($outDir !== $dir && !is_dir($outDir)) mkdir($outDir, 0755, true);
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($files as $file) {
if (!$file->isFile()) continue;
$ext = strtolower($file->getExtension());
if (!in_array($ext, $opts['extensions'])) continue;
$src = $file->getPathname();
$dst = $outDir . DIRECTORY_SEPARATOR . $file->getBasename();
// 計算座標
list($x, $y) = calculatePosition($src, $opts['position'], $opts['fontSize'], strlen($text)*0.6);
addTextWatermark($src, $dst, $text, $opts['fontSize'], 0, $x, $y, '', $opts['color'], $opts['alpha']);
echo "Processed: $dst\n";
}
}
function calculatePosition(string $image, string $pos, int $fontSize, int $textWidth): array {
$size = getimagesize($image);
$w = $size[0]; $h = $size[1];
$margin = 15;
switch ($pos) {
case 'top-left': return [$margin, $fontSize + $margin];
case 'top-right': return [$w - $textWidth - $margin, $fontSize + $margin];
case 'bottom-left': return [$margin, $h - $margin];
case 'bottom-right': return [$w - $textWidth - $margin, $h - $margin];
case 'center': return [($w - $textWidth)/2, $h/2 + $fontSize/2];
case 'tile': return ['tile' => true]; // 特殊標記
default: return [$margin, $fontSize + $margin];
}
}
?>
<?php
function addTiledWatermark(string $src, string $dst, string $text, array $options = []) {
$opts = array_merge([
'fontSize' => 16,
'color' => [200,200,200],
'alpha' => 60,
'angle' => -30,
'spacingX' => 200,
'spacingY' => 100,
], $options);
$im = imagecreatefromjpeg($src); // 簡化:假設 JPEG
$w = imagesx($im); $h = imagesy($im);
$fontFile = $options['fontFile'] ?? '';
// ... 字型搜尋邏輯同上 ...
$color = imagecolorallocatealpha($im, $opts['color'][0], $opts['color'][1], $opts['color'][2], $opts['alpha']);
for ($y = -$h; $y < $h * 2; $y += $opts['spacingY']) {
for ($x = -$w; $x < $w * 2; $x += $opts['spacingX']) {
imagettftext($im, $opts['fontSize'], $opts['angle'], $x, $y, $color, $fontFile, $text);
}
}
imagejpeg($im, $dst, 90);
imagedestroy($im);
}
?>
| 問題 | 解決方案 |
|---|---|
| 中文顯示方框/亂碼 | 字型檔不支援中文。必須使用包含中文字形的 TTF/OTF (如 msyh.ttc, simhei.ttf, SourceHanSansCN-Regular.otf)。 |
| 文字位置不正確 | imagettftext 的 $y 是基線。使用 imagettfbbox() 計算實際高度調整。 |
| 透明度無效 | 確保呼叫 imagealphablending($im, true) 和 imagesavealpha($im, true),且使用 imagecolorallocatealpha。 |
| 字型檔找不到 | Linux 伺服器常無中文字型。下載 Noto Sans CJK 或 Source Han Sans 放入專案 /fonts/ 目錄並指定路徑。 |
| 記憶體耗盡 | 大圖 (如 4000x3000) 處理需大量記憶體。調整 memory_limit 或分塊處理。 |
<?php
// Imagick 版水印 (功能更強、效能更好、支援更多格式)
$im = new Imagick('photo.jpg');
$draw = new ImagickDraw();
$draw->setFont('msyh.ttc');
$draw->setFontSize(24);
$draw->setFillColor('rgba(255,255,255,0.3)');
$draw->setTextAlignment(Imagick::ALIGN_RIGHT);
$draw->annotation($im->getImageWidth() - 10, $im->getImageHeight() - 10, '© MySite.com');
$im->drawImage($draw);
$im->writeImage('photo_wm.jpg');
$im->destroy();
?>