Files
AnyImage/docs/internals/operators/cutout-tool.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.2 KiB
Raw Blame History

Cutout Tool

Cutout 从图片上的一个 Selection 开始,可按需生成 Depth 或 Normal Map,最后在 Blender 中创建带材质与 Modifier 的 Mesh Object。

执行过程

flowchart TD
    Select["圈定 SelectionPath"]
    Menu["打开 Shape Pie Menu"]
    Shape["选择 Shape"]
    Raster["SelectionPath → 几何 SelectionMask"]
    Route{"需要后台生成数据?"}
    Local["读取局部 RGBA<br/>源 Alpha × Selection 值"]
    Bounds["写出几何 bounds 矩形 RGBA"]
    Job["生成 Depth / Normal 产物"]
    Encoded["加载并 Pack 输入 RGBA"]
    Ready["贴图 Alpha × Selection Mask"]
    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;Depth Shape 和 Normal Map 先通过同一个矩形图片协议生成所需产物。

节点说明

圈定 Selection,选择 Shape

Cutout 工具由 __init__.py 注册,交互从 main.py 中的 SelectCutoutSelection 开始。它接收 Lasso 或双击整图手势,只构建内存中的 SelectionPath,然后调用 interaction.py 打开 Shape 饼菜单。此时不读取源图像素、不栅格化 Mask,也不扫描可见 Alpha。

对象命中由 Blender 原生点选决定。普通点击另一对象时只切换 active;空白单击保留当前 active Image Empty。Lasso 可从透明区域或 Viewport 空白开始拖动,双击也可从空命中位置打开整图 Shape 菜单。起始点不判断图片范围或 Alpha,完整 Selection 的几何与内容有效性在后续栅格化和 Shape 构建阶段判断。

interaction.py 读取工具选项,并在构建菜单按钮时把只含 Points 的完整 SelectionPath 编码一次,同时把当前 Inward Axis 固定到各 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 在 Path Bounds 与 2 px 抗锯齿边缘内栅格化为一次纯几何 SelectionMask。values 保存局部几何权重,bounds 保存它在源图中的位置;栅格阶段不读取、接收或合并源 Alpha。

实际 Cutout 业务随后读取一次源 RGBA。无需 AI 时,它在几何 bounds 内显式计算 source_alpha × selection_values,由该内容 Mask 判断空内容并约束轮廓;原始 SelectionMask 不被修改。此时按 Selection bounds 长边 ÷ Mesh Detail 像素间距 计算实际采样长边;只有它超过 Maximum Cutout Mesh Resolution 时才等比增大有效间距。bounds 的原始像素长边本身不会触发降密。

需要后台生成数据?

选择 Depth Balloon、Depth Surface 或开启 Normal Map 时需要提交 Job。

Surface 和 Balloon 的本地路径直接使用本地内容 Mask。

生成 Depth / Normal 产物

main.py 先通过 common/image.py 把 Selection 几何 bounds 内的原始 RGBA 写成矩形输入。该文件保留源 Alpha,不预乘 Lasso 值;文件、Packed 或内存图片都使用同一个入口。随后把请求提交给 server/jobs/cutout.py,生成本次 Shape 需要的产物:

  • 需要 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 原生 XY / Z 对象空间,正面中性法线为局部 +Z;其余 Shape 使用 Tangent Space。main.normal_mode_for_shape() 根据 Shape 确定 Normal Space。

Maximum AI Input Size 约束 MoGe-2 的分析图长边;网格密度使用 Maximum Cutout Mesh Resolution,最终颜色图使用 Selection bounds 分辨率。

Color 使用 Selection bounds 内的源 RGBA;Normal 的可见区域、MoGe-2 黑底输入和 Depth Metadata 的参考区域使用源 Alpha。Depth EXR 的 RGB 保存连续 Field,Alpha 保存模型 Validity。Selection Mask 只在 Blender 构建网格时与贴图 Alpha 相乘。

构建 BaseShape

Job 完成后,main.py 在 Blender 主线程加载并 Pack 可选的 Depth 与 Normal 图片,并复用提交前的矩形 RGBA 输入作为颜色图。它从同一 Image 读取 Alpha,与已栅格化的 Selection Mask 相乘后构建网格内容 Mask;颜色及 AI 贴图保持原 Alpha。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,并由 O Image Layer Color 直接驱动 Principled BSDF 的 IOR Level。本地 Cutout 创建一次几何 bounds 颜色图;AI Cutout 直接复用已编码并 Pack 的矩形输入。

Shape Modifier 如何形成结果 必需后台产物
Surface 保持基础平面轮廓,可增加真实厚度 无
Balloon 使用 o_balloon 从边缘向内部鼓起 无
Depth Balloon 在 Balloon 上叠加相对基准面的深度 z-depth.exr、depth.json
Depth Surface 使用相机点 XYZ 重建透视表面 vector-depth.exr、depth.json

Depth Shape 的 Reference Depth,以及 Depth Balloon 的 Direction,都由 object.py 调用 geometry.py 的标定函数,根据最终网格内容 Mask 计算。透明像素达到 5% 时使用有效内容区域自适应标定,否则使用稳定的固定基准。

Normal Map 关闭时材质使用几何法线;开启时,object.py 把生成的图片和已经确定的 Normal Space 交给材质节点。最后 main.py 提交 Undo,整条 Cutout 流程结束。

Inward Axis

flowchart TD
    Base["BaseShape 与四种 Shape<br/>原生 XY / Z"] --> Switch{"Inward Axis"}
    Switch -- "-Z(默认)" --> Native["节点几何直接输出<br/>对象沿用源 Empty local transform"]
    Switch -- "+X" --> Orient["O Image Cutout 执行 Z → X<br/>对象矩阵执行 X → Z"]
    Native --> World["相同世界空间结果"]
    Orient --> World
    Normal["Depth Surface<br/>Object Space Normal"] --> Mode{"Inward Axis"}
    Mode -- "-Z" --> Keep["保持原生 Z 轴通道"]
    Mode -- "+X" --> Swizzle["按 Z → X 转换一次"]

BaseShape 与 Shape 子节点始终在局部 XY 平面建模,并沿 -Z 向图片内部生成厚度和深度。统一的 O Image Cutout Modifier 只在选择 +X 时转换最终几何;object.py 同步使用逆矩阵,因此默认 -Z 与源 Image Empty 的 local transform 一致,+X 模式仍保持相同世界空间形状。Depth Surface 的 Object Space Normal 默认直接使用原生 Z 轴 Artifact,仅在 +X 模式转换通道;Tangent Space Normal 不变。

Balloon 与 Depth Balloon 的材质会创建 Geometry Attribute 节点,把 o_balloon 的 Float 输出直接连接到 O Image Layer 已有的 Normal Scale;Surface 与 Depth Surface 不建立这条连接。