当前位置: 代码网 > it编程>前端脚本>Python > 基于Python实现一个图片水印批量添加工具

基于Python实现一个图片水印批量添加工具

2025年10月27日 Python 我要评论
平时处理图片时,你是不是总遇到这样的麻烦:想给一批图片加水印,单张处理太费时间,用专业软件又觉得小题大做?今天就带大家亲手做一个实用的python小工具,既能选单个图片加水印,也能批量处理整个文件夹的

平时处理图片时,你是不是总遇到这样的麻烦:想给一批图片加水印,单张处理太费时间,用专业软件又觉得小题大做?今天就带大家亲手做一个实用的python小工具,既能选单个图片加水印,也能批量处理整个文件夹的图片,小白跟着步骤走也能搞定!

一、工具核心功能与准备工作

先明确下这个工具能帮我们解决什么问题,以及动手前需要准备哪些东西。

1. 核心功能

咱们做的这个工具,主打“灵活”和“高效”,具体能实现这3个功能:

  • 单图处理:选一张图片,手动调整水印位置、透明度,预览效果后再保存
  • 批量处理:选一个文件夹,一键给里面所有图片(支持jpg、png、jpeg格式)加统一水印
  • 自定义设置:自己改水印文字、字体大小、颜色,还能调水印的透明度,避免遮挡图片内容

2. 环境准备

做这个工具不用复杂的环境,只要装2个python库就行,新手直接按步骤来:

  1. 先确认电脑装了python(建议3.7及以上版本,没装的话去官网下载,记得勾“add python to path”)
  2. 打开命令提示符(windows按win+r,输cmd;mac打开终端),输入下面两行命令,安装需要的库:
    • 安装处理图片的库:pip install pillow
    • 安装做图形界面的库(让工具更直观):pip install tkinter
      (注:tkinter在python3里通常是自带的,要是安装报错,直接跳过这步试试,大概率能正常用)

二、代码拆解与实现(附完整代码)

我把整个工具的代码分成了3个部分,每部分都标了注释,大家可以跟着理解,也能直接复制用。

1. 导入需要的库

先把后面要用到的工具包导进来,就像做饭前把调料准备好一样:

from pil import image, imagedraw, imagefont  # 处理图片和添加文字水印
import tkinter as tk  # 做图形界面
from tkinter import filedialog, messagebox, ttk  # 界面里的文件选择、提示框等组件
import os  # 处理文件夹和文件路径

2. 核心功能函数(加水印的关键)

这部分是工具的“心脏”,负责实现图片读取、水印添加、保存等核心操作,我拆成了3个函数,每个函数干一件事,逻辑更清楚:

(1)单个图片加水印函数

def add_watermark_to_single_image():
    # 1. 让用户选择要处理的单个图片
    img_path = filedialog.askopenfilename(
        title="选一张要加水印的图片",
        filetypes=[("图片文件", "*.jpg;*.png;*.jpeg"), ("所有文件", "*.*")]
    )
    if not img_path:  # 要是用户没选图片,直接退出函数
        return

    # 2. 获取用户设置的水印参数(文字、大小、颜色、透明度)
    watermark_text = entry_text.get().strip()
    if not watermark_text:  # 没填水印文字的话,弹出提示
        messagebox.showwarning("提示", "请先输入水印文字哦!")
        return

    try:
        font_size = int(entry_font_size.get().strip())
        opacity = int(entry_opacity.get().strip())
        # 简单判断参数是否合理,避免出错
        if font_size <= 0 or opacity < 0 or opacity > 100:
            raise valueerror
    except valueerror:
        messagebox.showerror("错误", "字体大小要填正整数,透明度要在0-100之间哦!")
        return

    # 3. 读取图片,处理png透明格式(避免透明图片加水印后背景变黑色)
    img = image.open(img_path).convert("rgba")
    # 创建一个和图片一样大的透明图层,用来放水印
    watermark_layer = image.new("rgba", img.size, (255, 255, 255, 0))
    draw = imagedraw.draw(watermark_layer)

    # 4. 加载字体(这里用系统默认字体,windows和mac路径不一样,做了兼容)
    try:
        if os.name == "nt":  # windows系统
            font = imagefont.truetype("arial.ttf", font_size)
        else:  # mac或linux系统
            font = imagefont.truetype("/library/fonts/arial.ttf", font_size)
    except ioerror:
        # 要是没找到arial字体,就用默认字体,虽然丑点但不影响用
        font = imagefont.load_default(size=font_size)

    # 5. 计算水印位置(默认放在右下角,距离边缘20像素,也可以自己改这里的数值)
    text_width, text_height = draw.textbbox((0, 0), watermark_text, font=font)[2:]
    img_width, img_height = img.size
    x = img_width - text_width - 20  # 右边距20
    y = img_height - text_height - 20  # 下边距20

    # 6. 添加水印(处理透明度)
    # 把用户输入的0-100透明度,转成pil需要的0-255范围
    alpha = int(255 * (opacity / 100))
    # 这里默认水印是黑色,想改颜色的话,把(0,0,0,alpha)里的前三个数换成rgb值
    draw.text((x, y), watermark_text, font=font, fill=(0, 0, 0, alpha))

    # 7. 合并图片和水印图层,保存结果
    result = image.alpha_composite(img, watermark_layer)
    # 处理png转jpg的情况(jpg不支持透明,要加白色背景)
    if img_path.lower().endswith(('.jpg', '.jpeg')):
        result = result.convert("rgb")
    
    # 让用户选保存路径
    save_path = filedialog.asksaveasfilename(
        defaultextension=os.path.splitext(img_path)[1],
        filetypes=[("图片文件", "*.jpg;*.png;*.jpeg"), ("所有文件", "*.*")],
        title="保存加水印后的图片"
    )
    if save_path:
        result.save(save_path)
        messagebox.showinfo("成功", f"图片已保存到:\n{save_path}")

