為保護圖片版權、推廣品牌,網站常需在用戶上傳圖片時自動添加水印。本文提供完整的上傳→驗證→加水印(文字/圖片)→生成縮略圖→保存的流程代碼,基於 GD 庫實現。
<?php
$config = [
'max_size' => 2 * 1024 * 1024, // 2MB
'dest_dir' => 'uploads/', // 儲存目錄
'allow_types' => ['image/jpeg','image/png','image/gif','image/webp'],
'watermark' => true, // 是否加水印
'wm_type' => 1, // 1=文字 2=圖片
'wm_position' => 4, // 1左下 2右下 3左上 4右上 5居中
'wm_text' => '© Mysite.com', // 文字水印內容
'wm_font' => __DIR__ . '/fonts/simhei.ttf', // TTF 字體路徑
'wm_font_size' => 18,
'wm_color' => [255,255,255,100], // RGBA 白色半透明
'wm_image' => 'watermark.png', // 圖片水印路徑
'make_thumb' => true, // 生成縮略圖
'thumb_scale' => 0.5, // 縮略比例
];
?>
<?php
function handleUpload(array $file, array $cfg): array {
$res = ['success' => false, 'msg' => '', 'path' => ''];
if ($file['error'] !== UPLOAD_ERR_OK) {
$res['msg'] = '上傳錯誤碼: ' . $file['error']; return $res;
}
if ($file['size'] > $cfg['max_size']) {
$res['msg'] = '文件過大'; return $res;
}
if (!in_array($file['type'], $cfg['allow_types'], true)) {
$res['msg'] = '類型不允許'; return $res;
}
// 二次驗證 MIME
$finfo = finfo_open(FILEINFO_MIME_TYPE);
if (!in_array(finfo_file($finfo, $file['tmp_name']), $cfg['allow_types'], true)) {
$res['msg'] = '文件內容類型不符'; return $res;
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$name = date('YmdHis') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
$dest = rtrim($cfg['dest_dir'], '/') . '/' . $name;
if (!move_uploaded_file($file['tmp_name'], $dest)) {
$res['msg'] = '移動失敗'; return $res;
}
$res['success'] = true;
$res['path'] = $dest;
return $res;
}
?>
<?php
function addWatermark(string $srcPath, array $cfg): bool {
$info = getimagesize($srcPath);
if (!$info) return false;
$mime = $info['mime'];
// 建立源圖資源
$createMap = [
'image/jpeg' => 'imagecreatefromjpeg',
'image/png' => 'imagecreatefrompng',
'image/gif' => 'imagecreatefromgif',
'image/webp' => 'imagecreatefromwebp',
];
if (!isset($createMap[$mime])) return false;
$src = $createMap[$mime]($srcPath);
// 建立真彩畫布並複製(保持透明度)
$w = imagesx($src); $h = imagesy($src);
$dst = imagecreatetruecolor($w, $h);
imagealphablending($dst, false);
imagesavealpha($dst, true);
$transparent = imagecolorallocatealpha($dst, 0, 0, 0, 127);
imagefill($dst, 0, 0, $transparent);
imagecopy($dst, $src, 0, 0, 0, 0, $w, $h);
if ($cfg['wm_type'] === 1) { // 文字水印
$color = imagecolorallocatealpha($dst, ...$cfg['wm_color']);
$box = imagettfbbox($cfg['wm_font_size'], 0, $cfg['wm_font'], $cfg['wm_text']);
$tw = $box[2] - $box[0]; $th = $box[1] - $box[7];
[$x, $y] = calcPosition($w, $h, $tw, $th, $cfg['wm_position'], 10);
imagettftext($dst, $cfg['wm_font_size'], 0, $x, $y, $color, $cfg['wm_font'], $cfg['wm_text']);
} elseif ($cfg['wm_type'] === 2 && is_file($cfg['wm_image'])) { // 圖片水印
$wm = imagecreatefrompng($cfg['wm_image']); // 假設 PNG 透明
$wmW = imagesx($wm); $wmH = imagesy($wm);
[$x, $y] = calcPosition($w, $h, $wmW, $wmH, $cfg['wm_position'], 10);
imagecopy($dst, $wm, $x, $y, 0, 0, $wmW, $wmH);
imagedestroy($wm);
}
// 覆蓋保存
$saveMap = [
'image/jpeg' => fn() => imagejpeg($dst, $srcPath, 90),
'image/png' => fn() => imagepng($dst, $srcPath, 6),
'image/gif' => fn() => imagegif($dst, $srcPath),
'image/webp' => fn() => imagewebp($dst, $srcPath, 85),
];
$ok = $saveMap[$mime]();
imagedestroy($src);
imagedestroy($dst);
return $ok;
}
function calcPosition(int $W, int $H, int $w, int $h, int $pos, int $margin): array {
return match($pos) {
1 => [$margin, $H - $h - $margin], // 左下
2 => [$W - $w - $margin, $H - $h - $margin], // 右下
3 => [$margin, $margin], // 左上
4 => [$W - $w - $margin, $margin], // 右上
5 => [($W - $w) / 2, ($H - $h) / 2], // 居中
default => [$W - $w - $margin, $H - $h - $margin],
};
}
?>
<?php
function makeThumb(string $srcPath, float $scale = 0.5): ?string {
$info = getimagesize($srcPath);
if (!$info) return null;
$mime = $info['mime'];
$createMap = [
'image/jpeg' => 'imagecreatefromjpeg',
'image/png' => 'imagecreatefrompng',
'image/gif' => 'imagecreatefromgif',
'image/webp' => 'imagecreatefromwebp',
];
if (!isset($createMap[$mime])) return null;
$src = $createMap[$mime]($srcPath);
$w = imagesx($src); $h = imagesy($src);
$nw = (int)($w * $scale); $nh = (int)($h * $scale);
$dst = imagecreatetruecolor($nw, $nh);
imagealphablending($dst, false);
imagesavealpha($dst, true);
imagecopyresampled($dst, $src, 0, 0, 0, 0, $nw, $nh, $w, $h);
$thumbPath = preg_replace('/\.\w+$/', '_thumb$0', $srcPath);
$saveMap = [
'image/jpeg' => fn() => imagejpeg($dst, $thumbPath, 85),
'image/png' => fn() => imagepng($dst, $thumbPath, 6),
'image/gif' => fn() => imagegif($dst, $thumbPath),
'image/webp' => fn() => imagewebp($dst, $thumbPath, 80),
];
$ok = $saveMap[$mime]();
imagedestroy($src); imagedestroy($dst);
return $ok ? $thumbPath : null;
}
?>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['image'])) {
$up = handleUpload($_FILES['image'], $config);
if (!$up['success']) die($up['msg']);
if ($config['watermark']) addWatermark($up['path'], $config);
if ($config['make_thumb']) makeThumb($up['path'], $config['thumb_scale']);
echo "上傳成功: {$up['path']}";
}
?>
ini_set('memory_limit', '256M') 或分塊處理。imagecopy 直接疊加最自然。