开发者

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

开发者 https://www.devze.com 2025-10-28 09:20 出处:网络 作者: xcLeigh
目录一、工具核心功能与准备工作1. 核心功能2. 环境准备二、代码拆解与实现(附完整代码)1. 导入需要的库2. 核心功能函数(加水印的关键)(1)单个图片加水印函数(2)批量处理文件夹图片函数(3)创建图形界面函数
目录
  • 一、工具核心功能与准备工作
    • 1. 核心功能
    • 2. 环境准备
  • 二、代码拆解与实现(附完整代码)
    • 1. 导入需要的库
    • 2. 核心功能函数(加水印的关键)
      • (1)单个图片加水印函数
      • (2)批量处理文件夹图片函数
      • (3)创建图形界面函数
    • 3. 主程序入口
    • 三、升级版图片水印工具:支持多参数自定义配置
      • 3.1 功能升级说明
        • 3.2 完整代码实现(含参数配置)
          • 3.3 参数配置说明
            • 3.3.1. 基础参数
            • 3.3.2. 高级参数
          • 3.4 使用示例和效果演示
          • 四、工具使用步骤(小白友好版)
            • 1. 保存代码
              • 2. 运行工具
                • 3. 单图处理操作
                  • 4. 批量处理操作
                  • 五、常见问题解决(避坑指南)
                    • 1. 双击脚本没反应/报错“no module named xxx”
                      • 2. 处理PNG图片后,背景变黑色
                        • 3. 水印文字显示乱码/方块
                        • 六、功能扩展建议(进阶玩法)
                          • 七、总结

                            平时处理图片时,你是不是总遇到这样的麻烦:想给一批图片加水印,单张处理太费时间,用专业软件又觉得小题大做?今天就带大家亲手做一个实用的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="基于Python实现一个图片水印批量添加工具",
                                    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="基于Python实现一个图片水印批量添加工具"
                                )
                                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()
                            

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

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

                            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="水印文字:",).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="字体大小:",).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):",).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="文字颜色:",).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,)
                                    self.color_label.pack(side=tk.LEFT, padx=5)
                                    
                                    ttk.Button(color_frame, text="选择颜色", command=sewww.devze.comlf.choose_color).pack(side=tk.LEFT)
                            
                                    # 4. 水印位置选择
                                    ttk.Label(config_card, text="水印位置:",).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="重复分布:",).grid(
                                        row=4, column=0, padx=10, pady=10, sticky="w")
                                    
                                    ttk.Label(config_card, text="行数:",).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="列数:",).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="水印旋转角度:",).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="水平间距(像素):",).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="垂直间距(像素):",).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="请配置参数后选择图片处理",, 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_watepythonrmark_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="基于Python实现一个图片水印批量添加工具",
                                        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 > php1 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="基于Python实现一个图片水印批量添加工具"
                                        )
                                        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中心分布

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

                            批量生成效果

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

                            生成水印前的图片

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

                            生成水印后的图片

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

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

                            代码写好后,怎么用呢?跟着这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图片水印批量添加的资料请关注编程客栈(www.devze.com)其它相关文章!

                            0

                            精彩评论

                            暂无评论...
                            验证码 换一张
                            取 消

                            关注公众号