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:
2026-09-01 22:36:20 +08:00
parent 2ff7b04f56
commit 7f6e47432e
54 changed files with 2831 additions and 634 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-01

View File

@@ -0,0 +1,82 @@
## Context
`rasterize_selection_path()` 当前同时完成路径填充、抗锯齿、源 Alpha 相交、空内容判断和内容 bounds 收紧。调用方因此必须先通过 Blender `Image.pixels` 取得 RGBA,导致 `SelectionMask` 随图片透明度变化,而不是稳定表达用户圈选。
Image Tool 与 Cutout 的实际业务确实需要图片内容,但目的不同:本地编辑要把 Selection 乘入源 Alpha,Cutout 几何要用内容 Mask,AI 操作要把局部图片写成 Job 输入。需要 AI 时,现有代码先把 Lasso Mask 乘入临时 RGBA 并编码 PNG;响应后又读取源图并新建颜色 Image,造成同一操作中的第二次大图编码。实测 6288×5920 RGBA PNG 的 Selection 栅格化约 0.17–0.55 秒,提交前 PNG 保存约 1.79 秒,新建颜色 Image 的 Pack 约 1.74 秒。图片编码成本不属于 Selection,并且本设计接受生成新图片所需的单次编码成本。
## Goals / Non-Goals
**Goals:**
- 让 SelectionPath 与 SelectionMask 只表达用户圈选几何,不受源图片 Alpha 或颜色影响。
- 让 Image Tool、Cutout 与 AI 输入准备在各自业务边界显式消费源 Alpha 或 RGBA。
- 所有 AI 操作统一使用 Selection 几何 bounds 内、未乘 Lasso Mask 的矩形局部 RGBA 文件。
- 复用提交前输入或 Refine 输出的已有编码图片,避免 AI 响应阶段重新构建并编码相同颜色图。
- 保持 non-zero winding、Invert、抗锯齿和局部画布语义。
**Non-Goals:**
- 不优化生成新图片时不可避免的 PNG 编码、Pack 或磁盘写入耗时。
- 不按 FILE、Packed、Generated 或 Dirty 来源建立不同的 Operator 主链。
- 不引入 Pillow、GPU readback、后台 Blender 数据访问或新的运行时依赖。
- 不改变 SelectionPath JSON、AI 模型、Cutout Shape 算法或 Job 进程边界。
## Decisions
### 1. Selection 栅格化只产生几何 Mask
`rasterize_selection_path(image_size, selection_path, *, antialias=False)` 只根据路径、Invert、图片尺寸和抗锯齿生成 `SelectionMask(values, bounds)`。删除 `source_alpha` 与 `alpha_threshold` 参数,栅格器不导入 Blender Image helper,也不执行内容可见性检查。
普通 Selection 的 bounds 来自路径与抗锯齿 padding 裁切到图片范围;Invert 仍使用整张图片。相同 Path、尺寸和抗锯齿设置必须在透明、半透明与不透明图片上得到完全相同的 SelectionMask。
相比保留可选 Alpha 参数,彻底删除内容入口可以固定职责并阻止调用方继续把内容 bounds 误当成 Selection bounds;不保留兼容调用方式。
### 2. 图片业务直接合并局部 Alpha 与 Selection 值
Image Tool 在 `SelectionMask.bounds` 内裁切源 RGBA,并直接执行 `local_alpha *= selection_values`。最终内容 bounds、空内容和结果图片创建都属于 Image 编辑业务。实现应在已有局部数组上计算行列可见范围,不为每个可见像素生成坐标数组,也不把内容相交结果写回 SelectionMask。
本地 Cutout 在构建 BaseShape 时显式组合局部源 Alpha 与 Selection 值;颜色、几何和空内容判断使用该业务结果。SelectionMask 本身在调用链中保持不变。相比新增第二种与 SelectionMask 同形的公共值类型,业务直接消费两个一通道局部数组更简单,也避免把图片内容重新包装成“Selection”。
### 3. AI 输入是 Selection bounds 的原始矩形 RGBA
将当前“Selection 输入”改为“Bounds 图片输入”:公共图片 helper 接收源 RGBA 与几何 bounds,只裁切并写出矩形 RGBA,不把 SelectionMask 乘入 Alpha。Image Tool、Cutout Refine、Normal 与 Depth 都调用同一个入口;来源已经统一为 Blender Image,不在 Operator 中按文件、Packed 或生成图片分流。
Refine 把 Lasso 当作上下文范围。BEN2 可以在该矩形内保留 Lasso 外的内容,其输出 Alpha 是 Refine 业务结果,不再与原始 Lasso Mask 相交。Normal 或 Depth 也消费同一矩形输入,最终 Shape 仍由本地 Selection 几何约束。
Invert Selection 的几何 bounds 是全图,因此其 AI 输入自然退化为全图矩形,不增加特殊协议。
### 4. 已编码的 AI 图片直接成为颜色资产
AI 响应选择一个已有文件作为最终局部颜色图:启用 Refine 时使用 BEN2 输出;仅生成 Normal 或 Depth 时使用提交前的 Bounds 输入。Blender 直接加载该文件,继承源颜色空间和 Alpha 约定,Pack 已编码文件后交给 Image Empty 或 Cutout 材质。
Image Tool Refine 直接按响应 bounds 替换源 Image Empty,不再提取输出 Alpha、重新读取源 RGBA、创建 Pixel Result Image。Cutout 从同一个局部文件读取需要的 Alpha 构建几何,并把该已加载 Image 用作颜色纹理,不再调用 `_cropped_color_image()` 重建同一颜色内容。
输入临时文件必须保持到响应完成且 Blender 已加载并 Pack;随后由现有 Job 清理生命周期删除。相比继续从原图重建,这一方案保持颜色来源一致,并把已有文件 Pack 从大图重新编码变为文件收纳。
### 5. 内容错误由消费业务报告
纯 Selection 只在路径与图片范围没有几何交集时报告空 Selection。源 Alpha 全透明、Image 编辑结果为空、Refine 输出为空或 Cutout 内容 Mask 为空,分别由执行该内容操作的业务报告。这样错误发生位置与实际失败职责一致。
## Risks / Trade-offs
- [Refine 结果扩展到 Lasso 外] → 这是明确接受的产品语义;测试固定 Lasso 只提供矩形上下文,不作为 Refine 硬 Mask。
- [Selection bounds 不再等于内容 bounds] → 在类型和文档中只把 SelectionMask bounds 称为几何 bounds,内容业务独立计算并传递 placement bounds。
- [AI 输入矩形包含额外背景] → 最终本地 Shape 仍受 Selection 几何约束;Refine 则有意允许模型利用并保留矩形上下文。
- [复用 PNG 改变颜色空间或 Alpha 配置] → 加载后显式继承源 Image 的颜色空间与 Alpha mode,并用结果等价测试覆盖。
- [临时文件被过早清理] → 保持现有 Job cleanup 边界,只有成功加载并 Pack 或操作取消后才删除输入。
- [全图或 Invert 仍需处理大图] → 接受单次新图片编码成本,本次只删除 Selection 的内容依赖和 AI 响应阶段的重复编码。
## Migration Plan
1. 修改 Selection 栅格 API 与测试,使其只产生几何 Mask,并迁移所有调用方。
2. 将 Image Tool 与本地 Cutout 的源 Alpha 合并、内容 bounds 和空内容检查放入各自业务阶段。
3. 将 `prepare_selection_input` 替换为统一的 Bounds 矩形 RGBA 输入,并更新 AI Job 输入语义和测试。
4. 调整 Image Tool 与 Cutout AI 响应,直接加载并 Pack 输入或 Refine 输出,删除源图重读和颜色 Image 重建。
5. 更新内部文档,运行相关测试、完整测试和真实高分辨率阶段基准。
回退时整体恢复旧栅格参数、Masked AI 输入和响应重建链,不保留双入口或兼容层。
## Open Questions
无。

