Files
AnyImage/docs/internals/operators/cutout-tool.md
nan 7f6e47432e feat(selection): separate selection geometry from image content processing
- Introduced a new design for selection rasterization that decouples the selection geometry from the image content, ensuring that the SelectionMask is independent of the source image's alpha and color.
- Updated the `rasterize_selection_path` function to only accept selection paths and image dimensions, removing parameters related to source alpha and alpha thresholds.
- Implemented a new approach for handling selection paths using non-zero winding fill, ensuring overlapping areas are preserved.
- Enhanced performance by utilizing a scanline algorithm for rasterization, significantly reducing processing time for high-resolution images.
- Updated internal documentation and added comprehensive tests to validate the new behavior and performance improvements.
- Ensured backward compatibility by maintaining existing interfaces where necessary while introducing breaking changes in the selection processing logic.
2026-09-01 22:36:20 +08:00

96 lines
7.0 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.
# Cutout Tool
Cutout 从图片上的一个 Selection 开始,可按需执行 Refine Selection、生成 Depth 或 Normal Map,最后在 Blender 中创建带材质与 Modifier 的 Mesh Object。
## 执行过程
```mermaid
flowchart TD
Select["圈定 SelectionPath"]
Menu["打开 Shape Pie Menu"]
Shape["选择 Shape"]
Raster["SelectionPath → 几何 SelectionMask"]
Route{"需要后台生成数据?"}
Local["读取局部 RGBA<br/>源 Alpha × Selection 值"]
Bounds["写出几何 bounds 矩形 RGBA"]
Job["执行 Refine Selection,生成 Depth / Normal 产物"]
Encoded["加载并 Pack 输入或 Refine RGBA"]
Ready["汇总内容 Mask 与 Shape 输入"]
Build["构建 BaseShape"]
Create["创建 Blender 对象"]
Select --> Menu --> Shape --> Raster --> Route
Route -- "否" --> Local --> Ready
Route -- "是" --> Bounds --> Job --> Encoded --> Ready
Ready --> Build --> Create
```
Surface 与 Balloon 在本地组合源 Alpha 与几何 Selection 后构建 `BaseShape`;Refine Selection、Depth Shape 和 Normal Map 先通过同一个矩形图片协议生成所需产物。
## 节点说明
### 圈定 Selection,选择 Shape
Cutout 工具由 `__init__.py` 注册,交互从 `main.py` 中的 `SelectCutoutSelection` 开始。它接收 Lasso 或双击整图手势,只构建内存中的 `SelectionPath`,然后调用 `interaction.py` 打开 Shape 饼菜单。此时不读取源图像素、不栅格化 Mask,也不扫描可见 Alpha。
`interaction.py` 读取工具选项,并在构建菜单按钮时把只含 Points 与 Invert 的完整 `SelectionPath` 编码一次,将同一个 JSON 值直接设置给各 `CutoutSelectionToShape`。这次编码只用于跨 Blender Operator 传值,不属于 Selection Mask 构建。**Mesh Detail** 的 Low、Medium、High、Ultra 分别映射到 32、16、8、4 px 的采样间距,再换算期望网格分辨率;档位越高,采样越密。菜单中的 Surface、Balloon、Depth Balloon 和 Depth Surface,以及它们对应的节点分支索引,统一来自 `shape.py`。
### 栅格化 Selection
用户选定 Shape 后,`main.py` 中的 `CutoutSelectionToShape` 接过主线。它从自身 Property 恢复 `SelectionPath`,再使用 `common/selection.py` 以 non-zero winding 栅格化为一次纯几何 `SelectionMask`:非反选只处理 Path Bounds 与 2 px 抗锯齿边缘,反选覆盖完整图片范围。`values` 保存局部几何权重,`bounds` 保存它在源图中的位置;栅格阶段不读取、接收或合并源 Alpha。
实际 Cutout 业务随后读取一次源 RGBA。无需 AI 时,它在几何 bounds 内显式计算 `source_alpha × selection_values`,由该内容 Mask 判断空内容并约束轮廓;原始 `SelectionMask` 不被修改。此时也会应用 **Maximum Cutout Mesh Resolution**,统一限制四种 Shape 几何范围的最长采样边。
### 需要后台生成数据?
只有以下情况需要提交 Job:
- 开启 **Refine Selection**;
- 选择 Depth Balloon 或 Depth Surface;
- 开启 **Normal Map**。
Surface 和 Balloon 的本地路径直接使用本地内容 Mask。
### 执行 Refine Selection,生成 Depth / Normal 产物
`main.py` 先通过 `common/image.py` 把 Selection 几何 bounds 内的原始 RGBA 写成矩形输入。该文件保留源 Alpha,不预乘 Lasso 值;文件、Packed 或内存图片都使用同一个入口。随后把请求提交给 `server/jobs/cutout.py`,生成本次 Shape 需要的产物:
- **Refine Selection** 调用 `server/selection/refine_selection.py`,写出收紧后的 `selection.png` 和 `selection_bounds.json`;
- 需要 Vector Depth 时写出相机空间 XYZ 组成的 `vector-depth.exr`,需要 Z Depth 时写出 `z-depth.exr`,两者都配套 `depth.json`;
- **Normal Map** 根据既定空间写出 `tangent-normal.png` 或 `object-normal.png`。
Depth 与 Normal Map 共享同一个 MoGe-2 `GeometryFrame`。模型输入先按 Alpha 合成到黑底;Vector/Z Depth 的 RGB 保留完整连续 Field,Alpha 保存模型 Validity。Depth Surface 的 Normal Map 编码为 Cutout 对象空间,其正面是对象局部 `-X`;其余 Shape 使用 Tangent Space。`main.normal_mode_for_shape()` 根据 Shape 确定 Normal Space。
**Maximum AI Input Size** 约束 BEN2 和 MoGe-2 的分析图长边;网格密度使用 **Maximum Cutout Mesh Resolution**,最终颜色图使用源图裁切分辨率。
Refine 把 Lasso 视为矩形上下文,因此 BEN2 可以保留 bounds 内、Lasso 外的内容。启用 Refine 时,最终内容 Alpha 完全来自 BEN2;只生成 Depth 或 Normal 时,最终几何仍以本地几何 Selection 值乘矩形输入 Alpha 约束。
### 构建 BaseShape
Job 完成后,`main.py` 在 Blender 主线程加载并 Pack 可选的 Depth 与 Normal 图片,并选取一份已有编码 RGBA:启用 Refine 时使用 BEN2 输出;只生成 Depth 或 Normal 时使用提交前的矩形输入。它从同一局部 Image 读取 Alpha 构建内容 Mask,并把这个 Image 直接交给颜色材质。`main.create_cutout_shape()` 调用 `geometry.py` 中的 `build_base_shape()`:
1. 从内容 Alpha 值提取外轮廓和孔洞;
2. 按 Mesh Detail 对应的采样间距生成轮廓与内部采样点;
3. 进行约束三角化;
4. 计算四种 Shape 共用的 `o_balloon`;
5. 汇总顶点、三角面、UV 和 Balloon 权重,得到内存中的 `BaseShape`。
`geometry.py` 还提供 Depth 标定、采样和网格尺度计算,供后面的对象创建阶段使用。
### 创建 Blender 对象
`main.create_cutout_shape()` 把 `BaseShape`、颜色 Image 和可选产物交给 `object.py` 的 `create_shape_object()`。这里创建 Material、Mesh Object,并挂上统一的 `O Image Cutout` Modifier。材质通过 `common/material.py` 创建,Cutout 使用 `1.2` 的 IOR。本地 Cutout 创建一次几何 bounds 颜色图;AI Cutout 直接复用已编码并 Pack 的矩形输入或 BEN2 输出,不再重读完整源图并重新编码同一颜色内容。
| Shape | Modifier 如何形成结果 | 必需后台产物 |
| --- | --- | --- |
| Surface | 保持基础平面轮廓,可增加真实厚度 | 无 |
| Balloon | 使用 `o_balloon` 从边缘向内部鼓起 | 无 |
| Depth Balloon | 在 Balloon 上叠加相对基准面的深度 | `z-depth.exr`、`depth.json` |
| Depth Surface | 使用相机点 XYZ 重建透视表面 | `vector-depth.exr`、`depth.json` |
Depth Shape 的 Base Plane Depth,以及 Depth Balloon 的 Direction,都由 `object.py` 调用 `geometry.py` 的标定函数,根据最终内容 Alpha 计算。透明像素达到 `5%` 时使用有效内容区域自适应标定,否则使用稳定的固定基准。
Normal Map 关闭时材质使用几何法线;开启时,`object.py` 把生成的图片和已经确定的 Normal Space 交给材质节点。最后 `main.py` 提交 Undo,整条 Cutout 流程结束。
Balloon 与 Depth Balloon 的材质会创建 Geometry Attribute 节点,把 `o_balloon` 的 Float 输出直接连接到 `O Image Layer` 已有的 **Normal Scale**;Surface 与 Depth Surface 不建立这条连接。