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.
This commit is contained in:
@@ -1,44 +0,0 @@
|
||||
# Crop Tool
|
||||
|
||||
工具栏保留两个独立 AnyImage 入口:四合一 Crop Tool 组与紧接其后的 Cutout Tool。Crop Tool 依次包含 Crop Box、Crop Lasso、Crop Polyline 和 Crop Perspective。
|
||||
|
||||
## 工具设置
|
||||
|
||||
四个子工具各自保存 **Keep Original**,默认关闭。关闭时结果替换源 Image Empty;开启时复制源对象承载结果,保留但取消选择原 Empty,并选择、激活新结果。
|
||||
|
||||
Crop Box、Crop Lasso 和 Crop Polyline 还分别保存 **Invert**;Box 与 Lasso 分别保存 **Refine Selection**。这些状态互不共享。Crop Perspective 没有 Invert 或 Refine Selection。Crop 不注册选项字母快捷键。
|
||||
|
||||
## 执行过程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Group["Crop Box / Lasso / Polyline / Perspective"]
|
||||
Group --> Kind{"Selection Crop?"}
|
||||
Kind -- "Box / Lasso / Polyline" --> Path["采集 SelectionPath"]
|
||||
Path --> Mask["栅格化 SelectionMask"]
|
||||
Mask --> Refine{"Box / Lasso Refine Selection?"}
|
||||
Refine -- "否" --> Local["源 Alpha × Selection"]
|
||||
Refine -- "是" --> Job["refine-image-selection<br/>保持 Selection bounds"]
|
||||
Job --> Refined["BEN2 Alpha × Selection"]
|
||||
Kind -- "Perspective" --> Quad["四点 Quad 与 Homography"]
|
||||
Local --> Result["创建结果 Image"]
|
||||
Refined --> Result
|
||||
Quad --> Result
|
||||
Result --> Keep{"当前子工具 Keep Original?"}
|
||||
Keep -- "否" --> Replace["替换源 Empty"]
|
||||
Keep -- "是" --> Copy["保留源 Empty<br/>选择并激活结果 Empty"]
|
||||
```
|
||||
|
||||
## Selection Crop
|
||||
|
||||
`box.py`、`lasso.py` 和 `polyline.py` 定义 WorkspaceTool,交互与落地集中在 `selection.py`。公共 `CropSelectionGesture` 从 `common/viewport.py` 读取当前子工具的独立设置,并把内存中的 `SelectionPath` 交给 `CropImageSelection`。
|
||||
|
||||
本地路径在几何 bounds 内把 Selection 值乘入源 Alpha,再按可见内容紧裁切。Box 或 Lasso 开启 Refine Selection 时,Operator 写出保留源 Alpha 的 bounds 矩形,并把已栅格化的 Selection Mask 保存为同一临时容器内的 Blender 本地 sidecar。`server/jobs/refine_image_selection.py` 返回与输入同尺寸的 `Source Alpha × BEN2 Alpha` RGBA;Blender 读取 sidecar,把 Selection 值乘入结果 Alpha,并以提交前的 bounds 创建结果 Image。Job request 只包含图片输入、模型和设备;Keep Original 在 Blender 主线程落地结果时执行。
|
||||
|
||||
## Crop Perspective
|
||||
|
||||
`perspective.py` 接受任意顺序的四个角点并规范为凸 Quad,创建 512 × 512 预览,随后按目标宽高比执行分块 Homography。输出最长边不超过 8192 px,总像素不超过 16,777,216;插值使用预乘 Alpha,结果按可见 Alpha 紧裁切。
|
||||
|
||||
四种 Crop 最终都调用 `common/image.py` 的 `place_empty_image_result()`。该入口负责替换或复制 Image Empty、画布对齐、Collection 归属,以及 Keep Original 下选择并激活结果对象。
|
||||
|
||||
本地像素路径与 Refine Job 路径都通过公共 Image 编辑结果入口创建数据块。结果以源 Image 的完整名称请求命名,不移除 `.png` 等名称内容,也不添加材质贴图后缀;源 Image 被保留时产生的 `.001` 等数字后缀完全由 Blender 处理。
|
||||
@@ -1,6 +1,6 @@
|
||||
# Cutout Tool
|
||||
|
||||
Cutout 从图片上的一个 Selection 开始,可按需执行 Refine Selection、生成 Depth 或 Normal Map,最后在 Blender 中创建带材质与 Modifier 的 Mesh Object。
|
||||
Cutout 从图片上的一个 Selection 开始,可按需生成 Depth 或 Normal Map,最后在 Blender 中创建带材质与 Modifier 的 Mesh Object。
|
||||
|
||||
## 执行过程
|
||||
|
||||
@@ -13,8 +13,8 @@ flowchart TD
|
||||
Route{"需要后台生成数据?"}
|
||||
Local["读取局部 RGBA<br/>源 Alpha × Selection 值"]
|
||||
Bounds["写出几何 bounds 矩形 RGBA"]
|
||||
Job["执行 Refine Selection,生成 Depth / Normal 产物"]
|
||||
Encoded["加载并 Pack 输入或 Refine RGBA"]
|
||||
Job["生成 Depth / Normal 产物"]
|
||||
Encoded["加载并 Pack 输入 RGBA"]
|
||||
Ready["贴图 Alpha × Selection Mask"]
|
||||
Build["构建 BaseShape"]
|
||||
Create["创建 Blender 对象"]
|
||||
@@ -25,7 +25,7 @@ flowchart TD
|
||||
Ready --> Build --> Create
|
||||
```
|
||||
|
||||
Surface 与 Balloon 在本地组合源 Alpha 与几何 Selection 后构建 `BaseShape`;Refine Selection、Depth Shape 和 Normal Map 先通过同一个矩形图片协议生成所需产物。
|
||||
Surface 与 Balloon 在本地组合源 Alpha 与几何 Selection 后构建 `BaseShape`;Depth Shape 和 Normal Map 先通过同一个矩形图片协议生成所需产物。
|
||||
|
||||
## 节点说明
|
||||
|
||||
@@ -33,41 +33,38 @@ Surface 与 Balloon 在本地组合源 Alpha 与几何 Selection 后构建 `Base
|
||||
|
||||
Cutout 工具由 `__init__.py` 注册,交互从 `main.py` 中的 `SelectCutoutSelection` 开始。它接收 Lasso 或双击整图手势,只构建内存中的 `SelectionPath`,然后调用 `interaction.py` 打开 Shape 饼菜单。此时不读取源图像素、不栅格化 Mask,也不扫描可见 Alpha。
|
||||
|
||||
`interaction.py` 读取工具选项,并在构建菜单按钮时把只含 Points 与 Invert 的完整 `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`。
|
||||
对象命中由 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 栅格化为一次纯几何 `SelectionMask`:非反选只处理 Path Bounds 与 2 px 抗锯齿边缘,反选覆盖完整图片范围。`values` 保存局部几何权重,`bounds` 保存它在源图中的位置;栅格阶段不读取、接收或合并源 Alpha。
|
||||
用户选定 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 的原始像素长边本身不会触发降密。
|
||||
|
||||
### 需要后台生成数据?
|
||||
|
||||
只有以下情况需要提交 Job:
|
||||
|
||||
- 开启 **Refine Selection**;
|
||||
- 选择 Depth Balloon 或 Depth Surface;
|
||||
- 开启 **Normal Map**。
|
||||
选择 Depth Balloon、Depth Surface 或开启 **Normal Map** 时需要提交 Job。
|
||||
|
||||
Surface 和 Balloon 的本地路径直接使用本地内容 Mask。
|
||||
|
||||
### 执行 Refine Selection,生成 Depth / Normal 产物
|
||||
### 生成 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`,写出与输入 bounds 同尺寸的 `selection.png`;
|
||||
- 需要 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。
|
||||
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** 约束 BEN2 和 MoGe-2 的分析图长边;网格密度使用 **Maximum Cutout Mesh Resolution**,最终颜色图使用源图裁切分辨率。
|
||||
**Maximum AI Input Size** 约束 MoGe-2 的分析图长边;网格密度使用 **Maximum Cutout Mesh Resolution**,最终颜色图使用 Selection bounds 分辨率。
|
||||
|
||||
Refine 的贴图内容 Alpha 统一为 `Source Alpha × BEN2 Alpha`。Color 直接使用该 RGBA;Normal 的可见区域、MoGe-2 黑底输入和 Depth Metadata 的参考区域使用同一 Alpha。Depth EXR 的 RGB 仍保存连续 Field,Alpha 保存模型 Validity。Selection Mask 只在 Blender 构建网格时与贴图 Alpha 相乘。
|
||||
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:启用 Refine 时使用完整 bounds 的 BEN2 输出;只生成 Depth 或 Normal 时使用提交前的矩形输入。它从同一 Image 读取 Alpha,与已栅格化的 Selection Mask 相乘后构建网格内容 Mask;颜色及 AI 贴图保持原 Alpha。`main.create_cutout_shape()` 调用 `geometry.py` 中的 `build_base_shape()`:
|
||||
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 对应的采样间距生成轮廓与内部采样点;
|
||||
@@ -79,7 +76,7 @@ Job 完成后,`main.py` 在 Blender 主线程加载并 Pack 可选的 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 的矩形输入或 BEN2 输出。
|
||||
`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 如何形成结果 | 必需后台产物 |
|
||||
| --- | --- | --- |
|
||||
@@ -96,16 +93,16 @@ Normal Map 关闭时材质使用几何法线;开启时,`object.py` 把生成
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Base["四种 Shape<br/>内部几何 +X"] --> Switch{"Inward Axis"}
|
||||
Switch -- "+X" --> Legacy["节点几何不转换<br/>对象矩阵追加轴变换"]
|
||||
Switch -- "-Z(默认)" --> Orient["O Image Cutout 最终几何轴转换<br/>对象沿用源 Empty local transform"]
|
||||
Legacy --> World["相同世界空间结果"]
|
||||
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 -- "+X" --> Keep["保持原通道"]
|
||||
Mode -- "-Z" --> Swizzle["(-y, z, -x)"]
|
||||
Mode -- "-Z" --> Keep["保持原生 Z 轴通道"]
|
||||
Mode -- "+X" --> Swizzle["按 Z → X 转换一次"]
|
||||
```
|
||||
|
||||
Shape 子节点始终按内部 `+X` 建模。统一的 `O Image Cutout` Modifier 在最终输出处按选项切换轴转换;`object.py` 同步选择对象矩阵,因此默认 `-Z` 与源 Image Empty 的 local transform 一致,旧 `+X` 模式仍保持相同世界空间形状。Depth Surface 的 Object Space Normal 仅在 `-Z` 模式执行通道转换;Tangent Space Normal 不变。
|
||||
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 不建立这条连接。
|
||||
|
||||
57
docs/internals/operators/image-edit-tool.md
Normal file
57
docs/internals/operators/image-edit-tool.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Image Edit Tool
|
||||
|
||||
工具栏包含 **Frame**、**Mask**、**Rectify** 三合一 Image Edit 组,Cutout 作为紧接其后的独立工具。
|
||||
|
||||
## 工具设置
|
||||
|
||||
Frame 和 Rectify 没有业务选项。Frame 让 active Image Empty 承载合成结果,并删除其他参与合并的选中 Image Empty;Rectify 直接替换 active Image Empty 的图片。
|
||||
|
||||
Mask 保存 **Gesture**、**Mode** 和 **Radius**。Gesture 包含 Lasso、Brush 与 Polyline;Mode 包含 Set、Add 与 Subtract,默认使用 Subtract;Radius 默认 25 px,仅在 Brush 时显示和生效。
|
||||
|
||||
## 执行过程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Group["Frame / Mask / Rectify"]
|
||||
Group --> Kind{"子工具"}
|
||||
Kind -- "Frame" --> Sources["选中 still Image Empty<br/>active 为对象和深度基准"]
|
||||
Sources --> Project["Frame 像素生成观察射线<br/>与图片平面求交"]
|
||||
Project --> Composite["逐像素深度与 Alpha 合成"]
|
||||
Composite --> Canvas["完整 Frame Canvas<br/>范围外透明"]
|
||||
Canvas --> Facing["active 朝向当前视角<br/>删除其他参与对象"]
|
||||
Kind -- "Mask" --> Gesture{"Lasso / Brush / Polyline"}
|
||||
Gesture --> Mask["栅格化 SelectionMask"]
|
||||
Mask --> Mode{"Set / Add / Subtract"}
|
||||
Mode --> Alpha["只合成当前 Alpha<br/>保持完整 Canvas"]
|
||||
Kind -- "Rectify" --> Quad["四点 Quad 与 Homography"]
|
||||
Alpha --> Replace["替换 active Empty 的 Image"]
|
||||
Quad --> Replace
|
||||
```
|
||||
|
||||
## Frame
|
||||
|
||||
`frame.py` 读取所有选中的 still Image Empty,并要求 active object 是 Selection 内的 still Image Empty。仅有 active 状态但未选中的图片不构成输入;active 是非图片对象时,即使 Selection 中存在图片也以 warning 取消。Frame 可以从 Viewport 任意位置开始,但结果矩形必须与 active 图片投射相交。选中的非 Image Empty 被忽略;任一选中 Image Empty 是视频或序列时整体取消。
|
||||
|
||||
每个参与图片使用冻结的 Viewport 投影矩阵与对象世界矩阵建立屏幕到图片平面的 Projective Transform。Frame 输出像素中心沿当前观察射线映射到图片局部平面,再换算为源像素坐标;有限且位于 Image Empty 显示边界内的交点参与采样。正交视图接受任意有限视图深度;透视视图只接受观察方向前方的交点。图片部分跨越透视观察原点时,前方区域继续采样,后方、平行射线和图片范围外透明。
|
||||
|
||||
输出像素按观察射线上的几何深度从远到近排序,再以预乘 Alpha 合成;同深度时 active 位于最上层。Frame 与 active 的交叠通过连续有效投射区域和 Frame 矩形的正面积交集判断,不依赖源 Alpha 或离散输出像素中心。输出分辨率使用 Frame 在当前 Region 中的屏幕像素宽高,不超过 Region 尺寸,并由 Preferences 的 **Maximum Frame Resolution** 等比限制最长边;总像素不超过 16,777,216。透明边缘保留在完整 Canvas 中;紧邻内容的一圈透明像素保留相邻 RGB,避免线性插值产生黑边。
|
||||
|
||||
Frame 与 Cutout Lasso 只在 `CLICK_DRAG` 后进入手势。Mask 与 Rectify 使用 `PRESS` 启动。三个绘制型工具都由 Blender 原生点选决定命中对象:点到另一对象时只切换 active,不开始编辑;没有命中对象时保留当前 active Image Empty,因此透明区域和 Viewport 空白都可作为起点。Mask 使用 `PAINT_CROSS` cursor;Brush 单击生成一个圆形印记。Polyline 通过逐点单击建立路径,至少三个点后在起点周围显示随鼠标接近而放大的闭合圈,并以点击 15 px UI Scale 命中范围、双击或 Enter 完成;Backspace 删除最后一点。手势与图片范围、可见内容的有效性在提交阶段判断。Frame 的普通空白点击继续清空选择,Shift 点击由显式 Viewport 对象选择 keymap 处理。
|
||||
|
||||
结果平面本地 XY 平行视平面且正面朝向观察者。正交视图与 active 原点位于透视观察方向前方时,结果沿用 active 对象原点的视图深度;透视 active 原点位于视后方但图片仍有有效前方交叠时,结果使用有效交叠区域屏幕中心与 active 图片平面的交点深度。active Blender Object 保留名称、Collection 和身份并承载 packed 合成 Image,其他参与 Image Empty 随后删除;整个操作属于一个 Undo 步骤。
|
||||
|
||||
## Mask
|
||||
|
||||
`mask.py` 定义一个 WorkspaceTool 和一个 `EditImageAlpha` Operator。Operator 启动时冻结 Scene 中保存的 Gesture、Mode 和 Radius;Lasso 把自由手绘闭合路径转换为图片像素,Polyline 把逐点闭合路径送入同一个 SelectionMask 与 Alpha 合成入口。
|
||||
|
||||
Brush 拖动阶段只维护经过距离与近共线简化的屏幕轨迹,并为每个采样点生成圆、为每对相邻点生成等宽连接条;不读取源 RGBA,不创建临时 Image,也不投影、栅格化或更新 Pixel Buffer。Overlay 在局部 screen-space scanline bands 中合并这些 Primitive,生成无内部接缝、尖角或三角孔的一次填充与闭合外轮廓;Subtract 为红色,Set/Add 为灰色。释放后把同一组 Primitive 投影到图片,以逐像素最大值合入单个覆盖画布,再通过公共 Alpha 合成与 Image 替换入口提交 Packed 结果。取消或切换工具只结束 Overlay,源 Image 与共享引用始终不变。
|
||||
|
||||
设当前 Alpha 为 `A`、手势权重为 `M`:Set 计算 `A × M`,Add 计算 `max(A, M)`,Subtract 计算 `A × (1 - M)`。RGB 原样保留。结果始终使用源图片宽高和原有画框,因此 Image Empty 的 identity、matrix、display size 和 offset 保持不变;全透明结果同样有效。
|
||||
|
||||
## Rectify
|
||||
|
||||
`rectify.py` 接受任意顺序的四个角点并规范为凸 Quad,创建 512 × 512 预览,随后按目标宽高比执行分块 Homography。输出最长边不超过 8192 px,总像素不超过 16,777,216;插值使用预乘 Alpha,结果按可见 Alpha trim 后映射回源图片平面。
|
||||
|
||||
Mask 与 Rectify 最终都以 packed Image 调用 `common/image.py` 的 `replace_empty_image()`。Mask 不提供 placement bounds;Rectify 使用校正区域的 placement bounds 调整图片画框。Frame 复用同模块的 Image 创建、Homography、预乘采样和单对象替换函数,并在自身业务边界完成多图合成与对象删除。
|
||||
|
||||
三个工具的结果都以源 Image 的完整名称请求命名,不移除 `.png` 等名称内容,也不添加材质贴图后缀。源数据仍被其他对象引用时,Blender 为结果追加数字后缀;源数据可清理时,替换入口让结果恢复准确原名。
|
||||
@@ -9,7 +9,7 @@
|
||||
| `operators/clipboard_image/` | [Clipboard Image](clipboard-image.md) | 系统剪贴板读取、粘贴与快捷键 |
|
||||
| `operators/convert_to_plane/` | [Convert to Plane](convert-to-plane.md) | Mesh Plane 与 Depth Plane 创建 |
|
||||
| `operators/cutout_tool/` | [Cutout Tool](cutout-tool.md) | Selection、基础 Shape 构建与 Blender 对象创建 |
|
||||
| `operators/crop_tool/` | [Crop Tool](crop-tool.md) | Box、Lasso、Polyline 与 Perspective 图片裁切 |
|
||||
| `operators/image_edit_tool/` | [Image Edit Tool](image-edit-tool.md) | Frame 合成、Mask Alpha 编辑与 Rectify 透视校正 |
|
||||
| `operators/debug.py` | [Debug Operator](debug.md) | 模型接口与原始产物验证 |
|
||||
| `operators/remove_background.py` | [Remove Background](remove-background.md) | BEN2 去背与结果替换 |
|
||||
| `operators/upscale.py` | [Upscale](upscale.md) | 当前 Image 的 4× 高清与输入长边检查 |
|
||||
|
||||
Reference in New Issue
Block a user