View File

@@ -0,0 +1,29 @@
## Why
当前 `SelectionPath → SelectionMask` 在栅格化阶段读取并合并源图 Alpha,使选区几何依赖图片内容,也迫使 Image Tool 与 Cutout 在业务开始前读取整图。Selection 应客观表达用户圈选;源 Alpha、图片裁切、AI 输入和最终图片持久化应由实际业务分别处理。
## What Changes
- **BREAKING**:Selection 栅格化只接收图片尺寸与 `SelectionPath`,删除源 Alpha、Alpha threshold 及内容可见性职责;`SelectionMask` 只保存 non-zero winding、Invert 与抗锯齿产生的几何值和几何 bounds。
- Image Tool 与本地 Cutout 在实际操作中显式将局部源 Alpha 与 `SelectionMask.values` 相乘,并在需要时计算内容 bounds;公共 Selection 模块不读取 Blender Image。
- 需要 AI 的 Image Tool 与 Cutout 统一按几何 bounds 写出一份未应用 Lasso Mask 的矩形局部 RGBA 文件。Refine 将 Lasso 作为上下文范围,允许 BEN2 结果保留矩形内、Lasso 外的内容。
- AI 响应复用已经编码的局部图片:Refine 直接使用 BEN2 输出,只有 Normal 或 Depth 时直接使用提交前输入;不再重新读取源图、重建同一颜色 Image 后再次编码 Pack。
- 删除由 Selection 栅格化隐式承担的空内容判断,改由消费源 Alpha 或 AI 结果的具体业务报告空内容。
- 增加 Selection 纯几何等价、Alpha 合并边界、矩形 AI 输入和编码图片复用测试,并同步内部文档。
## Capabilities
### New Capabilities
- `selection-content-processing`: 规定纯几何 Selection Mask、业务侧源 Alpha 合并、Bounds 矩形 AI 输入及已有编码图片复用行为。
### Modified Capabilities
无。
## Impact
- 影响 `common/selection.py`、`common/image.py`、Image Tool、Cutout Tool、Cutout 颜色图创建、AI 输入/响应处理及相关测试和内部文档。
- `rasterize_selection_path()` 的内部调用协议发生变化,不保留带 `source_alpha` 或 `alpha_threshold` 的兼容入口。
- Cutout 与 Image Tool 的 AI 输入 Alpha 语义改变:输入为几何 bounds 内的原始矩形 RGBA,而不是预先乘入 Lasso Mask 的 RGBA。
- 不新增依赖,不改变 SelectionPath JSON、填充规则、抗锯齿核、AI 模型或 Job Server 进程边界;不以本次变更优化不可避免的新图片编码耗时。

