# 图像编辑器设计 ## 1. 文档定位 本文档定义合成预览区的实现规则,包括衣服底图显示、印花图层拖动、缩放、旋转、坐标换算、参数同步,以及预览和最终导出保持一致的规则。 后续任何涉及 `image_canvas.py`、印花变换、坐标换算、图片预览或最终合成一致性的任务,都必须阅读本文档。 ## 2. 设计目标 - 用户可以在预览区直接拖动、缩放、旋转印花。 - 参数面板和预览区双向同步。 - 预览区缩放不影响真实合成参数。 - 所有合成参数以衣服底图原始像素坐标系保存。 - 最终导出结果与预览中的衣服、印花位置、尺寸、角度一致。 - 图片合成逻辑可以脱离 GUI 单独测试。 ## 3. 核心原则 必须遵守: - Qt 预览负责交互和显示。 - Pillow 负责最终图片合成和导出。 - 禁止截图预览区作为最终导出图片。 - 禁止把屏幕坐标直接保存为合成坐标。 - 禁止把预览缩放比例写入真实合成参数。 - `TransformState` 是唯一可信的印花变换状态。 ## 4. 坐标系定义 图像编辑器涉及三套坐标系: ### 4.1 屏幕坐标 屏幕坐标是鼠标事件中的窗口坐标或控件坐标。 用途: - 接收鼠标点击、拖动、滚轮事件。 - 判断用户操作了哪个控制点。 规则: - 屏幕坐标只能作为交互输入。 - 屏幕坐标不能直接用于最终合成。 ### 4.2 Scene 坐标 Scene 坐标是 `QGraphicsScene` 中的坐标。 用途: - 显示衣服底图。 - 显示印花图层。 - 显示选中框、缩放手柄和旋转手柄。 推荐规则: - `QGraphicsScene` 的坐标应尽量与衣服底图原始像素坐标保持一致。 - 衣服底图左上角放在 scene 坐标 `(0, 0)`。 - 衣服底图宽高等于原始图片像素宽高。 - 画布缩放通过 `QGraphicsView` 的 view transform 实现,不修改 scene 中图片真实尺寸。 ### 4.3 原图像素坐标 原图像素坐标是衣服底图原始图片的像素坐标。 用途: - 保存印花位置。 - 保存印花尺寸。 - 执行最终 Pillow 合成。 - 保存模板参数。 规则: - `TransformState.x` 和 `TransformState.y` 使用原图像素坐标。 - `TransformState.width` 和 `TransformState.height` 使用原图像素尺寸。 - `TransformState.rotation` 使用角度,单位为度。 ## 5. 图层结构 预览区使用 `QGraphicsView / QGraphicsScene`。 图层顺序: ```text 背景棋盘格 衣服底图 Item 印花图片 Item 印花选中框 Item 缩放控制点 Item 旋转控制点 Item 操作提示 Overlay ``` 说明: - 背景棋盘格可以由 view 背景或 scene 背景绘制。 - 衣服底图不可拖动。 - 印花图片可拖动、缩放、旋转。 - 选中框和控制点仅用于交互,不参与最终导出。 - 操作提示仅显示在 UI 中,不参与最终导出。 ### 5.1 预览控制层显隐 预览区的印花选中框、缩放控制点、旋转连接线和旋转控制点属于交互控制层。该控制层只帮助用户识别和操作当前印花,不属于图片内容。 按键行为: - 当预览区拥有键盘焦点时,按 `Esc` 应隐藏当前印花的交互控制层。 - `Esc` 只隐藏印花选中框、缩放控制点、旋转连接线和旋转控制点,不隐藏印花图片本身。 - `Esc` 不执行撤销,不回滚当前拖动、缩放或旋转结果,不修改 `TransformState`。 - 如果焦点在参数输入框、文件列表或其他控件中,预览区可以不响应该次 `Esc`。 焦点规则: - `QGraphicsView` 应允许获得键盘焦点。 - 用户点击预览区时,预览区应获得焦点,以便后续 `Esc` 能被画布接收。 恢复规则: - 控制层被隐藏后,用户再次点击预览区中的印花图片,应恢复控制层,并继续沿用原有拖动逻辑。 - 控制层隐藏期间,隐藏的缩放控制点和旋转控制点不需要响应命中测试;恢复后原有缩放、旋转行为保持不变。 - 重新加载印花图片或创建新的印花图层时,控制层默认显示。 一致性规则: - 控制层显隐状态不参与模板保存、队列项保存、单张导出或批量导出。 - 最终导出仍只读取 `TransformState` 和原始图片,不读取控制层显隐状态。 ## 6. 状态模型 印花变换状态使用统一模型表示: ```text TransformState x: float y: float width: float height: float rotation: float keep_aspect_ratio: bool ``` 字段含义: - `x`:印花未旋转包围盒左上角在衣服原图中的 X 坐标。 - `y`:印花未旋转包围盒左上角在衣服原图中的 Y 坐标。 - `width`:印花缩放后的宽度。 - `height`:印花缩放后的高度。 - `rotation`:围绕印花中心旋转的角度,单位为度。 - `keep_aspect_ratio`:是否锁定宽高比例。 规则: - UI 面板、预览区和导出逻辑必须共享同一个 `TransformState`。 - 预览区交互完成后更新 `TransformState`。 - 参数面板输入变化后更新 `TransformState`。 - `TransformState` 变化后统一刷新预览。 ## 7. 印花 Item 设计 建议实现自定义印花图层: ```text TransformablePrintItem ``` 职责: - 显示印花图片。 - 接收拖动、缩放、旋转交互。 - 绘制或管理选中框。 - 发出变换状态变化信号。 不负责: - 不负责最终 Pillow 导出。 - 不负责批量任务。 - 不负责读取文件夹。 - 不负责保存模板文件。 ## 8. 拖动设计 ### 8.1 操作行为 用户按住印花主体区域并拖动,移动印花位置。 要求: - 拖动过程中实时移动印花。 - 拖动过程中可以实时更新状态,也可以拖动结束后统一提交。 - 拖动结束后必须同步 X/Y 输入框。 - 拖动不改变宽度、高度和旋转角度。 ### 8.2 坐标更新 拖动位移应在 scene 坐标中计算。 推荐公式: ```text new_x = old_x + delta_scene_x new_y = old_y + delta_scene_y ``` 由于 scene 坐标与原图像素坐标一致,`new_x` 和 `new_y` 可以直接写入 `TransformState`。 规则: - 不强制限制印花必须完全在衣服底图内。 - 导出时只保留衣服画布范围内的内容。 - 如需边界吸附,应作为后续增强功能,不作为第一阶段必需。 ## 9. 缩放设计 ### 9.1 控制点 选中印花时显示 8 个缩放控制点: ```text 左上、上中、右上、右中、右下、下中、左下、左中 ``` 第一阶段至少需要支持四角缩放: ```text 左上、右上、右下、左下 ``` ### 9.2 缩放行为 要求: - 默认锁定宽高比例。 - 勾选 `锁定宽高比例` 时,拖动四角控制点按原始比例缩放。 - 取消锁定后,可以独立改变宽度和高度。 - 缩放后同步宽度和高度输入框。 - 缩放不改变旋转角度。 ### 9.3 最小尺寸 必须设置最小尺寸,避免印花缩放为 0 或负数。 建议: ```text min_width = 10 px min_height = 10 px ``` ### 9.4 缩放中心 第一阶段推荐使用对角固定缩放: - 拖动右下角时,左上角保持不动。 - 拖动左上角时,右下角保持不动。 - 拖动右上角时,左下角保持不动。 - 拖动左下角时,右上角保持不动。 若实现复杂度过高,可第一阶段使用中心缩放,但必须保证参数和导出一致。 ### 9.5 旋转状态下缩放 旋转后的缩放更复杂。第一阶段推荐规则: - 控制点跟随旋转后的选中框显示。 - 计算缩放时先将鼠标点从 scene 坐标转换到印花本地坐标。 - 在印花本地坐标中计算新的宽高。 - 再更新 `TransformState`。 如果第一阶段暂不实现旋转状态下的精确控制点缩放,必须在实现说明中明确限制,并保证输入框缩放和导出仍然正确。 ## 10. 旋转设计 ### 10.1 控制点 旋转控制点位于印花选中框顶部中心外侧,通过一条短线连接选中框。 ### 10.2 旋转中心 旋转中心为印花缩放后矩形的中心点。 计算: ```text center_x = x + width / 2 center_y = y + height / 2 ``` ### 10.3 鼠标旋转 用户拖动旋转控制点时,根据鼠标当前位置和中心点计算角度。 推荐公式: ```text angle = atan2(mouse_y - center_y, mouse_x - center_x) rotation = angle_to_degrees(angle) + offset ``` `offset` 用于让控制点初始方向和视觉方向一致。 要求: - 拖动时实时更新预览。 - 旋转结束后同步角度输入框。 - 角度可以为负数。 - 内部可以归一化到 `-180..180` 或 `0..360`,但 UI 表示要一致。 ### 10.4 按钮旋转 右侧参数栏包含: - `左旋 90°` - `右旋 90°` 规则: ```text 左旋 90°:rotation = rotation - 90 右旋 90°:rotation = rotation + 90 ``` 更新后必须刷新预览并同步输入框。 ## 11. 参数面板同步 参数面板包含: - X 坐标。 - Y 坐标。 - 宽度。 - 高度。 - 锁定宽高比例。 - 角度。 同步规则: ```text 预览区交互 -> 更新 TransformState -> 更新参数面板 参数面板输入 -> 更新 TransformState -> 刷新预览区 模板选择 -> 生成 TransformState -> 刷新预览区和参数面板 队列项选择 -> 加载该项 TransformState -> 刷新预览区和参数面板 ``` 防循环规则: - 参数面板程序化更新输入框时,应避免触发重复变更。 - 可以使用 `blockSignals(True/False)` 或内部更新标记。 ## 12. 预览缩放 预览区支持画布缩放,例如: ```text 82% ``` 规则: - 预览缩放只改变 `QGraphicsView` 的显示变换。 - 预览缩放不改变 `TransformState.x/y/width/height`。 - 鼠标事件需要通过 Qt 的坐标映射转换回 scene 坐标。 推荐: ```text view.mapToScene(mouse_pos) ``` ## 13. 最终导出设计 最终导出由 Pillow 执行,不使用 Qt 截图。 ### 13.1 单张合成输入 ```text garment_path print_path TransformState ExportOptions ``` ### 13.2 合成步骤 推荐流程: ```text 读取衣服底图为 RGBA 读取印花图为 RGBA 将印花缩放到 TransformState.width / height 按 TransformState.rotation 围绕中心旋转 计算旋转后图片应粘贴到衣服底图上的位置 使用 alpha_composite 合成 根据 ExportOptions 保存 PNG 或 JPG ``` ### 13.3 旋转后粘贴位置 Pillow 旋转通常会产生新的包围盒。 规则: - `TransformState.x/y/width/height` 表示旋转前矩形。 - 旋转中心为该矩形中心。 - 旋转后图片粘贴位置应保证旋转中心仍落在同一个中心点。 计算: ```text center_x = x + width / 2 center_y = y + height / 2 paste_x = center_x - rotated_width / 2 paste_y = center_y - rotated_height / 2 ``` ### 13.4 输出格式 PNG: - 保留 RGBA 或按需求输出不透明图。 JPG: - JPG 不支持透明通道。 - 保存 JPG 前必须合成到 RGB 背景。 - 默认背景建议使用白色,除非后续有其他需求。 ## 14. 预览与导出一致性 必须保证: - Qt 中印花显示位置来自 `TransformState`。 - Pillow 导出也使用同一个 `TransformState`。 - 两边使用相同的旋转中心。 - 两边使用相同的宽度、高度和角度定义。 - 预览缩放比例不参与导出。 允许存在的差异: - Qt 和 Pillow 的抗锯齿细节可能略有不同。 - 字节级像素不要求完全一致。 不允许存在的差异: - 印花明显偏移。 - 印花尺寸明显不同。 - 旋转中心不同。 - 透明区域显示异常。 ## 15. 队列项微调 UI 设计稿中存在 `已微调` 状态。 定义: - 当用户选中队列中的某一项,并手动调整位置、尺寸或旋转后,该队列项标记为 `已微调`。 - `已微调` 队列项应保存自己的 `TransformState`。 - 重新应用模板时,是否覆盖 `已微调` 项需要用户确认或遵循明确规则。 第一阶段推荐规则: - 当前队列项被手动调整后标记为 `已微调`。 - 批量导出时,已微调项使用自己的 `TransformState`。 - 未微调项使用当前模板生成的 `TransformState`。 ## 16. 低对比组合筛选设计 ### 16.1 问题定义 当衣服目标区域颜色和印花主要颜色接近时,用户即使完成合成,也可能看不清印花效果。软件应支持在导出前先筛选出这类组合,帮助用户决定是否跳过、替换印花或调整参数。 该功能是筛选和提示能力,不应自动修改素材,不应直接替用户删除队列项。 ### 16.2 分析范围 衣服颜色分析不应使用整张衣服图,而应使用当前印花将要覆盖的衣服区域。 输入: ```text garment_path print_path TransformState ``` 推荐区域: ```text garment_region = x, y, width, height ``` 印花颜色分析应只统计有效像素: ```text alpha > 20 ``` 这样可以避免透明 PNG 的透明区域干扰颜色判断。 ### 16.3 第一阶段算法 第一阶段推荐使用简单稳定的组合指标: ```text RGB 颜色距离 亮度差 ``` 示例判断: ```text 如果 RGB 距离 < 45 且亮度差 < 35 => 标记为 不明显 ``` 可见度分级: ```text 正常 偏低 不明显 ``` 阈值应集中定义,避免散落在 UI 或批量任务代码中。 ### 16.4 更准确的后续算法 后续可以在内存中模拟合成但不保存文件: ```text 按 TransformState 缩放、旋转印花 将印花在内存中叠加到衣服目标区域 比较合成前后的目标区域差异 如果差异很小,标记为不明显 ``` 这种方式比单纯比较主色更准确,因为它考虑透明度、旋转、图案面积和实际覆盖范围。 ### 16.5 队列集成 低对比筛选应发生在队列生成后、批量导出前: ```text 生成合成队列 -> 对每个队列项计算可见度评分 -> 标记 正常 / 偏低 / 不明显 -> 用户筛选或跳过低对比项 -> 执行批量导出 ``` 要求: - 单个组合分析失败不应中断整个队列。 - 分析失败应记录日志,并在队列项中显示可理解状态。 - 用户应能查看被标记为 `偏低` 或 `不明显` 的组合。 - 用户应能决定是否跳过这些组合。 ## 17. 边界情况 必须处理: - 印花部分超出衣服画布。 - 印花完全超出衣服画布。 - 印花透明 PNG。 - 衣服图和印花图尺寸很大。 - 文件路径包含中文。 - 宽度或高度输入为 0、负数或非数字。 - 旋转角度超过 360 度。 - 衣服目标区域和印花有效区域颜色接近,导致低对比。 - 印花有效像素过少,无法可靠判断主色。 处理规则: - 部分超出画布允许导出,只保留画布内内容。 - 完全超出画布时可以导出空印花效果,但应提示用户确认或标记风险。 - 非法尺寸输入应阻止提交并提示。 - 角度可以归一化显示。 - 低对比组合应提示或标记,但不自动删除。 - 印花有效像素过少时应标记为无法判断,并记录日志。 ## 18. 测试建议 核心测试: - 未旋转印花合成位置正确。 - 缩放后导出尺寸正确。 - 旋转 90 度后中心不偏移。 - 旋转 -8 度后预览和导出大体一致。 - 印花部分超出画布时不会报错。 - 透明 PNG 不出现黑底或白底。 - 预览缩放到 50% 或 200% 后,拖动仍能得到正确原图坐标。 - 参数面板输入后预览同步。 - 预览拖动后参数面板同步。 - 点击预览区后按 `Esc`,印花选中框、缩放控制点和旋转控制点隐藏,但印花仍可见。 - 控制层隐藏后再次点击印花,控制层恢复,拖动、缩放、旋转仍按原有规则工作。 - 控制层显隐不改变 `TransformState`,也不影响导出结果。 - 白色衣服配浅色印花时能标记为低对比或不明显。 - 深色衣服配深色印花时能标记为低对比或不明显。 - 透明 PNG 的透明区域不参与印花主色判断。