(2)批量处理文件夹图片函数

批量处理和单图逻辑差不多,主要多了“遍历文件夹”的步骤:

def batch_add_watermark():
    # 1. 让用户选要批量处理的文件夹
    folder_path = filedialog.askdirectory(title="选要批量加水印的文件夹")
    if not folder_path:
        return

    # 2. 获取用户设置的水印参数(和单图函数一样,避免重复写代码)
    watermark_text = entry_text.get().strip()
    if not watermark_text:
        messagebox.showwarning("提示", "请先输入水印文字哦!")
        return

    try:
        font_size = int(entry_font_size.get().strip())
        opacity = int(entry_opacity.get().strip())
        if font_size <= 0 or opacity < 0 or opacity > 100:
            raise valueerror
    except valueerror:
        messagebox.showerror("错误", "字体大小要填正整数,透明度要在0-100之间哦!")
        return

    # 3. 遍历文件夹里的所有文件,只处理图片
    # 先定义支持的图片格式,避免处理非图片文件
    supported_formats = ('.jpg', '.jpeg', '.png')
    # 统计成功处理的图片数量
    success_count = 0

    # 加载字体(和单图函数一样,做了系统兼容)
    try:
        if os.name == "nt":
            font = imagefont.truetype("arial.ttf", font_size)
        else:
            font = imagefont.truetype("/library/fonts/arial.ttf", font_size)
    except ioerror:
        font = imagefont.load_default(size=font_size)

    # 4. 逐个处理图片
    for filename in os.listdir(folder_path):
        # 只处理支持格式的文件
        if filename.lower().endswith(supported_formats):
            img_path = os.path.join(folder_path, filename)
            try:
                # 读取图片,和单图处理逻辑一致
                img = image.open(img_path).convert("rgba")
                watermark_layer = image.new("rgba", img.size, (255, 255, 255, 0))
                draw = imagedraw.draw(watermark_layer)

                # 计算水印位置(同样默认右下角)
                text_width, text_height = draw.textbbox((0, 0), watermark_text, font=font)[2:]
                img_width, img_height = img.size
                x = img_width - text_width - 20
                y = img_height - text_height - 20

                # 添加水印
                alpha = int(255 * (opacity / 100))
                draw.text((x, y), watermark_text, font=font, fill=(0, 0, 0, alpha))

                # 合并图层,保存图片
                result = image.alpha_composite(img, watermark_layer)
                if filename.lower().endswith(('.jpg', '.jpeg')):
                    result = result.convert("rgb")
                
                # 保存路径:在原文件夹下加“_watermarked”后缀
                name, ext = os.path.splitext(filename)
                save_path = os.path.join(folder_path, f"{name}_watermarked{ext}")
                result.save(save_path)
                success_count += 1
            except exception as e:
                # 遇到错误不崩溃,只是提示哪个文件处理失败
                messagebox.showerror("处理失败", f"文件 {filename} 处理出错:\n{str(e)}")

    # 批量处理结束后,提示结果
    messagebox.showinfo("批量处理完成", f"总共处理了 {success_count} 张图片\n结果保存在原文件夹,文件名带“_watermarked”后缀")

(3)创建图形界面函数

有了核心功能,再做个简单的界面,不用记命令,点鼠标就能操作:

