Files
cmbot/docs/08-image-editor-design.md
T

14 KiB
Raw Blame History

图像编辑器设计

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。

图层顺序:

背景棋盘格
衣服底图 Item
印花图片 Item
印花选中框 Item
缩放控制点 Item
旋转控制点 Item
操作提示 Overlay

说明:

  • 背景棋盘格可以由 view 背景或 scene 背景绘制。
  • 衣服底图不可拖动。
  • 印花图片可拖动、缩放、旋转。
  • 选中框和控制点仅用于交互,不参与最终导出。
  • 操作提示仅显示在 UI 中,不参与最终导出。

6. 状态模型

印花变换状态使用统一模型表示:

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 设计

建议实现自定义印花图层:

TransformablePrintItem

职责:

  • 显示印花图片。
  • 接收拖动、缩放、旋转交互。
  • 绘制或管理选中框。
  • 发出变换状态变化信号。

不负责:

  • 不负责最终 Pillow 导出。
  • 不负责批量任务。
  • 不负责读取文件夹。
  • 不负责保存模板文件。

8. 拖动设计

8.1 操作行为

用户按住印花主体区域并拖动,移动印花位置。

要求:

  • 拖动过程中实时移动印花。
  • 拖动过程中可以实时更新状态,也可以拖动结束后统一提交。
  • 拖动结束后必须同步 X/Y 输入框。
  • 拖动不改变宽度、高度和旋转角度。

8.2 坐标更新

拖动位移应在 scene 坐标中计算。

推荐公式:

new_x = old_x + delta_scene_x
new_y = old_y + delta_scene_y

由于 scene 坐标与原图像素坐标一致,new_x 和 new_y 可以直接写入 TransformState。

规则:

  • 不强制限制印花必须完全在衣服底图内。
  • 导出时只保留衣服画布范围内的内容。
  • 如需边界吸附,应作为后续增强功能,不作为第一阶段必需。

9. 缩放设计

9.1 控制点

选中印花时显示 8 个缩放控制点:

左上、上中、右上、右中、右下、下中、左下、左中

第一阶段至少需要支持四角缩放:

左上、右上、右下、左下

9.2 缩放行为

要求:

  • 默认锁定宽高比例。
  • 勾选 锁定宽高比例 时,拖动四角控制点按原始比例缩放。
  • 取消锁定后,可以独立改变宽度和高度。
  • 缩放后同步宽度和高度输入框。
  • 缩放不改变旋转角度。

9.3 最小尺寸

必须设置最小尺寸,避免印花缩放为 0 或负数。

建议:

min_width = 10 px
min_height = 10 px

9.4 缩放中心

第一阶段推荐使用对角固定缩放:

  • 拖动右下角时,左上角保持不动。
  • 拖动左上角时,右下角保持不动。
  • 拖动右上角时,左下角保持不动。
  • 拖动左下角时,右上角保持不动。

若实现复杂度过高,可第一阶段使用中心缩放,但必须保证参数和导出一致。

9.5 旋转状态下缩放

旋转后的缩放更复杂。第一阶段推荐规则:

  • 控制点跟随旋转后的选中框显示。
  • 计算缩放时先将鼠标点从 scene 坐标转换到印花本地坐标。
  • 在印花本地坐标中计算新的宽高。
  • 再更新 TransformState。

如果第一阶段暂不实现旋转状态下的精确控制点缩放,必须在实现说明中明确限制,并保证输入框缩放和导出仍然正确。

10. 旋转设计

10.1 控制点

旋转控制点位于印花选中框顶部中心外侧,通过一条短线连接选中框。

10.2 旋转中心

旋转中心为印花缩放后矩形的中心点。

计算:

center_x = x + width / 2
center_y = y + height / 2

10.3 鼠标旋转

用户拖动旋转控制点时,根据鼠标当前位置和中心点计算角度。

推荐公式:

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°

规则:

左旋 90°:rotation = rotation - 90
右旋 90°:rotation = rotation + 90

更新后必须刷新预览并同步输入框。

11. 参数面板同步

参数面板包含:

  • X 坐标。
  • Y 坐标。
  • 宽度。
  • 高度。
  • 锁定宽高比例。
  • 角度。

同步规则:

预览区交互 -> 更新 TransformState -> 更新参数面板
参数面板输入 -> 更新 TransformState -> 刷新预览区
模板选择 -> 生成 TransformState -> 刷新预览区和参数面板
队列项选择 -> 加载该项 TransformState -> 刷新预览区和参数面板

