diff --git a/canvas_widget.py b/canvas_widget.py index 308c6c2..8e35df8 100644 --- a/canvas_widget.py +++ b/canvas_widget.py @@ -1,3 +1,9 @@ +""" +作者:太一 +微信:taiyi1224 +邮箱:shuobo1224@qq.com +""" + import tkinter as tk from tkinter import ttk from PIL import Image, ImageTk @@ -36,6 +42,9 @@ class CanvasWidget(tk.Canvas): # 初始绘制 self.draw_preview() + def on_template_changed(self): + """当模板变化时重绘画布""" + self.draw_preview() def canvas_to_template(self, x, y): """将画布坐标转换为模板坐标""" scale = self.zoom @@ -204,16 +213,16 @@ class CanvasWidget(tk.Canvas): if img: # 应用遮罩 masked_img = ImageManager.apply_mask(img, fg.mask_shape) - + # 使用基于顶点的图像变形方法 transformed_img, (x, y) = ImageManager.transform_with_vertices( masked_img, fg.vertices ) - + # 确保图像具有透明度 if transformed_img.mode != 'RGBA': transformed_img = transformed_img.convert('RGBA') - + # 缩放到预览尺寸 scaled_img = transformed_img.resize( (max(1, int(transformed_img.width * self.zoom)), diff --git a/export_dialog.py b/export_dialog.py index abe8176..188a827 100644 --- a/export_dialog.py +++ b/export_dialog.py @@ -1,6 +1,19 @@ """导出设置对话框""" +""" +作者:太一 +微信:taiyi1224 +邮箱:shuobo1224@qq.com +""" + +""" +作者:太一 +微信:taiyi1224 +邮箱:shuobo1224@qq.com +""" + import tkinter as tk from tkinter import ttk, filedialog, messagebox +import os from pathlib import Path from exporter import Exporter diff --git a/exporter.py b/exporter.py index e9be104..3fa068c 100644 --- a/exporter.py +++ b/exporter.py @@ -1,3 +1,9 @@ +""" +作者:太一 +微信:taiyi1224 +邮箱:shuobo1224@qq.com +""" + from pathlib import Path from datetime import datetime from concurrent.futures import ThreadPoolExecutor, as_completed diff --git a/image_manager.py b/image_manager.py index 4da2952..fa6d336 100644 --- a/image_manager.py +++ b/image_manager.py @@ -1,3 +1,9 @@ +""" +作者:太一 +微信:taiyi1224 +邮箱:shuobo1224@qq.com +""" + import cv2 import numpy as np from PIL import Image, ImageDraw, ImageOps diff --git a/main.py b/main.py index a8cff3e..a73f9a2 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,14 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +作者:太一 +微信:taiyi1224 +邮箱:shuobo1224@qq.com +""" + +import sys +import os + import tkinter as tk from main_window import MainWindow diff --git a/main_window.py b/main_window.py index 593e771..bbd6126 100644 --- a/main_window.py +++ b/main_window.py @@ -1,3 +1,9 @@ +""" +作者:太一 +微信:taiyi1224 +邮箱:shuobo1224@qq.com +""" + import tkinter as tk from tkinter import ttk, messagebox, filedialog, simpledialog from pathlib import Path @@ -173,7 +179,7 @@ class MainWindow(tk.Tk): if file_paths: # 检查是否是第一次添加图片 is_first_batch = len(self.project.foregrounds) == 0 - + # 添加所有选择的图片 added_foregrounds = [] for file_path in file_paths: @@ -181,19 +187,19 @@ class MainWindow(tk.Tk): if not any(fg.file_path == file_path for fg in self.project.foregrounds): fg = self.project.add_foreground(file_path) added_foregrounds.append(fg) - + # 如果是第一次批量添加,则设置批量处理模式 if is_first_batch and len(added_foregrounds) > 1: # 第一张图片保持可见和可编辑 first_fg = added_foregrounds[0] first_fg.hidden = False first_fg.visible = True - + # 其他图片设置为隐藏 for fg in added_foregrounds[1:]: fg.hidden = True fg.visible = False - + # 选中第一张图片 self.canvas.selected_item = first_fg self.update_property_panel(first_fg) @@ -264,19 +270,43 @@ class MainWindow(tk.Tk): """设置模板""" dialog = TemplateDialog(self, self.project.template) if dialog.result: - if dialog.result["type"] == "preset": - self.project.template = Template( - dialog.result["ratio"], - dialog.result["width"] - ) - else: - self.project.template = Template() + # 处理所有类型的模板设置结果 + if dialog.result["type"] == "preset_loaded": + # 直接更新现有模板的属性 + self.project.template.ratio = dialog.result["ratio"] + self.project.template.width_px = dialog.result["width"] + self.project.template.height_px = dialog.result["height"] + self.project.template.bg_color = dialog.result["bg_color"] + if "bg_image_path" in dialog.result and dialog.result["bg_image_path"]: + self.project.template.load_background(dialog.result["bg_image_path"]) + elif dialog.result["type"] == "preset": + # 更新现有模板的属性 + self.project.template.ratio = tuple(dialog.result["ratio"]) + self.project.template.width_px = dialog.result["width"] + self.project.template.height_px = dialog.result["height"] + self.project.template.bg_color = dialog.result["bg_color"] + # 设置背景图 + if "bg_image_path" in dialog.result and dialog.result["bg_image_path"]: + self.project.template.load_background(dialog.result["bg_image_path"]) + elif "bg_image" in dialog.result: + self.project.template.bg_image = dialog.result["bg_image"] + elif dialog.result["type"] == "custom": + # 更新现有模板的属性 self.project.template.set_custom_size( dialog.result["width"], dialog.result["height"] ) + # 设置ratio属性 + if "ratio" in dialog.result: + self.project.template.ratio = dialog.result["ratio"] + self.project.template.bg_color = dialog.result["bg_color"] + # 设置背景图 + if "bg_image_path" in dialog.result and dialog.result["bg_image_path"]: + self.project.template.load_background(dialog.result["bg_image_path"]) + elif "bg_image" in dialog.result: + self.project.template.bg_image = dialog.result["bg_image"] - self.project.template.bg_color = dialog.result["bg_color"] + # 确保更新画布显示 self.canvas.draw_preview() def batch_export(self): @@ -300,7 +330,7 @@ class MainWindow(tk.Tk): if self.project.foregrounds: first_image_dir = Path(self.project.foregrounds[0].file_path).parent default_output_dir = first_image_dir / "output" - + dialog = ExportDialog(self, len(self.project.foregrounds)) if dialog.result: # 禁用主窗口防止重复操作 diff --git a/models.py b/models.py index 16fe18b..48b603c 100644 --- a/models.py +++ b/models.py @@ -1,3 +1,9 @@ +""" +作者:太一 +微信:taiyi1224 +邮箱:shuobo1224@qq.com +""" + import os from pathlib import Path import json @@ -124,13 +130,13 @@ class Template: self.width_px = width_px self.height_px = int(width_px * ratio[1] / ratio[0]) self.bg_image = None + self.bg_image_path = None # 背景图片路径 self.bg_color = "#FFFFFF" # 默认白色背景 + # models.py 中 set_custom_size 方法添加通知机制 def set_custom_size(self, width, height): - """设置自定义尺寸""" self.width_px = width self.height_px = height - # 根据实际尺寸设置比例 gcd = math.gcd(width, height) self.ratio = (width // gcd, height // gcd) @@ -138,6 +144,7 @@ class Template: """加载背景图""" try: self.bg_image = Image.open(file_path).convert("RGBA") + self.bg_image_path = file_path # 保存背景图片路径 return True except Exception as e: print(f"加载背景图失败: {e}") @@ -146,6 +153,7 @@ class Template: def clear_background(self): """清除背景图""" self.bg_image = None + self.bg_image_path = None def to_dict(self): """转换为字典,用于序列化""" @@ -153,7 +161,8 @@ class Template: "ratio": self.ratio, "width_px": self.width_px, "height_px": self.height_px, - "bg_color": self.bg_color + "bg_color": self.bg_color, + "bg_image_path": self.bg_image_path } @classmethod @@ -162,6 +171,12 @@ class Template: template = cls(data["ratio"], data["width_px"]) template.height_px = data["height_px"] template.bg_color = data.get("bg_color", "#FFFFFF") + template.bg_image_path = data.get("bg_image_path") + + # 如果存在背景图片路径,加载背景图片 + if template.bg_image_path: + template.load_background(template.bg_image_path) + return template diff --git a/template_dialog.py b/template_dialog.py index a62e2c5..fb401d2 100644 --- a/template_dialog.py +++ b/template_dialog.py @@ -1,5 +1,13 @@ +""" +作者:太一 +微信:taiyi1224 +邮箱:shuobo1224@qq.com +""" + import tkinter as tk from tkinter import ttk, messagebox, filedialog +from tkinter import TclError +import math from template_presets import TemplatePresetManager @@ -29,7 +37,7 @@ class TemplateDialog(tk.Toplevel): preset_frame.grid(row=0, column=0, columnspan=3, sticky=tk.EW, pady=(0, 10)) ttk.Label(preset_frame, text="选择预设:").grid(row=0, column=0, sticky=tk.W) - self.preset_var = tk.StringVar() + self.preset_var = tk.StringVar() # 默认空字符串,不选中任何预设 self.preset_combo = ttk.Combobox(preset_frame, textvariable=self.preset_var, width=30) self.preset_combo.grid(row=0, column=1, padx=5) self.update_preset_list() @@ -58,11 +66,15 @@ class TemplateDialog(tk.Toplevel): self.custom_frame = ttk.Frame(frame) ttk.Label(self.custom_frame, text="宽度:").grid(row=0, column=0, sticky=tk.W) self.width_var = tk.IntVar(value=current_template.width_px) - ttk.Entry(self.custom_frame, textvariable=self.width_var, width=8).grid(row=0, column=1, padx=5) + width_entry = ttk.Entry(self.custom_frame, textvariable=self.width_var, width=8) + width_entry.grid(row=0, column=1, padx=5) + width_entry.bind("", self.on_custom_size_change) ttk.Label(self.custom_frame, text="高度:").grid(row=0, column=2, sticky=tk.W, padx=5) self.height_var = tk.IntVar(value=current_template.height_px) - ttk.Entry(self.custom_frame, textvariable=self.height_var, width=8).grid(row=0, column=3, padx=5) + height_entry = ttk.Entry(self.custom_frame, textvariable=self.height_var, width=8) + height_entry.grid(row=0, column=3, padx=5) + height_entry.bind("", self.on_custom_size_change) # 背景设置 ttk.Label(frame, text="背景颜色:").grid(row=3, column=0, sticky=tk.W, pady=5) @@ -100,8 +112,7 @@ class TemplateDialog(tk.Toplevel): presets = self.preset_manager.get_presets() preset_names = [preset["name"] for preset in presets["presets"]] self.preset_combo['values'] = preset_names - if preset_names: - self.preset_combo.current(0) + # 不自动选中任何预设,让用户手动选择 def load_selected_preset(self): """加载选中的预设""" @@ -110,6 +121,8 @@ class TemplateDialog(tk.Toplevel): messagebox.showwarning("警告", "请先选择一个预设") return + # 设置标志以避免在刷新界面时更改尺寸 + self._loading_preset = True if self.preset_manager.load_preset_to_template(selected_preset, self.current_template): # 更新界面显示 self.refresh_from_template() @@ -118,6 +131,8 @@ class TemplateDialog(tk.Toplevel): messagebox.showinfo("成功", f"已加载预设: {selected_preset}") else: messagebox.showerror("错误", "加载预设失败") + # 清除标志 + self._loading_preset = False def save_current_as_preset(self): """将当前设置保存为预设""" @@ -172,15 +187,28 @@ class TemplateDialog(tk.Toplevel): self.show_custom_frame() def on_ratio_change(self): - """处理比例变化""" if self.ratio_var.get() == "自定义": self.show_custom_frame() + # 更新比例显示为当前模板的比例 + ratio_str = f"{self.current_template.ratio[0]}:{self.current_template.ratio[1]}" + self.ratio_var.set(ratio_str) else: self.hide_custom_frame() - # 设置默认尺寸 - w, h = map(int, self.ratio_var.get().split(":")) - self.width_var.set(1920 if w > h else 1080) - self.height_var.set(int(self.width_var.get() * h / w)) + if not (hasattr(self, '_loading_preset') and self._loading_preset): + try: + w, h = map(int, self.ratio_var.get().split(":")) + current_width = self.width_var.get() + if current_width <= 0: + current_width = 100 # 避免宽度为0 + new_height = int(current_width * h / w) + new_height = max(10, new_height) # 确保最小高度 + self.height_var.set(new_height) + # 同步更新当前模板的尺寸 + self.current_template.width_px = current_width + self.current_template.height_px = new_height + self.current_template.ratio = (w, h) + except (ValueError, ZeroDivisionError): + messagebox.showerror("错误", "无效的比例格式") def show_custom_frame(self): """显示自定义尺寸框""" @@ -190,52 +218,96 @@ class TemplateDialog(tk.Toplevel): """隐藏自定义尺寸框""" self.custom_frame.grid_forget() + def on_custom_size_change(self, event=None): + """自定义尺寸变化时的处理""" + if self.ratio_var.get() == "自定义": + try: + width = self.width_var.get() + height = self.height_var.get() + if width <= 0 or height <= 0: + return + # 更新当前模板的尺寸 + self.current_template.set_custom_size(width, height) + # 更新比例显示 + ratio_str = f"{self.current_template.ratio[0]}:{self.current_template.ratio[1]}" + self.ratio_var.set(ratio_str) + # 刷新主窗口画布 + self.parent.get_canvas().draw_preview() + except (ValueError, TclError): + # 忽略无效输入 + pass + def choose_bg_image(self): """选择背景图""" file_path = filedialog.askopenfilename( - filetypes=[ - ("图像文件", "*.jpg;*.jpeg;*.png;*.webp;*.bmp"), - ("所有文件", "*.*") - ] + title="选择背景图", + filetypes=[("图片文件", "*.png *.jpg *.jpeg *.gif *.bmp")] ) if file_path: if self.current_template.load_background(file_path): self.bg_image_var.set("已设置") + # 刷新主窗口画布 + self.parent.get_canvas().draw_preview() + else: + messagebox.showerror("错误", "加载背景图失败") def clear_bg_image(self): """清除背景图""" self.current_template.clear_background() self.bg_image_var.set("无") + # 刷新主窗口画布 + self.parent.get_canvas().draw_preview() def on_ok(self): """确认按钮""" - # 如果选择了预设,则应用预设 - selected_preset = self.preset_var.get() - if selected_preset: - if not self.preset_manager.load_preset_to_template(selected_preset, self.current_template): - messagebox.showerror("错误", "加载预设失败") - return + # 只有在用户主动点击"加载"按钮加载预设时才应用预设 + # 不再自动应用选中的预设 try: if self.ratio_var.get() == "自定义": - width = self.width_var.get() - height = self.height_var.get() + try: + width = self.width_var.get() + height = self.height_var.get() + except tk.TclError: + messagebox.showerror("错误", "请输入有效的整数") + return if width <= 0 or height <= 0: messagebox.showerror("错误", "宽度和高度必须为正数") return + # 更新当前模板 + self.current_template.set_custom_size(width, height) + # 显式设置ratio属性为自定义比例 + gcd = math.gcd(width, height) + ratio_w = width // gcd + ratio_h = height // gcd + self.current_template.ratio = (ratio_w, ratio_h) + self.current_template.bg_color = self.bg_color_var.get() + self.result = { "type": "custom", "width": width, "height": height, - "bg_color": self.bg_color_var.get() + "ratio": (ratio_w, ratio_h), + "bg_color": self.bg_color_var.get(), + "bg_image": self.current_template.bg_image, + "bg_image_path": self.current_template.bg_image_path } else: w, h = map(int, self.ratio_var.get().split(":")) + # 更新当前模板 + self.current_template.ratio = (w, h) + self.current_template.width_px = self.width_var.get() + self.current_template.height_px = self.height_var.get() + self.current_template.bg_color = self.bg_color_var.get() + self.result = { "type": "preset", - "ratio": (w, h), + "ratio": tuple([w, h]), "width": self.width_var.get(), - "bg_color": self.bg_color_var.get() + "height": self.height_var.get(), + "bg_color": self.bg_color_var.get(), + "bg_image": self.current_template.bg_image, + "bg_image_path": self.current_template.bg_image_path } self.destroy() except Exception as e: @@ -250,5 +322,23 @@ class TemplateDialog(tk.Toplevel): + + + + + + + + + + + + + + + + + + diff --git a/transform_utils.py b/transform_utils.py index 3e7253a..fc58dc3 100644 --- a/transform_utils.py +++ b/transform_utils.py @@ -1,3 +1,9 @@ +""" +作者:太一 +微信:taiyi1224 +邮箱:shuobo1224@qq.com +""" + import math import cv2