import os from pathlib import Path import json import math from PIL import Image class ForegroundImage: """前景图对象,存储图片信息和变换参数""" def __init__(self, file_path): self.file_path = file_path # 四个顶点坐标 (左上, 右上, 左下, 右下) self.vertices = [ (0.0, 0.0), # top-left (100.0, 0.0), # top-right (0.0, 100.0), # bottom-left (100.0, 100.0) # bottom-right ] self.angle = 0.0 # 旋转角度 self.mask_shape = "rect" # 遗罩形状 self.locked = False # 是否锁定 self.visible = True # 是否可见 self.z_index = 0 # 图层顺序 self.hidden = False # 是否隐藏(用于批量处理模式) # 添加属性x, y, w, h的getter和setter方法,使其与vertices保持同步 @property def x(self): return self.vertices[0][0] # 左上角x坐标作为x属性 @x.setter def x(self, value): # 更新所有顶点的x坐标以保持宽度不变 dx = value - self.vertices[0][0] for i in range(len(self.vertices)): x, y = self.vertices[i] self.vertices[i] = (x + dx, y) @property def y(self): return self.vertices[0][1] # 左上角y坐标作为y属性 @y.setter def y(self, value): # 更新所有顶点的y坐标以保持高度不变 dy = value - self.vertices[0][1] for i in range(len(self.vertices)): x, y = self.vertices[i] self.vertices[i] = (x, y + dy) @property def w(self): # 宽度为右上角和左上角x坐标的差值 return self.vertices[1][0] - self.vertices[0][0] @w.setter def w(self, value): # 保持左上角不变,调整右上角和右下角的x坐标 if value > 0: old_w = self.vertices[1][0] - self.vertices[0][0] if old_w != 0: x0, y0 = self.vertices[0] # 左上角 x1, y1 = self.vertices[1] # 右上角 x2, y2 = self.vertices[2] # 左下角 x3, y3 = self.vertices[3] # 右下角 # 调整右上角和右下角的x坐标 self.vertices[1] = (x0 + value, y1) # 右上角 self.vertices[3] = (x0 + value, y3) # 右下角 @property def h(self): # 高度为左下角和左上角y坐标的差值 return self.vertices[2][1] - self.vertices[0][1] @h.setter def h(self, value): # 保持左上角不变,调整左下角和右下角的y坐标 if value > 0: old_h = self.vertices[2][1] - self.vertices[0][1] if old_h != 0: x0, y0 = self.vertices[0] # 左上角 x1, y1 = self.vertices[1] # 右上角 x2, y2 = self.vertices[2] # 左下角 x3, y3 = self.vertices[3] # 右下角 # 调整左下角和右下角的y坐标 self.vertices[2] = (x2, y0 + value) # 左下角 self.vertices[3] = (x3, y0 + value) # 右下角 def to_dict(self): """转换为字典,用于序列化""" return { "file_path": self.file_path, "vertices": self.vertices, "angle": self.angle, "mask_shape": self.mask_shape, "locked": self.locked, "visible": self.visible, "z_index": self.z_index, "hidden": self.hidden } @classmethod def from_dict(cls, data): """从字典创建对象""" fg = cls(data["file_path"]) fg.vertices = data["vertices"] fg.angle = data["angle"] fg.mask_shape = data.get("mask_shape", "rect") fg.locked = data.get("locked", False) fg.visible = data.get("visible", True) fg.z_index = data.get("z_index", 0) fg.hidden = data.get("hidden", False) return fg class Template: """模板对象,存储尺寸和背景信息""" def __init__(self, ratio=(16, 9), width_px=1200): self.ratio = ratio self.width_px = width_px self.height_px = int(width_px * ratio[1] / ratio[0]) self.bg_image = None self.bg_color = "#FFFFFF" # 默认白色背景 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) def load_background(self, file_path): """加载背景图""" try: self.bg_image = Image.open(file_path).convert("RGBA") return True except Exception as e: print(f"加载背景图失败: {e}") return False def clear_background(self): """清除背景图""" self.bg_image = None def to_dict(self): """转换为字典,用于序列化""" return { "ratio": self.ratio, "width_px": self.width_px, "height_px": self.height_px, "bg_color": self.bg_color } @classmethod def from_dict(cls, data): """从字典创建对象""" template = cls(data["ratio"], data["width_px"]) template.height_px = data["height_px"] template.bg_color = data.get("bg_color", "#FFFFFF") return template class Project: """项目对象,管理模板和前景图""" def __init__(self): self.template = Template() self.foregrounds = [] self.canvas_zoom = 1.0 self.file_path = None # 项目文件路径 def add_foreground(self, file_path, reference_fg=None): """添加前景图""" fg = ForegroundImage(file_path) # 如果提供了参考图片,则复制其属性 if reference_fg is not None: fg.vertices = [tuple(v) for v in reference_fg.vertices] # 复制顶点 fg.angle = reference_fg.angle fg.mask_shape = reference_fg.mask_shape fg.locked = reference_fg.locked fg.visible = reference_fg.visible else: # 尝试获取图片原始尺寸并设置初始大小 try: with Image.open(file_path) as img: w, h = img.size # 按比例缩小到模板的2/3大小 scale = min(self.template.width_px * 2/3 / w, self.template.height_px * 2/3 / h) # 计算初始位置(居中) initial_w = w * scale initial_h = h * scale initial_x = (self.template.width_px - initial_w) / 2 initial_y = (self.template.height_px - initial_h) / 2 # 设置顶点坐标 fg.vertices = [ (initial_x, initial_y), # top-left (initial_x + initial_w, initial_y), # top-right (initial_x, initial_y + initial_h), # bottom-left (initial_x + initial_w, initial_y + initial_h) # bottom-right ] except Exception as e: print(f"获取图片尺寸失败: {e}") # 设置图层顺序 fg.z_index = len(self.foregrounds) self.foregrounds.append(fg) return fg def remove_foreground(self, fg): """移除前景图""" if fg in self.foregrounds: self.foregrounds.remove(fg) # 更新图层顺序 self.update_z_indices() def update_z_indices(self): """更新所有前景图的图层顺序""" for i, fg in enumerate(sorted(self.foregrounds, key=lambda x: x.z_index)): fg.z_index = i def save(self, file_path=None): """保存项目到文件""" if file_path is None: file_path = self.file_path if file_path is None: return False data = { "template": self.template.to_dict(), "foregrounds": [fg.to_dict() for fg in self.foregrounds], "canvas_zoom": self.canvas_zoom } try: with open(file_path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) self.file_path = file_path return True except Exception as e: print(f"保存项目失败: {e}") return False def load(self, file_path): """从文件加载项目""" try: with open(file_path, "r", encoding="utf-8") as f: data = json.load(f) self.template = Template.from_dict(data["template"]) self.foregrounds = [ForegroundImage.from_dict(fg_data) for fg_data in data["foregrounds"]] self.canvas_zoom = data.get("canvas_zoom", 1.0) self.file_path = file_path return True except Exception as e: print(f"加载项目失败: {e}") return False