一、结论先给
水印分三种,用途完全不同,选错了等于白加。
| 形态 | 长什么样 | 防什么 | 不防什么 |
|---|---|---|---|
| 角标 | 右下角一行小字 | 声明归属、蹭图时留名 | 裁掉就消失 |
| 平铺 | 整图斜向重复文字 | 裁不掉、涂不掉 | 影响观感 |
| 底纹 | 大面积半透明 logo | 品牌感 | 几乎不防盗 |
经验取值(这是大量实践后的共识,具体还要看图):
| 参数 | 角标 | 平铺 | 底纹 |
|---|---|---|---|
| 透明度 | 40-60% | 10-20% | 5-10% |
| 字号 | 图片短边 2-3% | 图片短边 4-6% | 按 logo 定 |
| 位置 | 右下,距边 2-3% | 旋转 30°,间距 2 倍字高 | 居中 |
| 颜色 | 白或黑(带描边) | 灰/黑 | 品牌色 |
一句话结论:要防盗用平铺;要好看用角标;要品牌用底纹。
二、为什么你的水印等于没加
最常见的四个失效原因:
1. 透明度太高 半透明水印在浅色背景上几乎看不见,等于没加。但太高又挡内容。平铺水印 10-20% 是能"看得见但不碍眼"的区间。
2. 颜色没对比度 纯白水印压在白色背景上 = 隐形。纯黑水印压在深色照片上 = 隐形。
解法:描边或自动选色。
# 白字 + 黑描边(任何背景都可见)
magick input.jpg \
-gravity southeast -pointsize 32 \
-fill white -stroke black -strokewidth 2 \
-annotate +30+30 '© 你的名字' \
out.jpg
3. 位置固定在角落 右下角水印,裁掉 15% 就没了。要防裁只能平铺。
4. 水印图本身带了白底 用 JPG 当水印图,会把下方内容整块盖住。水印图必须是透明底 PNG。
# 检查水印图有没有 alpha 通道
identify -format '%[channels]\n' watermark.png
# 输出要包含 a(如 srgba),只有 srgb 说明没透明通道
三、ImageMagick 加角标水印
3.1 文字水印
magick input.jpg \
-gravity southeast \
-pointsize 32 \
-fill 'rgba(255,255,255,0.65)' \
-stroke 'rgba(0,0,0,0.35)' -strokewidth 1 \
-annotate +24+24 '© 997工具箱' \
out.jpg
| 参数 | 含义 |
|---|---|
-gravity southeast |
右下角(northwest 左上、center 居中) |
-pointsize |
字号,建议 = 图片短边 × 2.5% |
-annotate +24+24 |
距右下边缘的偏移 |
-fill rgba(...,0.65) |
填充色与透明度 |
-stroke |
描边,保证任何背景可见 |
3.2 图片水印(logo)
magick input.jpg logo.png -gravity southeast -geometry +24+24 \
-define compose:args=70 -compose dissolve -composite out.jpg
-define compose:args=70 就是 70% 不透明度。
3.3 中文字体问题
Linux 服务器常没中文字体,加水印变成方块:
# 看有哪些中文字体
fc-list :lang=zh
# 装(Debian/Ubuntu)
sudo apt install fonts-wqy-zenhei fonts-wqy-microhei
fc-cache -fv
# 指定字体文件
magick input.jpg -font /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc \
-gravity southeast -pointsize 32 -fill white \
-annotate +24+24 '中文水印' out.jpg
四、平铺水印:唯一裁不掉的形态
4.1 生成平铺底图 + 合成
# 第一步:生成一张与图片同尺寸的透明水印层(斜向重复文字)
magick -size 1200x1200 xc:none \
-font /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc \
-pointsize 48 -fill 'rgba(0,0,0,0.15)' \
-draw "rotate -30 translate 0,0" \
-annotate +0+0 '内部资料 禁止外传' \
tile.png
# 第二步:循环平铺(用 -tile 而不是手动画九次)
magick input.jpg \
\( -size 1200x1200 tile:tile.png \) \
-gravity center -composite out.jpg
不过 -tile 对旋转文字支持不友好。更稳的做法是用 Python(下节),或者用 multistamp 思路循环 draw:
magick -size 1200x1200 xc:none \
-font /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc -pointsize 44 \
-fill 'rgba(0,0,0,0.14)' \
-draw "rotate -30 translate -400,-800 text 0,100 '内部资料'" \
-draw "rotate -30 translate -400,-600 text 0,100 '内部资料'" \
-draw "rotate -30 translate -400,-400 text 0,100 '内部资料'" \
-draw "rotate -30 translate -400,-200 text 0,100 '内部资料'" \
-draw "rotate -30 translate -400,0 text 0,100 '内部资料'" \
-draw "rotate -30 translate -400,200 text 0,100 '内部资料'" \
-draw "rotate -30 translate -400,400 text 0,100 '内部资料'" \
-draw "rotate -30 translate -400,600 text 0,100 '内部资料'" \
-draw "rotate -30 translate -400,800 text 0,100 '内部资料'" \
-draw "rotate -30 translate -400,1000 text 0,100 '内部资料'" \
tile.png
magick input.jpg tile.png -gravity center -compose over -composite out.jpg
4.2 Python 版(推荐,平铺位置可控)
from PIL import Image, ImageDraw, ImageFont
import math
def tile_watermark(src, dst, text, font_path, opacity=0.15,
font_ratio=0.045, angle=30, gap_ratio=2.2):
im = Image.open(src).convert('RGBA')
W, H = im.size
layer = Image.new('RGBA', im.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(layer)
size = int(min(W, H) * font_ratio)
font = ImageFont.truetype(font_path, size)
alpha = int(255 * opacity)
# 先在一张小图上画单个水印,再旋转,再平铺(比逐个 rotate 画快得多)
tw, th = draw.textbbox((0, 0), text, font=font)[2:]
pad = int(th * gap_ratio)
unit = Image.new('RGBA', (tw + pad, th + pad), (0, 0, 0, 0))
ud = ImageDraw.Draw(unit)
ud.text((pad // 2, pad // 2), text, font=font, fill=(0, 0, 0, alpha))
unit = unit.rotate(angle, expand=True, resample=Image.BICUBIC)
uw, uh = unit.size
for y in range(-uh, H + uh, uh):
for x in range(-uw, W + uw, uw):
layer.alpha_composite(unit, (x, y))
out = Image.alpha_composite(im, layer).convert('RGB')
out.save(dst, quality=92)
tile_watermark('photo.jpg', 'out.jpg', '仅供内部使用',
'/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc')
为什么用"单水印图 + 平铺"而不是逐个 rotate 画:旋转文字的坐标计算很反直觉(translate 之后坐标系跟着转),逐个画出来的间距会歪。画一个单元再平铺,间距永远均匀。
五、批量加水印
#!/usr/bin/env bash
set -euo pipefail
FONT=${FONT:-/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc}
TEXT=${TEXT:-© 997工具箱}
mkdir -p watermarked
for f in *.jpg *.png; do
[ -e "$f" ] || continue
magick "$f" \
-gravity southeast -font "$FONT" -pointsize 32 \
-fill 'rgba(255,255,255,0.7)' -stroke 'rgba(0,0,0,0.35)' -strokewidth 1 \
-annotate +24+24 "$TEXT" \
"watermarked/${f}"
done
字号自适应的版本(不同尺寸的图用统一字号会很怪):
for f in *.jpg; do
# 短边的 2.5%
pts=$(magick identify -format '%[fx:int(min(w,h)*0.025)]' "$f")
magick "$f" -gravity southeast -font "$FONT" -pointsize "$pts" \
-fill 'rgba(255,255,255,0.7)' -stroke 'rgba(0,0,0,0.35)' -strokewidth 1 \
-annotate +24+24 "$TEXT" "watermarked/${f}"
done
六、水印挡不住截图:那还要加吗
要,但作用不是"防",是"溯源 + 威慑 + 声明"。
技术上,任何可见水印都能被:
- 截图(绕过一切)
- PS 内容识别填充(平铺水印也能抹,只是费劲)
- AI 去水印(越来越强)
所以水印的真实价值是:
- 溯源:带编号/姓名的水印,泄露了能查到是谁
- 威慑:对方知道图上有自己名字,动手前掂量
- 声明:平台申诉时能证明归属
真正要提高门槛的做法:
| 手段 | 效果 | 代价 |
|---|---|---|
| 平铺 + 个性化信息 | 高 | 影响观感 |
| 关键位置放水印(压在主体上) | 中高 | 明显 |
| 只发低清图(长边 800) | 中 | 清晰度不够 |
| 数字水印 / 隐写 | 高(可溯源) | 需专门工具 |
| 盲水印(频域) | 抗截图 | 实现复杂 |
对绝大多数场景,平铺 10-20% + 个性化文字 就是性价比最高的组合。
七、几个特殊场景
7.1 白底图(截图、文档)
纯白背景上,白色水印完全隐形,黑色水印又太扎眼。
# 用中灰 + 中等透明度
magick input.png -gravity southeast -pointsize 28 \
-fill 'rgba(120,120,120,0.55)' \
-annotate +20+20 '© 名字' out.png
7.2 深色照片
# 白字 + 深色投影
magick input.jpg -gravity southeast -pointsize 32 \
-fill 'rgba(255,255,255,0.85)' \
-stroke 'rgba(0,0,0,0.5)' -strokewidth 2 \
-annotate +24+24 '© 名字' out.jpg
7.3 已经很花的图
水印会被背景吃掉。加一层半透明底条:
magick input.jpg \
\( -size 400x60 xc:'rgba(0,0,0,0.35)' \) \
-gravity southeast -geometry +20+20 -compose over -composite \
-gravity southeast -pointsize 28 -fill white \
-annotate +30+32 '© 名字' out.jpg
八、几条纪律
- 防盗用平铺(10-20%),声明用角标(40-60%),两者可叠加。
- 水印图必须透明底 PNG,JPG 会盖住内容。
- 中文用
-font指定字体文件,服务器上默认没有中文字体。 - 字号按短边百分比算,别写死像素值。
- 白底图用中灰,深色图用白字加描边。
- 批量处理保留原图,输出写新目录。
- 别指望水印挡截图,它的价值是溯源和威慑。