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.
This commit is contained in:
@@ -17,13 +17,14 @@
|
||||
| `image.default_extract_frames()` | 取得当前 Empty 默认应处理的帧数。 |
|
||||
| `image.source_start_frame()` | 从源 Image Empty 取得 Job 应处理的起始帧。 |
|
||||
| `image.prepare_image_input()` | 将 Packed、Dirty、Sequence 等输入准备为 Job 可读路径,并返回临时标记。 |
|
||||
| `image.prepare_selection_input()` | 将 `SelectionMask` 裁切为带 Mask Alpha 的临时 RGBA Job 输入。 |
|
||||
| `image.cleanup_image_input()` | 按临时标记清理由 `prepare_image_input()` 创建的文件或目录。 |
|
||||
| `image.prepare_image_bounds_input()` | 复用调用方已读取的 RGBA,按几何 bounds 写出保留原始 Alpha 的矩形 Job 输入。 |
|
||||
| `image.cleanup_image_input()` | 清理由图片输入 helper 创建的临时文件或目录。 |
|
||||
| `image.image_base_name()` | 去除图片或视频扩展名和 Blender 数字后缀,得到数据块基础名。 |
|
||||
| `image.depth_data_name()` | 生成 Depth 结果数据块文件名。 |
|
||||
| `image.color_data_name()` | 生成 Color 结果数据块文件名。 |
|
||||
| `image.load_color_result_image()` | 加载、命名、Pack 并配置 Job 的 Color 或 Sequence 结果;不修改 Empty。 |
|
||||
| `image.load_normal_result_image()` | 加载、命名、设为 Non-Color 并 Pack Normal Map。 |
|
||||
| `image.load_packed_rgba_image()` | 加载已有 RGBA 文件,继承源图颜色空间和 Alpha mode,命名并 Pack。 |
|
||||
| `image.create_pixel_result_image()` | 从本地编辑后的 RGBA 像素创建 Packed Image;不修改 Empty。 |
|
||||
| `image.replace_empty_image()` | 执行 Image Empty 结果替换、共享引用分流、旧数据清理、帧设置继承和可选画布对齐。 |
|
||||
| `image.image_empty_bounds()` | 计算 Image Empty 在对象局部坐标中的实际显示边界。 |
|
||||
@@ -32,12 +33,14 @@
|
||||
|
||||
这组公共能力使用固定术语:
|
||||
|
||||
- Selection 是用户在图片空间圈定的业务范围;`SelectionPath` 保存路径点、反选状态和填充规则。
|
||||
- `SelectionMask` 是 Selection 的栅格化结果,只包含局部像素权重 `values` 和源图 `bounds`。
|
||||
- Selection 是用户在图片空间圈定的业务范围;`SelectionPath` 保存路径点和反选状态,并提供完整值的 JSON 编解码。
|
||||
- `SelectionMask` 是 Selection 的纯几何栅格化结果,只包含局部像素权重 `values` 和源图几何 `bounds`。
|
||||
- Mask 表示二维像素权重或 Alpha 运算。
|
||||
- Blender 对象 Selection 由上下文明确区分;图片选区细化动作称为 Refine Selection。
|
||||
|
||||
`viewport.py` 处理 View3D 中的点击、坐标投影、`ImageSelectionGesture`、工具设置和 Overlay 绘制;`selection.py` 负责图片空间 Selection 的值、栅格化和 Alpha 裁切。
|
||||
`viewport.py` 处理 View3D 中的点击、坐标投影、`ImageSelectionGesture`、工具设置和 Overlay 绘制;手势完成时只把内存中的 `SelectionPath` 交给实际工具。`selection.py` 负责图片空间 Selection 的值、栅格化和 Alpha 裁切;`SelectionPath → SelectionMask` 只在图片编辑或 Shape 生成阶段执行。
|
||||
|
||||
所有 Path 使用 non-zero winding 填充,同方向重复绕行的重叠区域保持选中。栅格器按路径边与像素中心扫描行的交点累计 winding:非反选只建立 Path Bounds 的局部画布,抗锯齿时增加 2 px 边缘;反选使用完整图片范围。栅格阶段不接收图片 Alpha 或颜色,内容相交、空内容和内容 bounds 由 Image Tool 或 Cutout 业务处理。
|
||||
|
||||
### Viewport 交互
|
||||
|
||||
@@ -62,7 +65,7 @@
|
||||
| 函数 | 职责 |
|
||||
| --- | --- |
|
||||
| `selection.selection_path_bounds()` | 校验 Selection Path 并返回整数像素包围框。 |
|
||||
| `selection.rasterize_selection_path()` | 把 `SelectionPath` 栅格化为 `SelectionMask`,并可与源 Alpha 相交。 |
|
||||
| `selection.rasterize_selection_path()` | 以局部 non-zero scanline 把 `SelectionPath` 栅格化为纯几何 `SelectionMask`。 |
|
||||
| `selection.crop_pixels_to_alpha()` | 按 Alpha 非零区域紧裁切 RGBA 像素。 |
|
||||
|
||||
## Depth 产物
|
||||
|
||||
@@ -38,4 +38,4 @@ flowchart TD
|
||||
|
||||
落点仍在 `actions.py` 中完成:3D View 创建 Reference 或 Plane,Node Editor 创建与当前节点树匹配的 Image 节点,绘制模式把 Image 配置为当前画笔的 Texture。鼠标位置和视图位置也在这里换算。
|
||||
|
||||
Plane 路径调用 `convert_to_plane/mesh_plane.py` 创建基础网格,并从节点资产加载 `O Mesh Plane`。`actions.py` 创建 Blender 数据。
|
||||
Plane 路径调用 `convert_to_plane/mesh_plane.py` 创建中心原点基础网格,并从节点资产加载 `O Mesh Plane`。`actions.py` 创建图像材质并放入 Mesh 第 `0` 个材质槽,节点组只生成几何。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Plane 转换
|
||||
|
||||
Plane 转换从当前 Image Empty 创建普通 Plane 或 Depth Plane:普通 Plane 同步完成,Depth Plane 先取得 MoGe-2 Z Depth 文件再回到 Blender 创建对象。
|
||||
Plane 转换从当前 Image Empty 创建普通 Plane 或 Depth Plane:普通 Plane 同步完成,Depth Plane 先取得 MoGe-2 Z Depth 与 Tangent Normal 文件,再回到 Blender 创建对象。
|
||||
|
||||
## 执行过程
|
||||
|
||||
@@ -10,12 +10,12 @@ flowchart TD
|
||||
Kind -- "Plane" --> Color["复制 Color Image"]
|
||||
Color --> Plane["创建 Plane Object"]
|
||||
Kind -- "Depth Plane" --> Job["generate-depth-plane-geometry"]
|
||||
Job --> Result["Color Image + z-depth.exr + depth.json"]
|
||||
Result --> Import["直接加载并 Pack Depth Image"]
|
||||
Job --> Result["Color Image + z-depth.exr + depth.json + tangent-normal.png"]
|
||||
Result --> Import["加载并 Pack Depth / Normal Image"]
|
||||
Import --> Depth["创建 Depth Plane Object"]
|
||||
```
|
||||
|
||||
两条路径共用 Image Empty 的显示范围、颜色材质和基础 Plane。Depth Plane 通过一次 MoGe-2 Server Job 生成 Z Depth,再由节点根据相机元数据转换为局部位移。
|
||||
两条路径共用 Image Empty 的显示范围、颜色材质和中心原点 Plane。Depth Plane 通过一次 MoGe-2 Server Job 同时生成 Z Depth 与 Tangent Normal;节点根据相机元数据生成单面地形体块,Normal Map 则进入材质着色。
|
||||
|
||||
## 节点说明
|
||||
|
||||
@@ -25,18 +25,32 @@ flowchart TD
|
||||
|
||||
### 创建普通 Plane
|
||||
|
||||
`conversion.py` 先调用 `object.create_color_image()` 复制当前 Image,再由 `object.create_plane_object()` 创建对象。`object.py` 使用 `mesh_plane.py` 生成与 Image Empty 显示范围一致的基础四边形,并通过 `nodes.py` 加载 `O Mesh Plane` 修改器。
|
||||
`conversion.py` 先调用 `object.create_color_image()` 复制当前 Image,再由 `object.create_plane_object()` 创建对象。`mesh_plane.py` 把基础四边形顶点放在局部原点两侧,并把 Image Empty 显示范围中心补入对象矩阵,因此世界空间位置不变而对象原点位于面中心。随后从节点资产加载 `O Mesh Plane` 修改器。
|
||||
|
||||
Movie 或 Sequence 的 Image User 设置会保留。转换完成后,新 Plane 替换源 Empty,并沿用原对象名称。
|
||||
材质存入 Mesh 第 `0` 个材质槽。`O Mesh Plane` 将带匿名标记的输入几何与生成几何临时合并,统一设置材质索引 `0` 后再删除输入几何,从而让纯生成面保留原 Mesh 的材质集合。Movie 或 Sequence 的 Image User 设置会保留。转换完成后,新 Plane 替换源 Empty,并沿用原对象名称。
|
||||
|
||||
### 生成 Z Depth
|
||||
### 生成 Depth 与 Normal
|
||||
|
||||
Depth 路径中的 `GenerateDepthPlane` 位于 `conversion.py`。它从 Preferences 读取 **Depth & Normal Model**、**MoGe Resolution Level** 和 **Maximum AI Input Size**,提交 `generate-depth-plane-geometry` Job。
|
||||
|
||||
`server/jobs/depth_plane.py` 编排任务,`server/geometry/prediction_artifacts.py` 把图片合成到黑底后调用 MoGe-2,并从 `GeometryFrame` 写出 `z-depth.exr` 与 `depth.json`。EXR 的 RGB 重复保存完整相机 Z,Alpha 保存模型 Validity;JSON 保存图片尺寸、相机内参与有效区域的参考深度。AI 输入上限只影响模型分析图,颜色副本仍保留原始分辨率。
|
||||
`server/jobs/depth_plane.py` 编排完整图片任务,`server/geometry/prediction_artifacts.py` 把图片合成到黑底后调用一次 MoGe-2,并从同一个 `GeometryFrame` 写出 `z-depth.exr`、`depth.json` 与 `tangent-normal.png`。该 Artifact 生成层也由 Cutout Job 复用;两种顶层 Job 继续各自负责完整图片与 Selection 的输入、结果协议。
|
||||
|
||||
EXR 的 RGB 重复保存完整相机 Z,Alpha 保存模型 Validity;JSON 保存图片尺寸、相机内参与有效区域的参考深度。Tangent Normal 使用 PNG 保存,只参与材质着色。AI 输入上限只影响模型分析图,颜色副本仍保留原始分辨率。
|
||||
|
||||
### 创建 Depth Plane
|
||||
|
||||
Job 返回后,`object.create_depth_plane_from_result()` 直接加载并 Pack `z-depth.exr`,数据块仍由 `depth_data_name()` 命名。`depth.json` 用于计算统一尺度,`O Image Depth Plane` 根据参考深度、统一尺度和用户的 Depth Amount 把相机 Z 转换为局部位移。
|
||||
Job 返回后,`object.create_depth_plane_from_result()` 加载并 Pack `z-depth.exr` 与 `tangent-normal.png`。Depth 数据块仍由 `depth_data_name()` 命名;Normal 图片设为 Non-Color,并以 Tangent Space 接入 `O Image Layer`。结果材质与 Cutout 一样使用 Principled IOR `1.2`。`depth.json` 的参考深度乘以统一尺度后初始化 **Base Plane Depth**,使它和采样深度处于同一局部距离单位。
|
||||
|
||||
随后 `object.py` 创建颜色材质和基础四边形,`nodes.py` 加载 `O Image Depth Plane` 修改器。`conversion.py` 最后用一个 Undo 步骤替换源 Empty。
|
||||
`O Image Depth Plane` 把 `O Mesh Plane` 的固定底面放在原图片平面,`Thickness` 只沿正法线移动可变面与中间环,再按各层位置应用深度位移:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Depth["Z Depth × Uniform Scale"] --> Offset["(Base Plane Depth - Depth) × Depth Scale"]
|
||||
Offset --> Clamp["负向位移限制在固定底面之前"]
|
||||
Block["O Mesh Plane 厚度体块"] --> Weight["固定底面 0 → 中间环 → 可变面 1"]
|
||||
Clamp --> Apply["位移 × 层权重"]
|
||||
Weight --> Apply
|
||||
Apply --> Terrain["单面地形体块"]
|
||||
```
|
||||
|
||||
固定底面位于原图片平面,未置换可变面位于正法线方向的 **Thickness** 距离处。**Depth Scale** 为 `0` 时成品厚度等于 **Thickness**;正向位移可以超出基础厚度,负向位移不会穿过固定底面。`conversion.py` 最后用一个 Undo 步骤替换源 Empty。
|
||||
|
||||
@@ -6,36 +6,40 @@ Cutout 从图片上的一个 Selection 开始,可按需执行 Refine Selection
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Select["圈定 Selection,选择 Shape"]
|
||||
Raster["栅格化 Selection"]
|
||||
Select["圈定 SelectionPath"]
|
||||
Menu["打开 Shape Pie Menu"]
|
||||
Shape["选择 Shape"]
|
||||
Raster["SelectionPath → 几何 SelectionMask"]
|
||||
Route{"需要后台生成数据?"}
|
||||
Local["沿用本地 SelectionMask"]
|
||||
Local["读取局部 RGBA<br/>源 Alpha × Selection 值"]
|
||||
Bounds["写出几何 bounds 矩形 RGBA"]
|
||||
Job["执行 Refine Selection,生成 Depth / Normal 产物"]
|
||||
Ready["汇总 Shape 输入"]
|
||||
Encoded["加载并 Pack 输入或 Refine RGBA"]
|
||||
Ready["汇总内容 Mask 与 Shape 输入"]
|
||||
Build["构建 BaseShape"]
|
||||
Create["创建 Blender 对象"]
|
||||
|
||||
Select --> Raster --> Route
|
||||
Select --> Menu --> Shape --> Raster --> Route
|
||||
Route -- "否" --> Local --> Ready
|
||||
Route -- "是" --> Job --> Ready
|
||||
Route -- "是" --> Bounds --> Job --> Encoded --> Ready
|
||||
Ready --> Build --> Create
|
||||
```
|
||||
|
||||
Surface 与 Balloon 直接使用 `SelectionMask` 构建 `BaseShape`;Refine Selection、Depth Shape 和 Normal Map 先通过 Server Job 生成所需产物。
|
||||
Surface 与 Balloon 在本地组合源 Alpha 与几何 Selection 后构建 `BaseShape`;Refine Selection、Depth Shape 和 Normal Map 先通过同一个矩形图片协议生成所需产物。
|
||||
|
||||
## 节点说明
|
||||
|
||||
### 圈定 Selection,选择 Shape
|
||||
|
||||
Cutout 工具由 `__init__.py` 注册,交互从 `main.py` 中的 `SelectCutoutSelection` 开始。它接收 Lasso 或双击整图手势,把图片空间路径保存为 `SelectionPath`,然后调用 `interaction.py` 打开 Shape 饼菜单。
|
||||
Cutout 工具由 `__init__.py` 注册,交互从 `main.py` 中的 `SelectCutoutSelection` 开始。它接收 Lasso 或双击整图手势,只构建内存中的 `SelectionPath`,然后调用 `interaction.py` 打开 Shape 饼菜单。此时不读取源图像素、不栅格化 Mask,也不扫描可见 Alpha。
|
||||
|
||||
`interaction.py` 完成 Selection 有效性检查并读取三个工具选项。**Mesh Detail** 的 Low、Medium、High、Ultra 分别映射到 32、16、8、4 px 的采样间距,再换算期望网格分辨率;档位越高,采样越密。菜单中的 Surface、Balloon、Depth Balloon 和 Depth Surface,以及它们对应的节点分支索引,统一来自 `shape.py`。
|
||||
`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` 接过主线。它使用 `common/selection.py` 把 `SelectionPath` 栅格化为 `SelectionMask`:`values` 保存局部像素权重,`bounds` 保存它在源图中的位置。套索外和源图透明区域不会进入最终 Selection。
|
||||
用户选定 Shape 后,`main.py` 中的 `CutoutSelectionToShape` 接过主线。它从自身 Property 恢复 `SelectionPath`,再使用 `common/selection.py` 以 non-zero winding 栅格化为一次纯几何 `SelectionMask`:非反选只处理 Path Bounds 与 2 px 抗锯齿边缘,反选覆盖完整图片范围。`values` 保存局部几何权重,`bounds` 保存它在源图中的位置;栅格阶段不读取、接收或合并源 Alpha。
|
||||
|
||||
此时也会应用 **Maximum Cutout Mesh Resolution**,统一限制四种 Shape 裁切区域的最长采样边。
|
||||
实际 Cutout 业务随后读取一次源 RGBA。无需 AI 时,它在几何 bounds 内显式计算 `source_alpha × selection_values`,由该内容 Mask 判断空内容并约束轮廓;原始 `SelectionMask` 不被修改。此时也会应用 **Maximum Cutout Mesh Resolution**,统一限制四种 Shape 几何范围的最长采样边。
|
||||
|
||||
### 需要后台生成数据?
|
||||
|
||||
@@ -45,11 +49,11 @@ Cutout 工具由 `__init__.py` 注册,交互从 `main.py` 中的 `SelectCutout
|
||||
- 选择 Depth Balloon 或 Depth Surface;
|
||||
- 开启 **Normal Map**。
|
||||
|
||||
Surface 和 Balloon 的本地路径直接沿用 `SelectionMask`。
|
||||
Surface 和 Balloon 的本地路径直接使用本地内容 Mask。
|
||||
|
||||
### 执行 Refine Selection,生成 Depth / Normal 产物
|
||||
|
||||
`main.py` 把请求提交给 `server/jobs/cutout.py`,生成本次 Shape 需要的产物:
|
||||
`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`;
|
||||
@@ -59,11 +63,13 @@ Depth 与 Normal Map 共享同一个 MoGe-2 `GeometryFrame`。模型输入先按
|
||||
|
||||
**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 主线程读取可选的 SelectionMask,加载并 Pack Depth 与 Normal 图片。`main.create_cutout_shape()` 调用 `geometry.py` 中的 `build_base_shape()`:
|
||||
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. 从最终 `SelectionMask` 提取外轮廓和孔洞;
|
||||
1. 从内容 Alpha 值提取外轮廓和孔洞;
|
||||
2. 按 Mesh Detail 对应的采样间距生成轮廓与内部采样点;
|
||||
3. 进行约束三角化;
|
||||
4. 计算四种 Shape 共用的 `o_balloon`;
|
||||
@@ -73,7 +79,7 @@ Job 完成后,`main.py` 在 Blender 主线程读取可选的 SelectionMask,
|
||||
|
||||
### 创建 Blender 对象
|
||||
|
||||
`main.create_cutout_shape()` 把 `BaseShape` 和可选产物交给 `object.py` 的 `create_shape_object()`。这里创建裁切后的 Color Image、Material、Mesh Object,并挂上统一的 `O Image Cutout` Modifier。材质通过 `common/material.py` 创建,Cutout 使用 `1.2` 的 IOR。Color Image 按最终 Selection 包围矩形裁切源图并保留 RGBA。
|
||||
`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 如何形成结果 | 必需后台产物 |
|
||||
| --- | --- | --- |
|
||||
@@ -82,7 +88,7 @@ Job 完成后,`main.py` 在 Blender 主线程读取可选的 SelectionMask,
|
||||
| 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` 的标定函数,根据最终 Selection Alpha 计算。透明像素达到 `5%` 时使用有效 Selection 自适应标定,否则使用稳定的固定基准。
|
||||
Depth Shape 的 Base Plane Depth,以及 Depth Balloon 的 Direction,都由 `object.py` 调用 `geometry.py` 的标定函数,根据最终内容 Alpha 计算。透明像素达到 `5%` 时使用有效内容区域自适应标定,否则使用稳定的固定基准。
|
||||
|
||||
Normal Map 关闭时材质使用几何法线;开启时,`object.py` 把生成的图片和已经确定的 Normal Space 交给材质节点。最后 `main.py` 提交 Undo,整条 Cutout 流程结束。
|
||||
|
||||
|
||||
@@ -7,13 +7,15 @@ Image Tool 从 Box、Lasso、Line 或 Polyline 手势得到一个图片 Selectio
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Tool["Box / Lasso / Line / Polyline"] --> Gesture["采集图片空间手势"]
|
||||
Gesture --> Selection["SelectionPath → SelectionMask"]
|
||||
Gesture --> Path["构建 SelectionPath"]
|
||||
Path --> Selection["SelectionPath → 几何 SelectionMask"]
|
||||
Selection --> Refine{"Refine Selection?"}
|
||||
Refine -- "否" --> Mask["使用本地 SelectionMask"]
|
||||
Refine -- "是" --> Job["refine-image-selection"]
|
||||
Job --> Mask
|
||||
Mask --> Alpha["源 Alpha × SelectionMask"]
|
||||
Alpha --> Crop["紧裁切并写回 Empty"]
|
||||
Refine -- "否" --> Alpha["读取局部 RGBA<br/>源 Alpha × Selection 值"]
|
||||
Alpha --> Crop["计算内容 bounds<br/>创建并写回 Image"]
|
||||
Refine -- "是" --> Bounds["读取 RGBA<br/>写出几何 bounds 矩形"]
|
||||
Bounds --> Job["refine-image-selection"]
|
||||
Job --> Result["加载并 Pack BEN2 RGBA"]
|
||||
Result --> Replace["按响应内容 bounds 替换 Empty"]
|
||||
```
|
||||
|
||||
四个工具共用 `EditImageMask`。Box 和 Lasso 可以执行 Refine Selection;Line 与 Polyline 始终保留几何边界,直接使用本地 `SelectionMask`。
|
||||
@@ -28,18 +30,18 @@ flowchart TD
|
||||
|
||||
### SelectionPath → SelectionMask
|
||||
|
||||
`EditImageMask` 通过 `common/selection.py` 把手势整理为 `SelectionPath`,再栅格化为 `SelectionMask`。凹多边形使用三角剖分,自交路径使用 `EVEN_ODD` 填充。预览缓存只在轨迹变化时更新。
|
||||
`ImageSelectionGesture` 把手势整理为内存中的 `SelectionPath`,其中 Points 与 Invert 共同定义 Selection,并直接交给同一个 `EditImageMask` Operator,不经过 JSON。预览和最终 Mask 统一使用 non-zero winding,同方向重复绕行的重叠区域保持选中。实际图片编辑开始后,`EditImageMask` 通过 `common/selection.py` 在必要画布内把路径栅格化为一次纯几何 `SelectionMask`;这一阶段不读取或接收源图 Alpha。预览缓存只在轨迹变化时更新。
|
||||
|
||||
Box 和 Lasso 分别读取自己的 **Refine Selection** 设置。Line 与 Polyline 直接使用几何边界。
|
||||
|
||||
### Refine Selection
|
||||
|
||||
未开启 Refine Selection 时,`edit_mask.py` 直接使用本地 `SelectionMask`。开启后,`RefineImageSelection` 把带 Selection Alpha 的局部 RGBA 提交给 `server/jobs/refine_image_selection.py`;Job 调用 `server/selection/refine_selection.py` 运行 BEN2,并返回收紧后的图片和 `bounds`。
|
||||
未开启 Refine Selection 时,`edit_mask.py` 在实际编辑阶段读取 RGBA 并使用本地几何 `SelectionMask`。开启后,它按 Selection 几何 bounds 写出完整矩形 RGBA 并提交给 `server/jobs/refine_image_selection.py`;矩形内、Lasso 外的源像素与 Alpha 保持原样。Job 调用 `server/selection/refine_selection.py` 运行 BEN2,并返回按结果 Alpha 收紧后的图片和局部内容 `bounds`。
|
||||
|
||||
响应阶段仍由 `edit_mask.py` 把局部结果映射回完整画布。
|
||||
Lasso 在 Refine 中只提供矩形上下文,不再作为 BEN2 输出的硬 Mask。响应阶段把局部内容 bounds 映射回源图,并直接加载、配置和 Pack BEN2 已编码的 RGBA 后替换 Empty;不再提取输出 Alpha、重读源图或重新创建同一颜色图片。
|
||||
|
||||
### 源 Alpha × SelectionMask
|
||||
|
||||
`edit_mask.py` 保留源 RGB,只把最终 `SelectionMask` 乘入源 Alpha,再按非零 Alpha 紧裁切。像素读取、结果 Image 创建和共享引用处理使用 `common/image.py`。
|
||||
`edit_mask.py` 在 `SelectionMask.bounds` 内复用源 RGBA,只把几何 Selection 值乘入源 Alpha,再按行列可见范围计算内容 bounds 并紧裁切。几何 SelectionMask 在合并前后保持不变。像素读取、结果 Image 创建和共享引用处理使用 `common/image.py`。
|
||||
|
||||
公共 Image Empty 替换入口同步更新 `empty_display_size` 和 `empty_image_offset`,使裁切后的内容对齐原图片平面并保持对象 `matrix_world`。
|
||||
|
||||
Reference in New Issue
Block a user