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

@@ -1,4 +1,4 @@
"""Generate optional AI files for one Blender-rasterized Cutout Selection."""
"""Generate optional AI files from one rectangular Cutout image input."""
import json
from pathlib import Path

View File

@@ -16,6 +16,7 @@ def run(context, parameters):
parameters,
images[0],
depth_mode="Z",
normal_mode="TANGENT",
)
context.check_cancelled()
context.progress(1.0, "Depth Plane data is ready")

View File

@@ -5,14 +5,16 @@ def _crop_to_alpha(image):
import numpy as np
pixels = np.asarray(image.convert("RGBA"), dtype=np.uint8)
visible_y, visible_x = np.nonzero(pixels[:, :, 3] > 0)
visible = pixels[:, :, 3] > 0
visible_y = np.flatnonzero(np.any(visible, axis=1))
visible_x = np.flatnonzero(np.any(visible, axis=0))
if not len(visible_x):
raise ValueError("The generated Selection contains no visible pixels")
bounds = (
int(visible_x.min()),
int(visible_y.min()),
int(visible_x.max()) + 1,
int(visible_y.max()) + 1,
int(visible_x[0]),
int(visible_y[0]),
int(visible_x[-1]) + 1,
int(visible_y[-1]) + 1,
)
return image.crop(bounds), bounds
@@ -24,7 +26,7 @@ def refine_selection(
device,
output_path,
):
"""Refine an image Selection from an already rasterized Selection."""
"""Refine foreground Alpha within one rectangular Selection context."""
model_manager = context.resource("model_manager")
foreground, _load_ms, _inference_ms = infer_foreground(
input_path,