""" 作者:太一 微信:taiyi1224 邮箱:shuobo1224@qq.com """ import tkinter as tk from tkinter import ttk from PIL import Image, ImageTk from image_manager import ImageManager from transform_utils import TransformHelper import cv2 class CanvasWidget(tk.Canvas): """自定义画布组件,处理绘图和交互""" def __init__(self, parent, project,main_window, *args, **kwargs): super().__init__(parent, *args, **kwargs, bg="#f0f0f0", highlightthickness=1, highlightbackground="#ccc") self.parent = parent # 这是Frame对象 self.main_window = main_window # 这是MainWindow对象 self.project = project self.selected_item = None self.dragging = False self.drag_data = {"x": 0, "y": 0} self.zoom = project.canvas_zoom # 绑定事件 self.bind("", self.on_click) self.bind("", self.on_drag) self.bind("", self.on_release) self.bind("", self.on_zoom) # Windows self.bind("", self.on_zoom) # Linux self.bind("", self.on_zoom) # Linux self.active_handle = None # 当前激活的控制点 # 图像缓存 self.image_cache = {} # 初始绘制 self.draw_preview() def on_template_changed(self): """当模板变化时重绘画布""" self.draw_preview() def canvas_to_template(self, x, y): """将画布坐标转换为模板坐标""" scale = self.zoom template_x = x / scale template_y = y / scale return template_x, template_y def template_to_canvas(self, x, y): """将模板坐标转换为画布坐标""" scale = self.zoom canvas_x = x * scale canvas_y = y * scale return canvas_x, canvas_y # 修改 canvas_widget.py 中的 get_foreground_at 方法,优化碰撞检测 def get_foreground_at(self, x, y): """获取指定坐标处的前景图,优化碰撞检测""" template_x, template_y = self.canvas_to_template(x, y) # 按图层顺序倒序检查(顶层优先) for fg in sorted(self.project.foregrounds, key=lambda f: -f.z_index): if not fg.visible: continue # 更精确的碰撞检测(考虑变换后的顶点) min_x = min(v[0] for v in fg.vertices) max_x = max(v[0] for v in fg.vertices) min_y = min(v[1] for v in fg.vertices) max_y = max(v[1] for v in fg.vertices) if min_x <= template_x <= max_x and min_y <= template_y <= max_y: return fg return None # 修改 canvas_widget.py 中的 on_click 方法,添加控制点检测 def on_click(self, event): """处理鼠标点击事件,添加1. 检查是否点击了控制点 2. 检查是否点击了图片 3. 处理选择状态 """ self.active_handle = None # 先检查是否点击了已选中项的控制点 if self.selected_item: handles = TransformHelper.calculate_handles(self.selected_item, self) for handle in handles.values(): if handle.hit_test(event.x, event.y): self.active_handle = handle.position # 保持选中状态 self.dragging = True self.drag_data["x"] = event.x self.drag_data["y"] = event.y self.draw_preview() return # 2. 检查是否点击了图片 fg = self.get_foreground_at(event.x, event.y) if fg and not fg.locked: self.selected_item = fg self.drag_data["x"] = event.x self.drag_data["y"] = event.y self.dragging = True self.main_window.update_property_panel(fg) else: self.selected_item = None self.main_window.clear_property_panel() self.draw_preview() def on_drag(self, event): """处理拖拽事件""" if self.dragging and self.selected_item: if self.active_handle: # 拖拽控制点 dx = event.x - self.drag_data["x"] dy = event.y - self.drag_data["y"] self.selected_item = TransformHelper.update_vertices( self.selected_item, self.active_handle, dx, dy, self ) else: # 拖拽整个图片 dx = event.x - self.drag_data["x"] dy = event.y - self.drag_data["y"] t_dx, t_dy = self.canvas_to_template(dx, dy) # 移动所有顶点 new_vertices = [] for x, y in self.selected_item.vertices: new_vertices.append((x + t_dx, y + t_dy)) self.selected_item.vertices = new_vertices # 更新拖拽起始位置 self.drag_data["x"] = event.x self.drag_data["y"] = event.y # 重绘 self.draw_preview() # 更新属性面板 self.main_window.update_property_panel(self.selected_item) def on_release(self, event): """处理鼠标释放事件""" self.dragging = False self.active_handle = None def on_zoom(self, event): """处理缩放事件""" # 计算缩放因子 if event.delta > 0 or event.num == 4: # 放大 self.zoom *= 1.1 else: # 缩小 self.zoom /= 1.1 # 限制缩放范围 self.zoom = max(0.1, min(self.zoom, 5.0)) # 更新项目的缩放值 self.project.canvas_zoom = self.zoom # 重绘 self.draw_preview() def draw_preview(self): """绘制预览内容""" self.delete("all") # 获取模板尺寸 template_w = self.project.template.width_px template_h = self.project.template.height_px # 计算画布上的模板尺寸 canvas_w = template_w * self.zoom canvas_h = template_h * self.zoom # 调整画布滚动区域 self.config(scrollregion=(0, 0, canvas_w, canvas_h)) # 绘制模板背景 self.create_rectangle(0, 0, canvas_w, canvas_h, fill=self.project.template.bg_color) # 绘制背景图(如果有) if self.project.template.bg_image: bg_key = id(self.project.template.bg_image) if bg_key not in self.image_cache or self.image_cache[bg_key][0] != self.zoom: # 缩放背景图并缓存 scaled_bg = self.project.template.bg_image.resize( (int(canvas_w), int(canvas_h)), Image.Resampling.LANCZOS ) self.image_cache[bg_key] = (self.zoom, ImageTk.PhotoImage(scaled_bg)) bg_img = self.image_cache[bg_key][1] self.create_image(0, 0, image=bg_img, anchor=tk.NW) # 按图层顺序绘制前景图 for fg in sorted(self.project.foregrounds, key=lambda f: f.z_index): # 跳过隐藏的图片 if fg.hidden or not fg.visible: continue try: # 检查缓存 (使用顶点元组作为键的一部分) img_key = (fg.file_path, tuple(tuple(v) for v in fg.vertices), fg.angle, fg.mask_shape, self.zoom) if img_key not in self.image_cache: # 加载并处理图像 img = ImageManager.load_image(fg.file_path) 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)), max(1, int(transformed_img.height * self.zoom))), Image.Resampling.LANCZOS ) # 缓存图像 self.image_cache[img_key] = ImageTk.PhotoImage(scaled_img) # 获取画布坐标 min_x = min(v[0] for v in fg.vertices) min_y = min(v[1] for v in fg.vertices) canvas_x, canvas_y = self.template_to_canvas(min_x, min_y) # 绘制图像 if img_key in self.image_cache: img = self.image_cache[img_key] # 使用包围盒左上角坐标绘制图像 self.create_image(canvas_x, canvas_y, image=img, anchor=tk.NW) # 如果是选中状态,绘制边框 if self.selected_item == fg: # 绘制变形后的边框 canvas_vertices = [ self.template_to_canvas(x, y) for x, y in fg.vertices ] # 绘制边框 self.create_polygon( canvas_vertices, outline="blue", width=2, dash=(4, 2) ) # 绘制控制点 handles = TransformHelper.calculate_handles(fg, self) for handle in handles.values(): self.create_oval( handle.x - handle.radius, handle.y - handle.radius, handle.x + handle.radius, handle.y + handle.radius, fill="white", outline="blue", width=2 ) except Exception as e: print(f"绘制图像失败: {e}") # 绘制一个占位矩形 min_x = min(v[0] for v in fg.vertices) min_y = min(v[1] for v in fg.vertices) canvas_x, canvas_y = self.template_to_canvas(min_x, min_y) # 计算宽度和高度 width = max(v[0] for v in fg.vertices) - min_x height = max(v[1] for v in fg.vertices) - min_y canvas_w, canvas_h = self.template_to_canvas(width, height) self.create_rectangle( canvas_x, canvas_y, canvas_x + canvas_w, canvas_y + canvas_h, outline="red", fill="#ffeeee", dash=(2, 2) ) self.create_text( canvas_x + canvas_w / 2, canvas_y + canvas_h / 2, text="无法加载", fill="red" )