def create_gui():
    # 1. 初始化窗口
    root = tk.tk()
    root.title("python图片水印工具")
    root.geometry("500x300")  # 窗口大小,也可以自己改
    root.resizable(true, true)  # 允许拉伸窗口

    # 2. 创建标签和输入框(按网格布局,整齐好看)
    # 水印文字设置
    ttk.label(root, text="水印文字:").grid(row=0, column=0, padx=10, pady=15, sticky="w")
    global entry_text
    entry_text = ttk.entry(root, width=40)
    entry_text.grid(row=0, column=1, padx=10, pady=15)
    entry_text.insert(0, "我的图片")  # 默认文字,可修改

    # 字体大小设置
    ttk.label(root, text="字体大小:").grid(row=1, column=0, padx=10, pady=5, sticky="w")
    global entry_font_size
    entry_font_size = ttk.entry(root, width=40)
    entry_font_size.grid(row=1, column=1, padx=10, pady=5)
    entry_font_size.insert(0, "20")  # 默认20号字,可修改

    # 透明度设置
    ttk.label(root, text="水印透明度(0-100):").grid(row=2, column=0, padx=10, pady=5, sticky="w")
    global entry_opacity
    entry_opacity = ttk.entry(root, width=40)
    entry_opacity.grid(row=2, column=1, padx=10, pady=5)
    entry_opacity.insert(0, "50")  # 默认半透明,可修改

    # 3. 创建功能按钮
    # 单图处理按钮
    btn_single = ttk.button(root, text="处理单个图片", command=add_watermark_to_single_image)
    btn_single.grid(row=3, column=0, padx=20, pady=20, sticky="ew")

    # 批量处理按钮
    btn_batch = ttk.button(root, text="批量处理文件夹", command=batch_add_watermark)
    btn_batch.grid(row=3, column=1, padx=20, pady=20, sticky="ew")

    # 4. 运行窗口
    root.mainloop()

3. 主程序入口

最后加一句代码,让脚本运行时自动打开界面:

if __name__ == "__main__":
    create_gui()

三、升级版图片水印工具:支持多参数自定义配置

在前一版工具的基础上,我们新增了文字颜色选择、水印位置自由切换、重复水印次数设置等功能,让水印效果更灵活可控。以下是完整的升级版代码,包含所有参数配置功能。

3.1 功能升级说明

  • 新增文字颜色选择:支持通过颜色选择器自定义水印文字颜色
  • 水印位置可选:提供9个常用位置(如左上角、中心、右下角等)
  • 重复水印次数:可设置水印在图片中重复出现的行数和列数(如3x3网格分布)
  • 保留原功能:兼容单图/批量处理、字体大小/透明度调整

3.2 完整代码实现(含参数配置)

from pil import image, imagedraw, imagefont
import tkinter as tk
from tkinter import filedialog, messagebox, ttk, colorchooser
import os
import math


