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

8.5 KiB
Raw Blame History

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:

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

无。