脚本专家 发表于 6 小时前

Python Pillow批量图片水印脚本实现文字Logo与平铺

批量处理图片水印是开发中常见的自动化需求,尤其当图片数量达到数千张时,手动操作几乎不可行。Python 的 Pillow 库提供了完整的图像处理接口,可以基于透明图层叠加的方式,快速实现文字水印、Logo 水印以及全屏平铺水印。本文从实际代码出发,给出三个可复用的水印函数,并提供一个通用的批量处理入口,适合直接嵌入到自动化流程中。


# 基础环境
pip install Pillow


核心思路是:打开原图并转换为 RGBA 模式,创建一张同尺寸的透明图层,在透明图层上绘制文字或粘贴 Logo,再通过 Image.alpha_composite 将图层与原图合成,最后按需转换为 RGB 保存。全程不修改原文件,输出到指定目录。

文字水印实现
文字水印通过 ImageDraw.text 绘制,支持自定义内容、位置、字号、颜色透明度以及旋转角度。字体默认读取 Windows 下的微软雅黑 msyh.ttc,如果不存在则回退到默认字体。注意,若要在 Linux 或 macOS 上运行,需要将字体路径替换为系统中实际存在的中文字体文件,否则中文会显示为方框。


from PIL import Image, ImageDraw, ImageFont
import os
from pathlib import Path

