PictureEdit/models.py
taiyi fc83bb0bfc revert f868be562f
revert 第一次提交
2025-09-03 11:55:31 +08:00

265 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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