Files
AnyImage/openspec/changes/replace-crop-with-mask-and-rectify/design.md
nan 1994d38ccd Add comprehensive tests for image processing and mask functionalities
- Introduced tests for color image handling, ensuring source preservation and premultiplied alpha usage.
- Implemented tests for brush stroke operations, validating segment boundaries and brush anti-aliasing.
- Added tests for frame anti-aliasing, confirming correct rendering behavior with various alpha values and depth settings.
- Created tests for frame projection, ensuring accurate overlap calculations and half-plane clipping.
- Developed tests for image premultiplied retention, verifying behavior during loading and packing processes.
- Added undo functionality tests for mask operations, ensuring single transaction behavior during edits.
2026-09-05 21:33:11 +08:00

105 lines
8.5 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.
## Context
当前 `operators/crop_tool/` 同时承载 Frame、Lasso、Polyline 与 Perspective。Lasso 和 Polyline 把一次几何 Selection 乘入源 Alpha,随后按可见内容紧裁切并调整 Image Empty 画框;Lasso 还可以提交 BEN2 Refine Job。三个 Crop 子工具分别保存 Keep Original,公共 `CropSelectionGesture` 因而混合了工具设置、Lasso/Polyline 交互和 Cutout 复用职责。
新交互把 Alpha 视为可反复编辑的当前状态:Mask 用 Lasso 或 Brush 生成一次手势 Mask,再以 Set、Add 或 Subtract 合成 Alpha。Frame、Mask、Rectify 是同一个简短的 Image Edit 工具组。当前工作区的 `standardize-cutout-z-axis` 同时修改 Cutout 主链,实施本 change 时应以该 change 完成后的 Cutout 结构为基线。
## Goals / Non-Goals
**Goals:**
- 用 **Frame / Mask / Rectify** 表达三个工具的真实产物,并删除全部旧 Crop 工具标识。
- 让一个 Mask WorkSpaceTool 和一个业务 Operator 承载 Lasso、Brush 及统一 Alpha 合成。
- 让 Mask 的图片尺寸、Image Empty 身份和矩形画框在每次编辑后保持不变。
- 删除 Crop/Cutout Refine Selection 与 Crop Keep Original 的完整参数和执行路径。
- 保留 Rectify 已有的四点透视校正、宽高比、输出限制和 Alpha trim 行为。
**Non-Goals:**
- 不为 Mask 维护独立 Blender Mask、Selection 历史或隐藏的原始 Alpha 副本。
- 不让 Cutout 使用 Brush、Set/Add/Subtract,Cutout 继续以单次 Lasso 定义 Shape 范围。
- 不改变 Frame 合成、Rectify Homography、Cutout Shape、Depth、Normal Map 或 Remove Background。
- 不为已删除的类、Operator ID、Tool ID、属性或 Job 保留兼容层。
## Decisions
### 1. 用 Image Edit 模块承载 Frame、Mask、Rectify
将 `operators/crop_tool/` 改为与业务边界一致的 `operators/image_edit_tool/`,内部文件使用 `frame.py`、`mask.py` 和 `rectify.py`。注册顺序固定为 Frame、Mask、Rectify,Frame 继续作为工具组首项;Cutout 保持为相邻的独立工具。
公开名称和 ID 只保留当前术语:`MaskTool`、`EditImageAlpha`、`RectifyTool`、`RectifyImagePerspective`,以及 `anyimage.mask`、`anyimage.edit_image_alpha`、`anyimage.rectify`、`anyimage.rectify_image_perspective`。激活入口改为 Image Edit 语义并默认选择 Frame。
不选择继续使用 Crop 模块并只修改标签,因为源码边界、状态属性和错误信息仍会表达已经不存在的裁切模型。
### 2. 一个 Mask Tool 读取 Gesture、Mode 与 Radius
Scene 设置保存 `mask_gesture`、`mask_mode` 和 `mask_radius`:Gesture 为 `LASSO` 或 `BRUSH`,Mode 为 `SET`、`ADD` 或 `SUBTRACT`,Radius 是 Brush 使用的正整数屏幕像素,默认 25 px。Tool Settings 以展开的三个图标显示 Mode;Radius 仅在 Brush 时显示。
Mask 只有一个 WorkSpaceTool 和一个 Operator。Operator 在 invoke 时冻结三个设置;Lasso 收集并闭合自由手绘路径,Brush 收集连续笔画并显示当前 Radius 圆环。单击 Brush 生成一个圆形印记,拖动时相邻采样之间生成连续覆盖,不因鼠标事件间距留下空洞。两种手势最终都只产生一个 `SelectionMask` 并进入同一个 Alpha 合成入口。
不选择两个 WorkSpaceTool,因为它们只有手势采集不同,分开后会重复模式、结果和设置逻辑,也不符合用户在当前 Tool 内切换 Gesture 的预期。
### 3. 直接以当前 Alpha 和本次手势 Mask 合成
设当前像素 Alpha 为 `A`,本次带边缘权重的手势 Mask 为 `M`:
```text
Set A' = A × M
Add A' = max(A, M)
Subtract A' = A × (1 - M)
```
所有运算逐像素限制在 `0–1`,RGB 原样复制。Set 保留本次手势内的当前可见内容;Add 可以重新提高已擦除区域的 Alpha;Subtract 降低本次手势覆盖区域的 Alpha。因为不保存初始 Alpha,Add 以本次 Mask 权重为恢复值,可能高于图片导入时的 Alpha;这是直接编辑当前 Alpha 的明确语义。
不选择保存隐藏的原始 Alpha,因为它会引入跨图片替换的额外状态、生命周期和同步规则,也会让 Add 不再是直接的 Alpha 涂抹。
### 4. Mask 始终替换同尺寸图片而不改变画框
Mask 读取完整当前 RGBA,应用 Alpha 合成后创建相同宽高的 packed Image,并用公共图片替换入口赋给 active Image Empty。它不传 `placement_bounds`,因此对象 identity、matrix、display size 和 image offset 不变;共享旧 Image 的其他对象继续引用旧数据。即使结果 Alpha 全部为零也允许完成,仍作为一个 Undo 步骤。
删除 Mask 的紧裁切和空 Alpha 拒绝逻辑。Rectify 仍可按自身业务生成不同尺寸并通过 placement bounds 对齐,因此“稳定画框”只属于 Mask。
### 5. Brush Radius 使用 Viewport 屏幕像素
Radius 与 Blender Circle Select 一样以 Region 屏幕像素表达,使圆形预览与用户看到的范围一致,不受图片分辨率影响。Brush 将屏幕笔画分解为相邻线段的圆角胶囊和端点圆形,在图片平面投影后合并局部 Mask;图片外覆盖被裁掉。实现复用 SelectionMask 的 bounds/value 表示和公共抗锯齿规则,不引入 Pillow、OpenCV 或几何依赖。
不选择图片像素 Radius,因为同一设置会随图片分辨率和 Viewport 缩放产生明显不同的屏幕笔刷尺寸。
### 6. Rectify 只更换业务名称并删除 Keep Original
Perspective 的四点采集、凸 Quad 规范化、512 px 预览、目标宽高比、分块 Homography、8192 px 长边限制、总像素限制、可见 Alpha trim 和结果 placement 保持不变。类、ID、状态文本、warning、Undo 名称、文档和测试统一改为 Rectify;完成后直接替换 active Image Empty,不再复制源对象。
不选择 `Perspective`,因为它只描述概念;`Rectify` 用一个单词表达把透视四边形校正为矩形的动作,并与 Frame、Mask 的长度一致。
### 7. 完整移除 Refine Selection 和 Keep Original 路径
Mask 删除 BEN2 属性、AI readiness、临时 bounds 输入、SelectionMask sidecar、异步响应 Operator 和 `refine-image-selection` Job。Cutout 删除对应工具设置、Pie 参数、Operator 参数和 `refine_model` 请求;需要 Depth 或 Normal 时继续提交原始 Selection bounds 矩形,颜色图直接复用该输入。
Server Cutout Job 不再导入或执行 refine,删除专用 Job 注册、Job 文件和只为该能力存在的 `server/selection/` 封装。BEN2 模型、缓存、Remove Background 与 Debug 保留。
删除三个旧 Crop Keep Original 属性和 Operator 参数。`place_empty_image_result()` 若不再有调用方则删除,由 Mask 和 Rectify 直接复用 `replace_empty_image()`;不保留复制结果对象的公共胶水代码。
## Risks / Trade-offs
- [Add 会让原本透明的像素变得可见] → 文档和公式明确它直接涂抹当前 Alpha;RGB 始终保留,Undo 提供操作级恢复。
- [屏幕圆形投影到倾斜图片后不再是图片像素圆形] → 以 Viewport 视觉范围为准,并用投影后的胶囊覆盖测试倾斜与缩放图片。
- [快速拖动产生断裂笔画] → 以相邻事件线段生成连续胶囊,而不是只绘制离散圆点。
- [模块和 ID 全量改名遗漏注册或注销] → 参数化检查类导出、工具顺序、Operator ID、状态文本、打包内容和逆序注销。
- [删除 Refine 时误删 BEN2 公共能力] → 测试固定 Remove Background、BEN2 Debug、模型目录和打包内容仍存在。
- [与 Cutout Z 轴 change 修改同一文件] → 先完成 `standardize-cutout-z-axis`,再基于其最终 `main.py`、测试和文档移除 Refine 分支。
## Migration Plan
1. 先完成并验证 `standardize-cutout-z-axis`,冻结本 change 的 Cutout 基线。
2. 将 Crop 模块与公共命名迁移为 Image Edit,注册 Frame / Mask / Rectify 并删除 Polyline 和旧 ID。
3. 实现 Mask 设置、Lasso/Brush 手势、统一 Alpha 合成和稳定画布结果,删除 Keep Original。
4. 将 Perspective 完整改名为 Rectify,保留 Homography 行为并删除 Keep Original。
5. 删除 Blender 与 Server 两侧 Refine Selection 路径,验证 Cutout 的本地、Depth 和 Normal 分支。
6. 更新测试与当前文档并运行完整测试;不构建节点资产、文档站点或发布包。
回退必须整体恢复旧 Crop 模块、工具注册、属性、Refine Job 和文档,避免 Blender 请求已删除的 Server Job 或注册不存在的 Tool ID。
## Open Questions
无。