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

474 lines
11 KiB
Markdown
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.
# 图像编辑器设计
## 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 中,不参与最终导出。
## 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. 边界情况
必须处理:
- 印花部分超出衣服画布。
- 印花完全超出衣服画布。
- 印花透明 PNG。
- 衣服图和印花图尺寸很大。
- 文件路径包含中文。
- 宽度或高度输入为 0、负数或非数字。
- 旋转角度超过 360 度。
处理规则:
- 部分超出画布允许导出,只保留画布内内容。
- 完全超出画布时可以导出空印花效果,但应提示用户确认或标记风险。
- 非法尺寸输入应阻止提交并提示。
- 角度可以归一化显示。
## 17. 测试建议
核心测试:
- 未旋转印花合成位置正确。
- 缩放后导出尺寸正确。
- 旋转 90 度后中心不偏移。
- 旋转 -8 度后预览和导出大体一致。
- 印花部分超出画布时不会报错。
- 透明 PNG 不出现黑底或白底。
- 预览缩放到 50% 或 200% 后,拖动仍能得到正确原图坐标。
- 参数面板输入后预览同步。
- 预览拖动后参数面板同步。