class watermarktool:
    def __init__(self, root):
        # 初始化主窗口
        self.root = root
        self.root.title("图片水印批量工具")
        self.root.geometry("700x550")
        self.root.resizable(true, true)
        self.root.configure(bg="#f0f0f0")  # 浅灰背景

        # 初始化参数
        self.current_color = (0, 0, 0)  # 默认黑色
        self.fullscreen_mode = false  # 全屏水印模式开关

        # 设置全局样式
        self.setup_styles()

        # 创建界面
        self.create_widgets()

    def setup_styles(self):
        """配置ttk样式,统一美化界面"""
        style = ttk.style()
        style.theme_use("clam")  # 使用clam主题增强跨平台一致性

        # 标题样式
        style.configure("title.tlabel", 
                       font=("微软雅黑", 12, "bold"),
                       background="#f0f0f0",
                       foreground="#333333")

        # 标签样式
        style.configure("label.tlabel",
                       font=("微软雅黑", 10),
                       background="#f0f0f0",
                       foreground="#555555",
                       padding=5)

        # 输入框样式
        style.configure("tentry",
                       font=("微软雅黑", 10),
                       padding=5,
                       fieldbackground="#ffffff",
                       borderwidth=1,
                       focusthickness=2,
                       focuscolor="#4a90d9")

        # 按钮样式
        style.configure("tbutton",
                       font=("微软雅黑", 10, "bold"),
                       padding=8,
                       background="#4a90d9",
                       foreground="#ffffff")
        style.map("tbutton",
                 background=[("active", "#357abd"), ("pressed", "#2a6099")])

        # 颜色预览样式(不依赖height,用padding控制大小)
        self.color_style = ttk.style()
        self.color_style.configure("color.tlabel", 
                                  background="#000000",  # 默认黑色
                                  borderwidth=1,
                                  relief="solid",
                                  padding=(10, 5))  # 用内边距控制宽高(水平10,垂直5)

        # 分组框标题样式(解决-font错误)
        style.configure("tlabelframe.label",
                       font=("微软雅黑", 10, "bold"),
                       foreground="#333333")

    def create_widgets(self):
        """创建界面组件,采用卡片式布局"""
        # 主容器(带边距)
        main_frame = ttk.frame(self.root, padding=20)
        main_frame.pack(fill=tk.both, expand=true)

        # 参数配置卡片(白色背景,阴影效果)
        config_card = ttk.labelframe(main_frame, text="水印参数配置", padding=15)
        config_card.pack(fill=tk.x, pady=(0, 20))

        # 1. 水印文字
        ttk.label(config_card, text="水印文字:", style="label.tlabel").grid(
            row=0, column=0, padx=10, pady=10, sticky="w")
        self.entry_text = ttk.entry(config_card, width=50)
        self.entry_text.grid(row=0, column=1, columnspan=3, padx=10, pady=10, sticky="ew")
        self.entry_text.insert(0, "我的图片")

        # 2. 字体大小 + 透明度(横向排列)
        ttk.label(config_card, text="字体大小:", style="label.tlabel").grid(
            row=1, column=0, padx=10, pady=10, sticky="w")
        self.entry_font_size = ttk.entry(config_card, width=10)
        self.entry_font_size.grid(row=1, column=1, padx=(10, 30), pady=10, sticky="w")
        self.entry_font_size.insert(0, "20")

        ttk.label(config_card, text="透明度(0-100):", style="label.tlabel").grid(
            row=1, column=2, padx=10, pady=10, sticky="w")
        self.entry_opacity = ttk.entry(config_card, width=10)
        self.entry_opacity.grid(row=1, column=3, padx=10, pady=10, sticky="w")
        self.entry_opacity.insert(0, "50")

        # 3. 文字颜色选择(修复-height错误:用padding控制预览框大小)
        ttk.label(config_card, text="文字颜色:", style="label.tlabel").grid(
            row=2, column=0, padx=10, pady=10, sticky="w")
        color_frame = ttk.frame(config_card)
        color_frame.grid(row=2, column=1, columnspan=3, padx=10, pady=10, sticky="w")
        
        # 移除-height参数,依赖样式中的padding控制大小
        self.color_label = ttk.label(color_frame, style="color.tlabel")
        self.color_label.pack(side=tk.left, padx=5)
        
        ttk.button(color_frame, text="选择颜色", command=self.choose_color).pack(side=tk.left)

        # 4. 水印位置选择
        ttk.label(config_card, text="水印位置:", style="label.tlabel").grid(
            row=3, column=0, padx=10, pady=10, sticky="w")
        self.position_var = tk.stringvar(value="右下")
        positions = ["左上", "中上", "右上", "左中", "中心", "右中", "左下", "中下", "右下"]
        self.position_combo = ttk.combobox(
            config_card, textvariable=self.position_var, values=positions, width=10, state="readonly"
        )
        self.position_combo.grid(row=3, column=1, padx=10, pady=10, sticky="w")

        # 5. 重复分布(行数+列数)
        ttk.label(config_card, text="重复分布:", style="label.tlabel").grid(
            row=4, column=0, padx=10, pady=10, sticky="w")
        
        ttk.label(config_card, text="行数:", style="label.tlabel").grid(
            row=4, column=1, padx=(10, 5), pady=10, sticky="e")
        self.entry_rows = ttk.entry(config_card, width=5)
        self.entry_rows.grid(row=4, column=2, padx=(0, 20), pady=10, sticky="w")
        self.entry_rows.insert(0, "1")

        ttk.label(config_card, text="列数:", style="label.tlabel").grid(
            row=4, column=2, padx=(20, 5), pady=10, sticky="e")
        self.entry_cols = ttk.entry(config_card, width=5)
        self.entry_cols.grid(row=4, column=3, padx=10, pady=10, sticky="w")
        self.entry_cols.insert(0, "1")

        # 6. 全屏水印模式(新增功能)
        fullscreen_frame = ttk.frame(config_card)
        fullscreen_frame.grid(row=5, column=0, columnspan=4, padx=10, pady=10, sticky="w")
        
        self.fullscreen_check = ttk.checkbutton(
            fullscreen_frame, text="全屏水印模式(忽略位置和行列设置)",
            command=self.toggle_fullscreen_mode
        )
        self.fullscreen_check.pack(side=tk.left)

        # 7. 全屏水印参数(仅在全屏模式下启用)
        self.fullscreen_params_frame = ttk.frame(config_card)
        self.fullscreen_params_frame.grid(row=6, column=0, columnspan=4, padx=10, pady=5, sticky="w")
        self.disable_frame(self.fullscreen_params_frame)  # 默认禁用(自定义禁用方法)

        ttk.label(self.fullscreen_params_frame, text="水印旋转角度:", style="label.tlabel").pack(side=tk.left, padx=5)
        self.entry_rotation = ttk.entry(self.fullscreen_params_frame, width=5)
        self.entry_rotation.pack(side=tk.left, padx=5)
        self.entry_rotation.insert(0, "30")  # 默认旋转30度

        ttk.label(self.fullscreen_params_frame, text="水平间距(像素):", style="label.tlabel").pack(side=tk.left, padx=5)
        self.entry_h_spacing = ttk.entry(self.fullscreen_params_frame, width=5)
        self.entry_h_spacing.pack(side=tk.left, padx=5)
        self.entry_h_spacing.insert(0, "100")

        ttk.label(self.fullscreen_params_frame, text="垂直间距(像素):", style="label.tlabel").pack(side=tk.left, padx=5)
        self.entry_v_spacing = ttk.entry(self.fullscreen_params_frame, width=5)
        self.entry_v_spacing.pack(side=tk.left, padx=5)
        self.entry_v_spacing.insert(0, "80")

        # 按钮区域(底部居中)
        btn_frame = ttk.frame(main_frame)
        btn_frame.pack(fill=tk.x, pady=10)

        ttk.button(
            btn_frame, text="处理单个图片", command=self.process_single_image, width=20
        ).pack(side=tk.left, padx=(0, 30), pady=10, anchor=tk.center)

        ttk.button(
            btn_frame, text="批量处理文件夹", command=self.batch_process_images, width=20
        ).pack(side=tk.left, padx=30, pady=10, anchor=tk.center)

        # 状态标签(底部显示提示信息)
        self.status_label = ttk.label(
            main_frame, text="请配置参数后选择图片处理", style="label.tlabel", anchor=tk.center
        )
        self.status_label.pack(fill=tk.x, pady=10)

        # 让列自适应宽度
        config_card.columnconfigure(1, weight=1)
        config_card.columnconfigure(3, weight=1)

    def disable_frame(self, frame):
        """自定义禁用框架内所有组件的方法(替代state=disabled)"""
        for widget in frame.winfo_children():
            widget.config(state="disabled")

    def enable_frame(self, frame):
        """自定义启用框架内所有组件的方法"""
        for widget in frame.winfo_children():
            widget.config(state="normal")

    def toggle_fullscreen_mode(self):
        """切换全屏水印模式,启用/禁用相关参数"""
        self.fullscreen_mode = not self.fullscreen_mode
        if self.fullscreen_mode:
            self.enable_frame(self.fullscreen_params_frame)
            self.update_status("已启用全屏水印模式")
        else:
            self.disable_frame(self.fullscreen_params_frame)
            self.update_status("已禁用全屏水印模式")

    def choose_color(self):
        """选择水印颜色并更新预览"""
        color = colorchooser.askcolor(title="选择水印颜色")[0]
        if color:
            self.current_color = (int(color[0]), int(color[1]), int(color[2]))
            hex_color = f"#{int(color[0]):02x}{int(color[1]):02x}{int(color[2]):02x}"
            self.color_style.configure("color.tlabel", background=hex_color)
            self.update_status(f"已选择颜色:{hex_color}")

    def get_watermark_params(self):
        """获取并校验水印参数"""
        watermark_text = self.entry_text.get().strip()
        if not watermark_text:
            messagebox.showwarning("提示", "请输入水印文字")
            return none

        try:
            font_size = int(self.entry_font_size.get().strip())
            opacity = int(self.entry_opacity.get().strip())
            rows = int(self.entry_rows.get().strip())
            cols = int(self.entry_cols.get().strip())
            
            if not (font_size > 0 and 0 <= opacity <= 100 and rows > 0 and cols > 0):
                raise valueerror
        except valueerror:
            messagebox.showerror("参数错误", "请检查参数:\n- 字体大小:正整数\n- 透明度:0-100\n- 行数/列数:正整数")
            return none

        # 全屏模式参数校验
        fullscreen_params = none
        if self.fullscreen_mode:
            try:
                rotation = int(self.entry_rotation.get().strip())
                h_spacing = int(self.entry_h_spacing.get().strip())
                v_spacing = int(self.entry_v_spacing.get().strip())
                if not (h_spacing > 0 and v_spacing > 0):
                    raise valueerror
                fullscreen_params = (rotation, h_spacing, v_spacing)
            except valueerror:
                messagebox.showerror("参数错误", "全屏模式参数需为正整数")
                return none

        return (
            watermark_text, font_size, opacity,
            self.current_color, self.position_var.get(),
            rows, cols, fullscreen_params
        )

    def load_font(self, size):
        """加载字体(兼容中文)"""
        try:
            if os.name == "nt":  # windows系统
                return imagefont.truetype("simsun.ttc", size)  # 宋体支持中文
            else:  # mac/linux
                return imagefont.truetype("/system/library/fonts/pingfang.ttc", size)  # 苹方字体
        except ioerror:
            self.update_status("未找到指定字体,使用默认字体")
            return imagefont.load_default(size=size)

    def calculate_position(self, position, img_w, img_h, text_w, text_h):
        """计算水印位置坐标"""
        margin = 20  # 边缘距离
        pos_map = {
            "左上": (margin, margin),
            "中上": ((img_w - text_w) // 2, margin),
            "右上": (img_w - text_w - margin, margin),
            "左中": (margin, (img_h - text_h) // 2),
            "中心": ((img_w - text_w) // 2, (img_h - text_h) // 2),
            "右中": (img_w - text_w - margin, (img_h - text_h) // 2),
            "左下": (margin, img_h - text_h - margin),
            "中下": ((img_w - text_w) // 2, img_h - text_h - margin),
            "右下": (img_w - text_w - margin, img_h - text_h - margin)
        }
        return pos_map.get(position, (margin, margin))  # 默认左上

    def draw_fullscreen_watermark(self, draw, watermark_text, font, img_width, img_height, fill_color):
        """绘制全屏水印(优化旋转逻辑)"""
        # 获取全屏模式参数
        params = self.get_watermark_params()
        if not params:
            return
        fullscreen_params = params[-1]
        rotation, h_spacing, v_spacing = fullscreen_params
        
        # 计算文字基础尺寸
        text_bbox = draw.textbbox((0, 0), watermark_text, font=font)
        text_width = text_bbox[2] - text_bbox[0]
        text_height = text_bbox[3] - text_bbox[1]
        
        # 计算网格覆盖范围(避免边缘空白)
        grid_width = img_width + text_width * 2
        grid_height = img_height + text_height * 2
        
        # 遍历网格绘制旋转水印
        for x in range(-text_width, grid_width, h_spacing):
            for y in range(-text_height, grid_height, v_spacing):
                # 创建临时图层(尺寸足够容纳旋转后的文字)
                temp_size = max(text_width, text_height) * 2
                temp_layer = image.new("rgba", (temp_size, temp_size), (255, 255, 255, 0))
                temp_draw = imagedraw.draw(temp_layer)
                
                # 在临时图层中心绘制文字
                temp_draw.text(
                    (temp_size//2 - text_width//2, temp_size//2 - text_height//2),
                    watermark_text,
                    font=font,
                    fill=fill_color
                )
                
                # 旋转临时图层(expand=true确保完整显示)
                rotated_layer = temp_layer.rotate(rotation, expand=true)
                rotated_w, rotated_h = rotated_layer.size
                
                # 计算最终绘制位置(文字中心对齐网格点)
                draw_x = x - rotated_w // 2
                draw_y = y - rotated_h // 2
                
                # 绘制旋转后的水印(用paste替代bitmap,避免透明度问题)
                draw._image.paste(rotated_layer, (draw_x, draw_y), rotated_layer)

    def process_single_image(self):
        """处理单个图片"""
        img_path = filedialog.askopenfilename(
            title="选择需要加水印的图片",
            filetypes=[("图片文件", "*.jpg;*.png;*.jpeg"), ("所有文件", "*.*")]
        )
        if not img_path:
            self.update_status("已取消选择图片")
            return

        # 获取参数
        params = self.get_watermark_params()
        if not params:
            return
        watermark_text, font_size, opacity, color, position, rows, cols, fullscreen_params = params

        try:
            self.update_status(f"正在处理图片:{os.path.basename(img_path)}")
            
            # 处理图片
            img = image.open(img_path).convert("rgba")
            watermark_layer = image.new("rgba", img.size, (255, 255, 255, 0))
            draw = imagedraw.draw(watermark_layer)
            font = self.load_font(font_size)
            alpha = int(255 * (opacity / 100))
            fill_color = (*color, alpha)

            # 全屏水印模式
            if self.fullscreen_mode and fullscreen_params:
                self.draw_fullscreen_watermark(draw, watermark_text, font, img.width, img.height, fill_color)
            else:
                # 普通模式:计算文字尺寸和分布
                text_bbox = draw.textbbox((0, 0), watermark_text, font=font)
                text_width = text_bbox[2] - text_bbox[0]
                text_height = text_bbox[3] - text_bbox[1]
                x_step = math.floor(img.width / (cols - 1)) if cols > 1 else 0
                y_step = math.floor(img.height / (rows - 1)) if rows > 1 else 0

                # 绘制水印
                for i in range(rows):
                    for j in range(cols):
                        base_x, base_y = self.calculate_position(
                            position, img.width, img.height, text_width, text_height
                        )
                        x = base_x + (j * x_step) if cols > 1 else base_x
                        y = base_y + (i * y_step) if rows > 1 else base_y
                        x = max(0, min(x, img.width - text_width))
                        y = max(0, min(y, img.height - text_height))
                        draw.text((x, y), watermark_text, font=font, fill=fill_color)

            # 合并保存
            result = image.alpha_composite(img, watermark_layer)
            if img_path.lower().endswith(('.jpg', '.jpeg')):
                result = result.convert("rgb")

            save_path = filedialog.asksaveasfilename(
                defaultextension=os.path.splitext(img_path)[1],
                filetypes=[("图片文件", "*.jpg;*.png;*.jpeg"), ("所有文件", "*.*")],
                title="保存加水印后的图片"
            )
            if save_path:
                result.save(save_path)
                messagebox.showinfo("成功", f"图片已保存到:\n{save_path}")
                self.update_status(f"处理完成:{os.path.basename(save_path)}")

        except exception as e:
            messagebox.showerror("处理失败", f"错误信息:\n{str(e)}")
            self.update_status("处理失败,请检查图片是否正常")

    def batch_process_images(self):
        """批量处理文件夹图片"""
        folder_path = filedialog.askdirectory(title="选择批量处理的文件夹")
        if not folder_path:
            self.update_status("已取消选择文件夹")
            return

        params = self.get_watermark_params()
        if not params:
            return
        watermark_text, font_size, opacity, color, position, rows, cols, fullscreen_params = params

        supported_formats = ('.jpg', '.jpeg', '.png')
        success_count = 0
        font = self.load_font(font_size)
        alpha = int(255 * (opacity / 100))
        fill_color = (*color, alpha)

        self.update_status(f"开始批量处理:{os.path.basename(folder_path)}")

        for filename in os.listdir(folder_path):
            if filename.lower().endswith(supported_formats):
                img_path = os.path.join(folder_path, filename)
                try:
                    img = image.open(img_path).convert("rgba")
                    watermark_layer = image.new("rgba", img.size, (255, 255, 255, 0))
                    draw = imagedraw.draw(watermark_layer)

                    # 全屏水印模式
                    if self.fullscreen_mode and fullscreen_params:
                        self.draw_fullscreen_watermark(draw, watermark_text, font, img.width, img.height, fill_color)
                    else:
                        # 普通模式绘制
                        text_bbox = draw.textbbox((0, 0), watermark_text, font=font)
                        text_width = text_bbox[2] - text_bbox[0]
                        text_height = text_bbox[3] - text_bbox[1]
                        x_step = math.floor(img.width / (cols - 1)) if cols > 1 else 0
                        y_step = math.floor(img.height / (rows - 1)) if rows > 1 else 0

                        for i in range(rows):
                            for j in range(cols):
                                base_x, base_y = self.calculate_position(
                                    position, img.width, img.height, text_width, text_height
                                )
                                x = base_x + (j * x_step) if cols > 1 else base_x
                                y = base_y + (i * y_step) if rows > 1 else base_y
                                x = max(0, min(x, img.width - text_width))
                                y = max(0, min(y, img.height - text_height))
                                draw.text((x, y), watermark_text, font=font, fill=fill_color)

                    # 保存处理结果
                    result = image.alpha_composite(img, watermark_layer)
                    if filename.lower().endswith(('.jpg', '.jpeg')):
                        result = result.convert("rgb")

                    name, ext = os.path.splitext(filename)
                    save_path = os.path.join(folder_path, f"{name}_watermarked{ext}")
                    result.save(save_path)
                    success_count += 1
                    self.update_status(f"已处理:{filename}({success_count}张)")

                except exception as e:
                    messagebox.showerror("单个文件失败", f"文件 {filename} 处理出错:\n{str(e)}")

        messagebox.showinfo("批量完成", f"批量处理结束!\n共成功处理 {success_count} 张图片")
        self.update_status(f"批量处理完成,成功 {success_count} 张")

    def update_status(self, text):
        """更新底部状态提示"""
        self.status_label.config(text=text)
        self.root.update_idletasks()  # 立即刷新界面


if __name__ == "__main__":
    root = tk.tk()
    app = watermarktool(root)
    root.mainloop()

3.3 参数配置说明

3.3.1. 基础参数

  • 水印文字:输入需要添加的水印内容(支持中文)
  • 字体大小:设置文字大小(建议10-50之间,根据图片尺寸调整)
  • 透明度:0-100的数值(0为完全透明,100为完全不透明)

3.3.2. 高级参数

  • 文字颜色:点击"选择颜色"打开调色板,可自定义任意颜色(默认黑色)
  • 水印位置:下拉选择9个常用位置(如"中心"会将水印放在图片正中间)
  • 重复分布:通过"行数"和"列数"设置水印重复次数:
    • 1x1:只显示一个水印(默认)
    • 2x2:显示4个水印(2行2列均匀分布)
    • 3x3:显示9个水印(适合大图片全屏水印)

3.4 使用示例和效果演示

  1. 制作版权水印:文字"© 2025 xcleigh工作室",白色半透明(透明度30),右下角1x1分布
  2. 制作全屏水印:文字"内部资料",灰色(rgb 128,128,128),透明度20,3x3中心分布

批量生成效果

生成水印前的图片

生成水印后的图片

四、工具使用步骤(小白友好版)

代码写好后,怎么用呢?跟着这4步来,保证能成功:

1. 保存代码

把上面所有代码复制下来,粘贴到记事本里,然后点“文件-另存为”,注意2个细节:

  • 文件名:结尾必须是.py,比如watermark_tool.py
  • 保存类型:选“所有文件”,编码选“utf-8”(避免中文乱码)

2. 运行工具

找到保存好的watermark_tool.py文件,双击它就能打开工具(前提是已经装了python)。打开后会看到这样的界面:

  • 上面三个输入框,分别填“水印文字”“字体大小”“透明度”
  • 下面两个按钮,选“处理单个图片”或“批量处理文件夹”

3. 单图处理操作

  1. 在输入框里填好参数(比如水印文字填“我的旅行照”,字体20,透明度50)
  2. 点“处理单个图片”,在弹出的窗口里选要加水印的图片(比如桌面上的photo.jpg
  3. 选好后,会弹出“保存”窗口,选个保存位置(比如还是桌面),点“保存”
  4. 提示“成功”后,去保存位置找图片,就能看到右下角多了水印

4. 批量处理操作

  1. 同样先填好参数(批量处理时,所有图片会用同一个参数)
  2. 点“批量处理文件夹”,选要处理的文件夹(比如“d盘-图片集”)
  3. 工具会自动处理文件夹里所有jpg、png图片,不用手动等
  4. 处理完会提示“完成”,去原文件夹看,每张图片都会多一个带“_watermarked”后缀的副本,比如photo_watermarked.jpg

五、常见问题解决(避坑指南)

用的时候可能会遇到小问题,这里整理了3个常见情况,教你怎么解决:

1. 双击脚本没反应/报错“no module named xxx”

原因:没装需要的库,或者python没添加到环境变量。
解决办法:

  • 按前面“准备工作”的步骤,重新打开cmd/终端,输入pip install pillowpip install tkinter
  • 要是还没反应,右键点击脚本,选“打开方式”,手动选python.exe(通常在c:\users\你的用户名\appdata\local\programs\python\pythonxx文件夹里)

2. 处理png图片后,背景变黑色

原因:png图片有透明背景,保存成jpg时不支持透明,默认会填黑色。
解决办法:

  • 处理png图片时,保存的时候选“保存类型”为png,不要选jpg
  • 要是必须存jpg,可以在代码里改“填充颜色”:把fill=(0,0,0,alpha)改成fill=(255,255,255,alpha),水印会变成白色,背景也会变成白色

3. 水印文字显示乱码/方块

原因:系统里没有arial字体,导致字体加载失败。
解决办法:

  • 手动换个系统有的字体,比如windows里的“微软雅黑”,把代码里加载字体的行改成:
    font = imagefont.truetype("msyh.ttc", font_size)
  • 或者直接用默认字体,虽然不好看,但不会乱码,代码里保留font = imagefont.load_default(size=font_size)就行

六、功能扩展建议(进阶玩法)

要是你觉得基础功能不够用,还能给工具加这些小功能,难度也不大:

  1. 加水印位置选择:现在默认右下角,可以加个下拉框,让用户选“左上角”“中间”“左下角”等位置
  2. 加图片水印:不光能加文字,还能加图片水印(比如自己的logo),用image.open("logo.png")读取logo,再贴到原图上
  3. 批量修改保存路径:现在批量处理只能存在原文件夹,可加个“选择输出文件夹”按钮,把结果统一存到新文件夹里
  4. 预览功能:加水印前先显示预览图,用户确认没问题再保存,避免反复修改

七、总结

这个python水印工具虽然简单,但特别实用,不管是处理日常照片,还是工作中给素材加水印,都能省不少时间。关键是代码全在上面,自己能改,想加什么功能就加什么。

新手不用怕,跟着步骤一步步来,先跑通基础版本,再慢慢改细节,既能学会python实用技能,又能做出自己能用的工具,这不比单纯看教程有意思多了?

以上就是基于python实现一个图片水印批量添加工具的详细内容,更多关于python图片水印批量添加的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com