吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1498|回复: 14
收起左侧

[Python 原创] 图片加水印按编号生成多张图片的工具

[复制链接]
七夕的乌鸦 发表于 2026-2-11 17:45
本帖最后由 七夕的乌鸦 于 2026-2-11 17:56 编辑

因工作需要,同事要做一批带编号的券,要做几千张,她比较懒,不想一张一张的弄,然后我搜了半天也没有找到我想要的那种效果,所以自己写了一个,我估计也就我们这用这玩意。
我感觉,这个UI还可以请大佬美化一下

打开之后是这样的。
1.png

然后导入一张图片。

2.png

先选择输出的路径,然后修改水印文本,最后把水印拖到合适的位置。

3.png

然后点:开始(生成并保存)

4.png

最后就是生成的效果。

5.png

[Python] 纯文本查看 复制代码
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk, ImageDraw, ImageFont
import os

class WatermarkTool:
    def __init__(self, root):
        self.root = root
        self.root.title("图片水印工具")
        self.root.geometry("1200x700")

        main_frame = ttk.Frame(root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.left_frame = ttk.LabelFrame(main_frame, text="功能区", width=300)
        self.left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
        self.left_frame.pack_propagate(False)

        self.right_frame = ttk.LabelFrame(main_frame, text="预览区")
        self.right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

        self.original_image = None
        self.display_image = None
        self.image_path = ""
        self.output_dir = tk.StringVar(value="请选择输出目录")
        self.watermark_text = "示例水印"
        self.start_num = tk.StringVar(value="1")
        self.end_num = tk.StringVar(value="5")
        self.current_num = 1
        self.drag_data = {"x": 0, "y": 0, "item": None}
        
        self.setup_left_panel()
        self.setup_right_panel()
        
    def setup_left_panel(self):
        select_btn = ttk.Button(self.left_frame, text="选择图片", command=self.select_image)
        select_btn.pack(pady=10, fill=tk.X, padx=10)

        range_frame = ttk.LabelFrame(self.left_frame, text="编号范围")
        range_frame.pack(pady=10, fill=tk.X, padx=10)
        
        ttk.Label(range_frame, text="起始编号:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        start_entry = ttk.Entry(range_frame, textvariable=self.start_num)
        start_entry.grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(range_frame, text="结束编号:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
        end_entry = ttk.Entry(range_frame, textvariable=self.end_num)
        end_entry.grid(row=1, column=1, padx=5, pady=5)

        watermark_frame = ttk.LabelFrame(self.left_frame, text="水印设置")
        watermark_frame.pack(pady=10, fill=tk.X, padx=10)
        
        ttk.Label(watermark_frame, text="水印文本:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.watermark_var = tk.StringVar(value=self.watermark_text)
        watermark_entry = ttk.Entry(watermark_frame, textvariable=self.watermark_var, width=12)
        watermark_entry.grid(row=0, column=1, padx=5, pady=5)

        confirm_watermark_btn = ttk.Button(watermark_frame, text="确定", command=self.update_watermark)
        confirm_watermark_btn.grid(row=0, column=2, padx=5, pady=5)

        output_frame = ttk.LabelFrame(self.left_frame, text="输出目录")
        output_frame.pack(pady=10, fill=tk.X, padx=10)
        
        self.output_dir_label = ttk.Label(output_frame, textvariable=self.output_dir)
        self.output_dir_label.pack(pady=5, fill=tk.X, padx=5)
        
        output_dir_btn = ttk.Button(output_frame, text="选择输出目录", command=self.select_output_dir)
        output_dir_btn.pack(pady=5, fill=tk.X, padx=5)

        generate_btn = ttk.Button(self.left_frame, text="开始(生成并保存)", command=self.generate_and_save_images)
        generate_btn.pack(pady=20, fill=tk.X, padx=10)

        log_frame = ttk.LabelFrame(self.left_frame, text="生成日志")
        log_frame.pack(pady=10, fill=tk.BOTH, expand=True, padx=10)

        self.log_text = tk.Text(log_frame, height=10, state=tk.DISABLED)
        scrollbar = ttk.Scrollbar(log_frame, orient="vertical", command=self.log_text.yview)
        self.log_text.configure(yscrollcommand=scrollbar.set)
        
        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.current_num_label = ttk.Label(self.left_frame, text=f"当前编号: {self.current_num}")
        self.current_num_label.pack(pady=5)
        
    def setup_right_panel(self):
        self.canvas = tk.Canvas(self.right_frame, bg='white', highlightthickness=1, highlightbackground='gray')
        self.canvas.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.canvas.bind("<ButtonPress-1>", self.on_drag_start)
        self.canvas.bind("<B1-Motion>", self.on_drag_motion)
        self.canvas.bind("<ButtonRelease-1>", self.on_drag_stop)

        h_scrollbar = ttk.Scrollbar(self.right_frame, orient="horizontal", command=self.canvas.xview)
        h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
        
        v_scrollbar = ttk.Scrollbar(self.right_frame, orient="vertical", command=self.canvas.yview)
        v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        self.canvas.configure(xscrollcommand=h_scrollbar.set, yscrollcommand=v_scrollbar.set)
        
    def select_image(self):
        file_path = filedialog.askopenfilename(
            title="选择图片",
            filetypes=[
                ("Image files", "*.jpg *.jpeg *.png *.bmp *.gif"),
                ("JPEG", "*.jpg *.jpeg"),
                ("PNG", "*.png"),
                ("BMP", "*.bmp"),
                ("All files", "*.*")
            ]
        )
        
        if file_path:
            self.image_path = file_path
            self.load_image(file_path)
            
    def select_output_dir(self):
        dir_path = filedialog.askdirectory(title="选择输出目录")
        if dir_path:
            self.output_dir.set(dir_path)
            
    def update_watermark(self):
        text = self.watermark_var.get()
        if text.strip() == "":
            messagebox.showwarning("警告", "水印文本不能为空")
            return

        if hasattr(self, 'watermark_item'):
            self.canvas.itemconfig(self.watermark_item, text=f"{text}{self.current_num}")
        
    def load_image(self, image_path):
        try:
            self.original_image = Image.open(image_path)
            self.display_image = self.original_image.copy()
            self.show_image_on_canvas()
        except Exception as e:
            messagebox.showerror("错误", f"无法打开图片: {str(e)}")
            
    def show_image_on_canvas(self):
        if self.original_image is None:
            return

        self.canvas.delete("all")

        canvas_width = self.canvas.winfo_width() - 20
        canvas_height = self.canvas.winfo_height() - 20
        
        if canvas_width <= 20 or canvas_height <= 20:
            canvas_width = 800
            canvas_height = 600
            
        img_width, img_height = self.original_image.size
        scale = min(canvas_width / img_width, canvas_height / img_height, 1.0)
        
        new_width = int(img_width * scale)
        new_height = int(img_height * scale)

        resized_img = self.original_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
        self.photo = ImageTk.PhotoImage(resized_img)

        img_x = (canvas_width + 20 - new_width) // 2
        img_y = (canvas_height + 20 - new_height) // 2
        self.canvas.create_image(img_x, img_y, anchor=tk.NW, image=self.photo)

        self.add_watermark_to_canvas(new_width, new_height)
        
    def add_watermark_to_canvas(self, img_width, img_height):
        x_pos = img_width - 100
        y_pos = img_height - 50

        self.watermark_item = self.canvas.create_text(
            x_pos, y_pos,
            text=f"{self.watermark_var.get()}{self.current_num}",
            font=("Arial", 20, "bold"),
            fill="red",
            anchor="nw"
        )

        bbox = self.canvas.bbox(self.watermark_item)
        x1, y1, x2, y2 = bbox
        width = x2 - x1
        height = y2 - y1

        self.drag_rect = self.canvas.create_rectangle(
            x_pos - 5, y_pos - 5, 
            x_pos + width + 5, y_pos + height + 5,
            outline="blue", dash=(4, 2), state=tk.HIDDEN
        )

        self.canvas.tag_bind(self.watermark_item, "<Enter>", self.on_watermark_enter)
        self.canvas.tag_bind(self.watermark_item, "<Leave>", self.on_watermark_leave)
        
    def on_watermark_enter(self, event):
        self.canvas.itemconfig(self.drag_rect, state=tk.NORMAL)
        
    def on_watermark_leave(self, event):
        self.canvas.itemconfig(self.drag_rect, state=tk.HIDDEN)
        
    def on_drag_start(self, event):
        item = self.canvas.find_closest(event.x, event.y)[0]
        if item == self.watermark_item or item == self.drag_rect:
            self.drag_data["item"] = item
            self.drag_data["x"] = event.x
            self.drag_data["y"] = event.y
            
    def on_drag_motion(self, event):
        if self.drag_data["item"]:
            delta_x = event.x - self.drag_data["x"]
            delta_y = event.y - self.drag_data["y"]

            self.canvas.move(self.watermark_item, delta_x, delta_y)
            self.canvas.move(self.drag_rect, delta_x, delta_y)

            self.drag_data["x"] = event.x
            self.drag_data["y"] = event.y
            
    def on_drag_stop(self, event):
        self.drag_data["item"] = None
        
    def append_log(self, message):
        self.log_text.config(state=tk.NORMAL)
        self.log_text.insert(tk.END, f"{message}\n")
        self.log_text.see(tk.END)
        self.log_text.config(state=tk.DISABLED)
        self.root.update_idletasks()
        
    def generate_and_save_images(self):
        self.log_text.config(state=tk.NORMAL)
        self.log_text.delete(1.0, tk.END)
        self.log_text.config(state=tk.DISABLED)

        success, msg = self.generate_images()
        if not success:
            self.append_log(f"错误: {msg}")
            messagebox.showerror("错误", msg)
            return

        self.save_all_images()
    
    def generate_images(self):
        try:
            start = int(self.start_num.get())
            end = int(self.end_num.get())
            
            if start > end:
                return False, "起始编号不能大于结束编号"
            
            if not self.original_image:
                return False, "请先选择一张图片"

            x, y = self.canvas.coords(self.watermark_item)

            canvas_width = self.canvas.winfo_width() - 20
            canvas_height = self.canvas.winfo_height() - 20
            img_width, img_height = self.original_image.size
            scale = min(canvas_width / img_width, canvas_height / img_height, 1.0)

            img_x = (canvas_width + 20 - img_width * scale) // 2
            img_y = (canvas_height + 20 - img_height * scale) // 2
            
            actual_x = (x - img_x) / scale
            actual_y = (y - img_y) / scale

            self.generated_images = []
            for num in range(start, end + 1):
                self.current_num = num
                self.current_num_label.config(text=f"当前编号: {self.current_num}")

                img_with_watermark = self.original_image.copy()

                draw = ImageDraw.Draw(img_with_watermark)

                font = None
                try:
                    font = ImageFont.truetype("simhei.ttf", 60)
                except:
                    try:
                        font = ImageFont.truetype("C:/Windows/Fonts/simhei.ttf", 60)
                    except:
                        try:
                            font = ImageFont.truetype("arial.ttf", 60)
                        except:
                            try:
                                font = ImageFont.truetype("C:/Windows/Fonts/arial.ttf", 60)
                            except:
                                font = ImageFont.load_default()
                
                watermark_text = self.watermark_var.get().strip()
                if not watermark_text:
                    return False, "水印文本不能为空"
                
                full_text = f"{watermark_text}{num}"

                self.append_log(f"正在为图片添加水印: {full_text}")

                draw.text((actual_x, actual_y), full_text, fill="red", font=font)
                
                self.generated_images.append(img_with_watermark)
            
            self.append_log(f"成功生成了 {len(self.generated_images)} 张带水印的图片")
            return True, f"成功生成了 {len(self.generated_images)} 张带水印的图片"
            
        except ValueError:
            return False, "请输入有效的数字编号"
        except Exception as e:
            return False, f"生成图片时出错: {str(e)}"
    
    def save_all_images(self):
        if not hasattr(self, 'generated_images') or len(self.generated_images) == 0:
            self.append_log("警告: 没有可保存的图片,请先生成")
            messagebox.showwarning("警告", "没有可保存的图片,请先生成")
            return

        save_dir = self.output_dir.get()
        if save_dir == "请选择输出目录":
            save_dir = filedialog.askdirectory(title="选择保存目录")
            if not save_dir:
                return
            else:
                self.output_dir.set(save_dir)
        
        try:
            base_filename = os.path.splitext(os.path.basename(self.image_path))[0]
            
            for i, img in enumerate(self.generated_images):
                num = int(self.start_num.get()) + i
                filename = f"{base_filename}_watermark_{num}.png"
                filepath = os.path.join(save_dir, filename)

                if img.mode == "CMYK":
                    img = img.convert("RGB")
                
                img.save(filepath, "PNG")
                self.append_log(f"已保存: {filename}")
                
            self.append_log(f"所有图片已保存到: {save_dir}")
            messagebox.showinfo("完成", f"所有图片已保存到: {save_dir}")
            
        except Exception as e:
            self.append_log(f"错误: 保存图片时出错: {str(e)}")
            messagebox.showerror("错误", f"保存图片时出错: {str(e)}")


def main():
    root = tk.Tk()
    app = WatermarkTool(root)

    root.bind('<Configure>', lambda e: app.show_image_on_canvas() if app.original_image else None)
    
    root.mainloop()

if __name__ == "__main__":
    main()






免费评分

参与人数 5吾爱币 +10 热心值 +5 收起 理由
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
grrr_zhao + 1 + 1 谢谢@Thanks!
sinmu + 1 + 1 我很赞同!
baisskm + 1 + 1 鼓励转贴优秀软件安全工具和文档!
only2025 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

dork 发表于 2026-2-11 21:41
水印位置不能自定义或拖动位置有点可惜
lzspain 发表于 2026-2-11 21:23
666paopao 发表于 2026-2-11 19:27
马到功成 发表于 2026-2-11 19:53
大佬 软件有成品否,代码不会玩啊
naixubao 发表于 2026-2-12 08:22
楼主大大,你好。打包时,出现当前主目录缺少:虚拟环境,requirements.txt,请补全后再打包。是什么问题啊。
chayunyuxiang 发表于 2026-2-12 09:07
成品在哪里?
lbdcyy 发表于 2026-2-12 09:21
这个可以有
JikeCoolTech 发表于 2026-2-12 11:20
需要一个打包的成品~
wqdcs 发表于 2026-2-12 11:29
编号、文本应该能自定义位置,有的图片右上角是图案怎么办,会分辨不清;字体也容易被PS,觉得应该有个简单的防伪处理
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-27 15:25

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表