[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()