View File

@@ -0,0 +1,72 @@
## ADDED Requirements
### Requirement: Selection Mask is independent of image content
系统 SHALL 仅根据 Selection Path、Invert、图片尺寸与抗锯齿设置栅格化 Selection Mask,并且 Selection 栅格化不得读取、接收或合并源图片 Alpha、颜色或 Alpha threshold。
#### Scenario: Images have different Alpha
- **WHEN** 相同 Selection Path、图片尺寸与抗锯齿设置用于透明度内容不同的图片
- **THEN** 系统产生逐值相同且 bounds 相同的 Selection Mask
#### Scenario: Selection is inverted
- **WHEN** Selection Path 启用 Invert
- **THEN** 系统在完整图片画布上产生路径外的纯几何 Selection Mask,而不检查源图片可见内容
### Requirement: Image businesses combine source Alpha explicitly
系统 SHALL 由 Image Tool 与本地 Cutout 在实际业务阶段显式组合 Selection Mask 值和对应 Bounds 内的源 Alpha;Selection Mask MUST 在组合前后保持纯几何值不变。
#### Scenario: Image Tool edits a Selection
- **WHEN** 用户提交未启用 Refine 的 Image Tool Selection
- **THEN** Image Tool 在几何 bounds 内把 Selection 值乘入源 Alpha,并由编辑业务计算内容 bounds、空结果和最终图片
#### Scenario: Local Cutout builds a Shape
- **WHEN** 用户选择不需要 AI 的 Cutout Shape
- **THEN** Cutout 在构建 Shape 时组合局部源 Alpha 与 Selection 值,并使用业务结果约束几何和判断空内容
#### Scenario: Source content is transparent
- **WHEN** 几何 Selection 有效但对应源 Alpha 没有可见内容
- **THEN** 消费该内容的 Image 或 Cutout 业务报告空内容,Selection 栅格化本身仍成功
### Requirement: AI receives one rectangular Bounds image
系统 SHALL 为需要 AI 的 Image Tool 或 Cutout 按 Selection 几何 bounds 写出一份矩形局部 RGBA 文件,并且该文件 MUST 保留 bounds 内的源 Alpha,不得预先乘入 Lasso Selection 值。
#### Scenario: Refine receives Lasso context
- **WHEN** 用户对 Lasso Selection 启用 Refine
- **THEN** BEN2 接收 Lasso 几何 bounds 对应的完整矩形 RGBA,矩形内、Lasso 外的源像素保持原样
#### Scenario: Normal or Depth requires AI
- **WHEN** Cutout 仅因 Normal 或 Depth 进入 AI 流程
- **THEN** Job 使用与 Refine 相同的 Bounds 矩形输入协议,不建立另一套 Masked Selection 输入
#### Scenario: Blender Image source varies
- **WHEN** 源 Image 来自文件、Packed 数据或 Blender 内存
- **THEN** Operator 通过同一个 Bounds 图片序列化入口产生 AI 输入,不按来源建立不同业务主链
### Requirement: Refine may expand within Selection Bounds
系统 SHALL 将 Refine 的 Lasso 解释为矩形上下文范围,而不是 BEN2 输出的硬 Mask;最终 Refine Alpha SHALL 由 BEN2 在该矩形内的结果决定。
#### Scenario: BEN2 preserves content outside Lasso
- **WHEN** BEN2 在 Selection bounds 内保留了 Lasso 路径外的像素
- **THEN** 系统保留这些像素,不再与原始 Lasso Selection Mask 相交
#### Scenario: BEN2 returns no visible content
- **WHEN** Refine 输出在矩形 bounds 内没有可见 Alpha
- **THEN** Refine 业务报告空内容且不创建 Image 或 Cutout 结果
### Requirement: AI operations reuse encoded local images
系统 SHALL 复用 AI 流程中已经编码的局部 RGBA 文件作为最终颜色 Image,不得重新读取完整源图并创建包含相同颜色内容的新内存 Image 后再次编码。
#### Scenario: Image Tool Refine completes
- **WHEN** BEN2 返回已收紧 bounds 的 Refine RGBA 文件
- **THEN** Image Tool 直接加载、配置并 Pack 该文件,然后按响应 bounds 替换源 Image Empty
#### Scenario: Cutout Refine completes
- **WHEN** Cutout 的 BEN2 Refine 返回局部 RGBA 文件
- **THEN** Cutout 使用该文件的 Alpha 构建结果几何,并使用同一个已加载 Image 作为颜色纹理
#### Scenario: Cutout generates only Normal or Depth
- **WHEN** Cutout AI 流程没有 Refine 输出
- **THEN** Cutout 直接加载并 Pack 提交前的 Bounds 输入作为颜色纹理
#### Scenario: AI operation cleans up
- **WHEN** 已有输入或 Refine 输出成功加载并 Pack,或者操作取消
- **THEN** 系统按 Job 生命周期清理临时文件,且 Blender 结果不再依赖临时路径