防循环规则:

  • 参数面板程序化更新输入框时,应避免触发重复变更。
  • 可以使用 blockSignals(True/False) 或内部更新标记。

12. 预览缩放

预览区支持画布缩放,例如:

82%

规则:

  • 预览缩放只改变 QGraphicsView 的显示变换。
  • 预览缩放不改变 TransformState.x/y/width/height。
  • 鼠标事件需要通过 Qt 的坐标映射转换回 scene 坐标。

推荐:

view.mapToScene(mouse_pos)

13. 最终导出设计

最终导出由 Pillow 执行,不使用 Qt 截图。

13.1 单张合成输入

garment_path
print_path
TransformState
ExportOptions

13.2 合成步骤

推荐流程:

读取衣服底图为 RGBA
读取印花图为 RGBA
将印花缩放到 TransformState.width / height
按 TransformState.rotation 围绕中心旋转
计算旋转后图片应粘贴到衣服底图上的位置
使用 alpha_composite 合成
根据 ExportOptions 保存 PNG 或 JPG

13.3 旋转后粘贴位置

Pillow 旋转通常会产生新的包围盒。

规则:

  • TransformState.x/y/width/height 表示旋转前矩形。
  • 旋转中心为该矩形中心。
  • 旋转后图片粘贴位置应保证旋转中心仍落在同一个中心点。

计算:

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 分析范围

衣服颜色分析不应使用整张衣服图,而应使用当前印花将要覆盖的衣服区域。

输入:

garment_path
print_path
TransformState

推荐区域:

garment_region = x, y, width, height

印花颜色分析应只统计有效像素:

alpha > 20

这样可以避免透明 PNG 的透明区域干扰颜色判断。

16.3 第一阶段算法

第一阶段推荐使用简单稳定的组合指标:

RGB 颜色距离
亮度差

示例判断:

如果 RGB 距离 < 45 且亮度差 < 35
=> 标记为 不明显

可见度分级:

正常
偏低
不明显

阈值应集中定义,避免散落在 UI 或批量任务代码中。

16.4 更准确的后续算法

后续可以在内存中模拟合成但不保存文件:

按 TransformState 缩放、旋转印花
将印花在内存中叠加到衣服目标区域
比较合成前后的目标区域差异
如果差异很小,标记为不明显

这种方式比单纯比较主色更准确,因为它考虑透明度、旋转、图案面积和实际覆盖范围。

16.5 队列集成

低对比筛选应发生在队列生成后、批量导出前:

生成合成队列
-> 对每个队列项计算可见度评分
-> 标记 正常 / 偏低 / 不明显
-> 用户筛选或跳过低对比项
-> 执行批量导出

要求:

  • 单个组合分析失败不应中断整个队列。
  • 分析失败应记录日志,并在队列项中显示可理解状态。
  • 用户应能查看被标记为 偏低 或 不明显 的组合。
  • 用户应能决定是否跳过这些组合。

17. 边界情况

必须处理:

  • 印花部分超出衣服画布。
  • 印花完全超出衣服画布。
  • 印花透明 PNG。
  • 衣服图和印花图尺寸很大。
  • 文件路径包含中文。
  • 宽度或高度输入为 0、负数或非数字。
  • 旋转角度超过 360 度。
  • 衣服目标区域和印花有效区域颜色接近,导致低对比。
  • 印花有效像素过少,无法可靠判断主色。

处理规则:

  • 部分超出画布允许导出,只保留画布内内容。
  • 完全超出画布时可以导出空印花效果,但应提示用户确认或标记风险。
  • 非法尺寸输入应阻止提交并提示。
  • 角度可以归一化显示。
  • 低对比组合应提示或标记,但不自动删除。
  • 印花有效像素过少时应标记为无法判断,并记录日志。

18. 测试建议

核心测试:

  • 未旋转印花合成位置正确。
  • 缩放后导出尺寸正确。
  • 旋转 90 度后中心不偏移。
  • 旋转 -8 度后预览和导出大体一致。
  • 印花部分超出画布时不会报错。
  • 透明 PNG 不出现黑底或白底。
  • 预览缩放到 50% 或 200% 后,拖动仍能得到正确原图坐标。
  • 参数面板输入后预览同步。
  • 预览拖动后参数面板同步。
  • 白色衣服配浅色印花时能标记为低对比或不明显。
  • 深色衣服配深色印花时能标记为低对比或不明显。
  • 透明 PNG 的透明区域不参与印花主色判断。