14 KiB
图像编辑器设计
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 的透明区域不参与印花主色判断。