def add_text_watermark(input_image, output_image, text="内部资料",
                     position="center", font_size=40, color=(255, 255, 255, 128),
                     angle=0):
    img = Image.open(input_image).convert('RGBA')
    txt_layer = Image.new('RGBA', img.size, (255, 255, 255, 0))
    draw = ImageDraw.Draw(txt_layer)

    try:
      font = ImageFont.truetype("C:/Windows/Fonts/msyh.ttc", font_size)
    except:
      font = ImageFont.load_default()

    bbox = draw.textbbox((0, 0), text, font=font)
    text_width = bbox - bbox
    text_height = bbox - bbox

    positions = {
      'center': ((img.width - text_width) // 2, (img.height - text_height) // 2),
      'bottom-right': (img.width - text_width - 20, img.height - text_height - 20),
      'bottom-left': (20, img.height - text_height - 20),
      'top-right': (img.width - text_width - 20, 20),
      'top-left': (20, 20),
    }
    x, y = positions.get(position, positions['center'])

    if angle != 0:
      big_layer = Image.new('RGBA', (img.width * 2, img.height * 2), (255, 255, 255, 0))
      big_draw = ImageDraw.Draw(big_layer)
      big_draw.text((img.width // 2, img.height // 2), text, font=font, fill=color)
      big_layer = big_layer.rotate(angle, expand=0)
      txt_layer = big_layer.crop((img.width // 2, img.height // 2,
                                    img.width // 2 + img.width,
                                    img.height // 2 + img.height))
    else:
      draw.text((x, y), text, font=font, fill=color)

    result = Image.alpha_composite(img, txt_layer)
    result = result.convert('RGB')
    result.save(output_image, quality=95)
    print(f"文字水印: {os.path.basename(input_image)}")


旋转处理的原理是先把文字画在一张比原图大四倍的透明画布中心,再旋转整张画布,最后裁剪出与原图等大的区域。这样可以避免旋转后文字超出边界。

Logo 图片水印实现
图片水印适合放公司 Logo,支持透明度调整和等比缩放。建议使用 PNG 透明背景,否则会带白色底块。缩放时根据原图宽度乘以 scale 计算宽度,高度按 Logo 原始宽高比计算,保证不变形。透明度控制通过修改 alpha 通道实现。


def add_image_watermark(input_image, output_image, logo_path,
                        position="bottom-right", opacity=0.5, scale=0.15):
    img = Image.open(input_image).convert('RGBA')
    logo = Image.open(logo_path).convert('RGBA')

    new_logo_size = (int(img.width * scale),
                     int(img.height * scale * logo.height / logo.width))
    logo = logo.resize(new_logo_size, Image.LANCZOS)

    if opacity < 1:
      r, g, b, a = logo.split()
      a = a.point(lambda x: int(x * opacity))
      logo = Image.merge('RGBA', (r, g, b, a))

    positions = {
      'center': ((img.width - logo.width) // 2, (img.height - logo.height) // 2),
      'bottom-right': (img.width - logo.width - 20, img.height - logo.height - 20),
      'bottom-left': (20, img.height - logo.height - 20),
      'top-right': (img.width - logo.width - 20, 20),
      'top-left': (20, 20),
    }
    x, y = positions.get(position, positions['bottom-right'])

    watermark_layer = Image.new('RGBA', img.size, (0, 0, 0, 0))
    watermark_layer.paste(logo, (x, y))

    result = Image.alpha_composite(img, watermark_layer)
    result = result.convert('RGB')
    result.save(output_image, quality=95)
    print(f"Logo水印: {os.path.basename(input_image)}")


平铺水印实现
对于防泄漏需求,可以将文字错落铺满全图。通过两个 while 循环按行列绘制,行号奇偶时偏移半个间距,形成类似砖墙的排列方式。适合对文档截图声明“机密文件”之类的场景。


def add_tiled_watermark(input_image, output_image, text="内部资料",
                         font_size=30, color=(255, 255, 255, 50), angle=-30, spacing=150):
    img = Image.open(input_image).convert('RGBA')
    txt_layer = Image.new('RGBA', img.size, (255, 255, 255, 0))
    draw = ImageDraw.Draw(txt_layer)

    try:
      font = ImageFont.truetype("C:/Windows/Fonts/msyh.ttc", font_size)
    except:
      font = ImageFont.load_default()

    bbox = draw.textbbox((0, 0), text, font=font)
    text_width = bbox - bbox
    text_height = bbox - bbox

    y = 0
    while y < img.height:
      x = 0
      row_offset = (y // spacing) % 2 * (spacing // 2)
      while x < img.width:
            draw.text((x + row_offset, y), text, font=font, fill=color)
            x += text_width + spacing
      y += text_height + spacing

    result = Image.alpha_composite(img, txt_layer)
    result = result.convert('RGB')
    result.save(output_image, quality=95)
    print(f"平铺水印: {os.path.basename(input_image)}")


批量处理入口
三个函数都接收 input_image 和 output_image 路径,因此可以统一封装批量处理函数。只需要遍历输入目录下的图片文件,调用对应水印函数即可。支持 jpg、png、bmp、webp、tiff 等常见格式。


def batch_add_watermark(input_dir, output_dir, watermark_func, **kwargs):
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.webp', '.tiff'}
    count = 0

    for file_path in input_path.iterdir():
      if file_path.is_file() and file_path.suffix.lower() in image_extensions:
            output_file = output_path / file_path.name
            watermark_func(str(file_path), str(output_file), **kwargs)
            count += 1

    print(f"\n批量水印完成: {count} 张图片")


使用示例
批量给员工工牌照片添加“仅限内部使用”文字水印:


if __name__ == "__main__":
    batch_add_watermark(
      input_dir="原始工牌照片",
      output_dir="已加水印",
      watermark_func=add_text_watermark,
      text="仅限内部使用",
      position="center",
      font_size=40,
    )


批量给产品图片添加 Logo:


# batch_add_watermark(
    #   input_dir="产品图片",
    #   output_dir="已加Logo",
    #   watermark_func=add_image_watermark,
    #   logo_path="company_logo.png",
    #   position="bottom-right",
    #   opacity=0.7,
    #   scale=0.1
    # )


对机密文档截图做全屏平铺水印:


# batch_add_watermark(
    #   input_dir="机密文档截图",
    #   output_dir="平铺水印",
    #   watermark_func=add_tiled_watermark,
    #   text="机密文件",
    #   font_size=25,
    #   angle=-30,
    # )


常见问题与排查
中文水印显示为方框,原因是代码找不到中文字体。解决方法是显式指定系统中文字体路径,比如微软雅黑 msyh.ttc 或宋体 simsun.ttc。

水印太显眼时,调节 color 元组的第四个参数 alpha 值,例如 (255, 255, 255, 50) 表示透明度较高。

保存 PNG 后水印丢失,这是因为合成的结果是 RGBA 模式,如果直接保存为 PNG 通常正常;但如果想保留透明通道,应使用 result.save(output_image, 'PNG'),不要转换为 RGB。

整体来看,Pillow 的水印方案足够应对大多数批量场景。通过替换字体路径和调整透明度、位置、缩放比例,可以灵活适配不同业务需求。对于需要更高性能的场景,可以考虑 multiprocessing 并行处理多个文件,但要注意 Pillow 线程安全性,推荐使用进程池。

热心网友4 发表于 1 小时前

Re: Python Pillow批量图片水印脚本实现文字Logo与平铺

感谢楼主分享,正好最近在处理批量加水印的需求,思路挺清晰的。透明图层叠加的方式确实比直接draw在原图上更灵活,而且不污染原图。 不过帖子里的代码好像没贴完?`add_image_watermark` 到 resize 那里截断了,后面平铺的部分也没看到。如果方便的话能不能把完整代码补上,特别是平铺的间距和随机角度那块,比较好奇怎么实现的。 另外提两个小建议:旋转文字那里,如果用 `big_layer.rotate` 裁剪固定中心区域,当角度不是0/90/270时,文字的中心点其实会偏离原图中心,因为旋转后文字包围盒变了。我之前踩过坑,后来是先把文字旋转好再贴到图层指定位置,或者用 `rotate` 的 `center` 参数直接旋转文字本身。还有就是 `alpha_composite` 之后转成 RGB 保存,如果原图是带透明通道的 PNG,最终会丢失透明信息,输出给 Web 场景可能要注意。 字体路径写死了 `msyh.ttc`,跨平台确实麻烦,可以加个环境变量或者自动检测常见字体路径。不过整体封装得很实用,直接能抄作业哈哈。期待后续补完。

热心网友4 发表于 1 小时前

Re: Python Pillow批量图片水印脚本实现文字Logo与平铺

感谢分享!正好最近在整理图片素材,这个批量水印的思路很实用。特别是透明图层叠加和旋转裁剪那块,处理得挺细致。我平时用的都是简单贴右下角,没想到还能平铺和旋转,改天试试看。另外字体路径那个坑确实要注意,换到服务器上跑的时候记得调一下。

热心网友6 发表于 1 小时前

Re: Python Pillow批量图片水印脚本实现文字Logo与平铺

楼主的文字水印实现得很实用,特别是旋转时用扩大画布再裁剪的思路,避免了边缘溢出,这个细节很到位。字体路径的问题确实是大坑,Windows 下直接用 msyh.ttc 没问题,但换到 Linux 服务器上就得改路径或者指定一个中文字体文件,不然中文全变成豆腐块,这点提醒很关键。 另外 `quality=95` 保存时是转成 RGB 再存的,如果原图是 PNG 带透明通道,输出会丢失透明信息,不过一般水印场景输出 JPG 也够用了。期待楼主的 Logo 水印完整代码,特别是透明度控制和等比缩放那段,想看看是怎么处理小尺寸 Logo 的模糊问题。
页: [1]
查看完整版本: Python Pillow批量图片水印脚本实现文字Logo与平铺