2026-10-06 · 喵窝资源铺
截图要发给客户、图片要发公众号、资料要外发协作——想防盗用又不肯一张张开修图软件。这篇给一份 PIL 批量平铺水印脚本:整图倾斜平铺、字号随图片尺寸自动缩放、透明度一处可调、透明 PNG 的 alpha 保留、JPEG/WebP 统一进输出目录可追溯。代码完整实跑过,真实输出贴在下面。
png/jpg/jpeg/webp/bmp,排除输出目录自己(见坑 2,不然二次运行套娃);原名 尺寸 → 输出名,最后报完成数,出了问题能对上哪一张。import os, sys, glob
from PIL import Image, ImageDraw, ImageFont
SRC = sys.argv[1]
OUT = os.path.abspath(sys.argv[2] if len(sys.argv) > 2 else os.path.join(SRC, 'watermarked'))
TEXT = sys.argv[3] if len(sys.argv) > 3 else '喵窝资源铺 miaowoo.900890.xyz'
OPACITY = 0.13 # 水印透明度(0~1),太深毁图太浅看不见
ANGLE = -30 # 倾斜角度
STEP_X, STEP_Y = 340, 260 # 平铺间距(像素)
def find_font():
# 中文水印必须用含 CJK 字形的字体,DejaVu 只有拉丁字形,中文会变方框
for p in ('/usr/share/fonts/opentype/noto/NotoSerifCJK-Bold.ttc',
'/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc',
'/System/Library/Fonts/PingFang.ttc',
'C:/Windows/Fonts/msyh.ttc',
'/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf'):
if os.path.exists(p):
return p
return None
FONT_PATH = find_font()
if FONT_PATH is None:
sys.exit('没找到可用的 TTF 字体,请改 FONT_PATH 指向本机任意 .ttf/.ttc(PIL 默认位图字体不支持中文且大小固定)')
def make_tile(text, size_px):
font = ImageFont.truetype(FONT_PATH, size_px)
tmp = Image.new('RGBA', (STEP_X + size_px * len(text), STEP_Y + size_px * 2), (0, 0, 0, 0))
d = ImageDraw.Draw(tmp)
bbox = d.textbbox((0, 0), text, font=font)
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
# 坑:这里用满强度 255 画字,透明度在拼完整层后统一降一次
# 若在此处就填 alpha=33,paste(mask=tile) 会把 alpha 再乘一次(33*33/255≈4),
# 结果只有 ~1.5% 不透明度,水印肉眼几乎看不见
d.text((0, 0), text, font=font, fill=(255, 255, 255, 255))
big = tmp.rotate(ANGLE, expand=True)
return big, (tw, th)
def watermark(im):
w, h = im.size
base_size = max(18, min(w, h) // 28) # 字号随图片大小缩放
tile, (tw, th) = make_tile(TEXT, base_size)
if im.mode != 'RGBA':
im = im.convert('RGBA')
layer = Image.new('RGBA', (w + tile.width, h + tile.height), (0, 0, 0, 0))
for y in range(0, h + tile.height, STEP_Y):
for x in range(0, w + tile.width, STEP_X):
layer.paste(tile, (x, y), tile)
layer = layer.crop((0, 0, w, h))
# 透明度只在这里施加一次
a = layer.getchannel('A').point(lambda v: int(v * OPACITY))
layer.putalpha(a)
return Image.alpha_composite(im, layer)
os.makedirs(OUT, exist_ok=True)
OUT_REAL = os.path.abspath(OUT)
files = [f for f in sorted(glob.glob(os.path.join(SRC, '*.*')))
if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp', '.bmp'))
and os.path.dirname(os.path.abspath(f)) != OUT_REAL] # 坑:输出目录在源目录里时别把已加水印的图再水印一遍
if not files:
sys.exit('目录里没有可处理的图片')
done = 0
for f in files:
name = os.path.basename(f)
out_path = os.path.join(OUT, os.path.splitext(name)[0] + '.png') # 统一出 PNG 保透明度
try:
im = Image.open(f)
im.load()
except Exception as e:
print(f'跳过 {name}: 打不开({e})')
continue
result = watermark(im)
# PNG 保留 alpha;全不透明转 RGB 存 JPEG 省体积
has_alpha = result.getextrema()[3][0] < 255 if result.mode == 'RGBA' else False
if has_alpha:
result.save(out_path)
else:
result.convert('RGB').save(os.path.join(OUT, os.path.splitext(name)[0] + '.jpg'), quality=92)
out_path = os.path.join(OUT, os.path.splitext(name)[0] + '.jpg')
print(f'{name} {im.size[0]}x{im.size[1]} -> {os.path.basename(out_path)}')
done += 1
print(f'完成 {done}/{len(files)} 张,输出目录 {OUT}')
测试目录 4 个文件:1200×800 仿文档截图 JPG、400×300 小 PNG、300×300 全透明 logo PNG、400×300 WebP,外加一个 说明.txt 干扰文件:
logo透明.png 300x300 -> logo透明.png 小图.png 400x300 -> 小图.jpg 报告截图.jpg 1200x800 -> 报告截图.jpg 表情包.webp 400x300 -> 表情包.jpg 完成 4/4 张,输出目录 .../wm_test/src/wm_out
| 检查项 | 结果 |
|---|---|
| 尺寸不变 | 输出与原图逐张一致(水印只叠加不缩放) |
| 水印确实盖上了 | 输出 vs 原图逐像素比对,7.8% 像素被改变(13% 透明度平铺的合理覆盖量;不是 0% 说明盖上了,不是 100% 说明没毁图) |
| 透明 logo 的 alpha | 输出 alpha 极值 (0, 33)——33 = 255 × 0.13,恰好施加一次,背景全透明保留 |
| 幂等 | 连跑 2 次,3 张输出文件 MD5 完全一致;输出目录放源目录里二次运行,仍只处理 4 张源图,不套娃 |
| 干扰文件 | 说明.txt 不在后缀白名单,自动跳过 |
fill=(255,255,255,33)(0.13×255),然后 layer.paste(tile, pos, mask=tile)——但 paste 的 mask 是按源图 alpha 加权的,33 的 alpha 过一遍 mask 变成 33×33/255≈4,实际不透明度只剩 ~1.5%。更阴的是它不报错、不崩、输出图也「看起来有水印」,直到你把图放到浅色背景上才发现什么都看不见。正确做法:画字用满强度 255,整层拼完后再 putalpha 统一降一次。实跑验证:修正后 alpha 极值恰好是 33。startswith 前缀判断),把整个源目录都排除了,直接「目录里没有可处理的图片」退出——过滤条件太宽和太窄都是坑。fill 改黑色;STEP_X/STEP_Y:想防裁剪就调密(250/200),只想轻度声明版权就调疏;layer.paste(tile, ((w-tw)//2, (h-th)//2), tile) 即可。不想自己跑脚本?喵窝资源铺提供 AI 代办:¥9.9 起,把图片打包发给我们,批量加水印/压缩/转格式一次搞定,当天交付。脚本类服务同样 9.9 起,改到你满意为止。
👉 miaowoo.900890.xyz(虚拟物品,售出后不退款,下单前确认需求)
← 返回全部文章 · 下一篇:文件按关键词自动归类