View File

@@ -0,0 +1,30 @@
## 1. 纯几何 Selection
- [x] 1.1 删除 `rasterize_selection_path()` 的源 Alpha 与 Alpha threshold 参数,使其只按 Path、Invert、图片尺寸和抗锯齿返回几何 `SelectionMask`
- [x] 1.2 调整 Selection 的空路径、越界、Invert、局部画布和 bounds 行为,确保栅格阶段不再承担图片内容可见性判断
- [x] 1.3 更新 Selection 单元测试,验证不同源 Alpha 不参与接口且相同几何输入始终产生相同 values 与 bounds
## 2. 业务侧 Alpha 合并
- [x] 2.1 调整 Image Tool,在几何 bounds 内直接将 Selection 值乘入局部源 Alpha,并由编辑业务计算空内容与最终 placement bounds
- [x] 2.2 移除 Image Tool 对已知局部结果的重复逐像素坐标扫描,使用按行列可见范围完成内容收紧
- [x] 2.3 调整本地 Cutout,使 BaseShape、空内容与几何处理显式消费局部源 Alpha 和纯 Selection 值,不修改 SelectionMask
- [x] 2.4 补充 Image Tool 与 Cutout 的透明、半透明、空内容、Invert 和内容 bounds 回归测试
## 3. 矩形 AI 输入
- [x] 3.1 将 `prepare_selection_input` 替换为统一的 Bounds 图片输入 helper,只裁切源 RGBA 并写出矩形局部文件,不乘入 Lasso Mask
- [x] 3.2 迁移 Image Tool Refine 与 Cutout Refine、Normal、Depth 调用链,使其共享同一个几何 bounds 输入协议
- [x] 3.3 更新 Server 与交互测试,验证 BEN2 可以保留 bounds 内、Lasso 外内容,Normal 与 Depth 不再使用另一套 Masked 输入
## 4. 复用 AI 编码图片
- [x] 4.1 建立加载、配置并 Pack 已编码局部 RGBA 的公共入口,继承源颜色空间、Alpha mode、命名和 placement bounds 所需信息
- [x] 4.2 调整 Image Tool Refine 响应,直接使用 BEN2 输出替换源 Image Empty,删除输出 Alpha 提取、源 RGBA 重读和 Pixel Result Image 重建
- [x] 4.3 调整 Cutout AI 响应:Refine 使用 BEN2 输出,只有 Normal 或 Depth 时使用提交前输入,并让同一局部 Image 同时服务几何 Alpha 与颜色材质
- [x] 4.4 验证成功、取消与异常路径只在结果已加载并 Pack 后清理临时输入,且不残留无用户的 Blender Image
## 5. 文档与验证
- [x] 5.1 更新 Selection、Image Tool、Cutout Tool 与 AI 输出内部文档,区分几何 bounds、内容 bounds 和矩形上下文语义
- [x] 5.2 运行相关测试与完整测试,并用真实高分辨率 RGBA 图片复测 Selection、AI 提交前准备和 AI 响应阶段,确认没有新增第二次图片编码