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

@@ -202,10 +202,10 @@ def _resize_values(values, size):
)
def extract_selection_contours(selection_mask, max_resolution=512, threshold=0.5):
def extract_selection_contours(selection_values, max_resolution=512, threshold=0.5):
import numpy as np
values = np.asarray(selection_mask.values)
values = np.asarray(selection_values)
if values.ndim != 2:
raise ValueError("Cutout Alpha must be two-dimensional")
if np.issubdtype(values.dtype, np.floating):
@@ -1341,13 +1341,13 @@ def crop_plane_bounds(source_object, crop_bounds, source_size):
def extract_cutout_contours(
selection_mask,
content_values,
mesh_resolution,
threshold=CUTOUT_ALPHA_THRESHOLD,
):
contour_resolution = max(512, min(int(mesh_resolution) * 8, 1024))
return extract_selection_contours(
selection_mask,
content_values,
max_resolution=contour_resolution,
threshold=threshold,
)
@@ -1373,11 +1373,11 @@ def cutout_sampling_spacing(source_object, mesh_resolution):
return max(width, height) / max(int(mesh_resolution), 8)
def selection_calibration(selection_mask, target_shape, threshold=CUTOUT_ALPHA_THRESHOLD):
"""Return the final Selection mask and transparency ratio at geometry resolution."""
def selection_calibration(content_values, target_shape, threshold=CUTOUT_ALPHA_THRESHOLD):
"""Return Cutout validity and transparency ratio at geometry resolution."""
import numpy as np
values = np.asarray(selection_mask.values, dtype=np.float32)
values = np.asarray(content_values, dtype=np.float32)
target_height, target_width = (int(value) for value in target_shape)
source_height, source_width = values.shape
y = np.minimum(
@@ -1394,16 +1394,22 @@ def selection_calibration(selection_mask, target_shape, threshold=CUTOUT_ALPHA_T
return valid, transparent_fraction
def build_base_shape(context, source_object, mesh_resolution, selection_mask):
def build_base_shape(
context,
source_object,
mesh_resolution,
content_values,
content_bounds,
):
import numpy as np
bounds = crop_plane_bounds(
source_object,
selection_mask.bounds,
content_bounds,
tuple(source_object.data.size),
)
selection_contours = extract_cutout_contours(
selection_mask,
content_values,
mesh_resolution,
threshold=CUTOUT_ALPHA_THRESHOLD,
)