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:
@@ -326,12 +326,10 @@ def triangulated_polygon_vertices(vertices):
|
||||
return _triangulated_polygon_vertices_python(vertices)
|
||||
|
||||
|
||||
def scanline_fill_triangles(vertices, fill_rule="EVEN_ODD", step=2.0):
|
||||
def scanline_fill_triangles(vertices, step=2.0):
|
||||
vertices = tuple(tuple(float(value) for value in vertex) for vertex in vertices)
|
||||
if len(vertices) < 3:
|
||||
return ()
|
||||
if fill_rule not in {"EVEN_ODD", "NONZERO"}:
|
||||
raise ValueError(f"Unknown polygon fill rule: {fill_rule}")
|
||||
minimum_y = min(vertex[1] for vertex in vertices)
|
||||
maximum_y = max(vertex[1] for vertex in vertices)
|
||||
band_bottom = minimum_y
|
||||
@@ -358,17 +356,11 @@ def scanline_fill_triangles(vertices, fill_rule="EVEN_ODD", step=2.0):
|
||||
while index < len(events):
|
||||
x = events[index][0]
|
||||
delta = 0
|
||||
count = 0
|
||||
while index < len(events) and abs(events[index][0] - x) <= 1e-8:
|
||||
delta += events[index][1]
|
||||
count += 1
|
||||
index += 1
|
||||
was_inside = winding != 0
|
||||
if fill_rule == "EVEN_ODD":
|
||||
if count % 2:
|
||||
winding = 0 if was_inside else 1
|
||||
else:
|
||||
winding += delta
|
||||
winding += delta
|
||||
is_inside = winding != 0
|
||||
if not was_inside and is_inside:
|
||||
interval_start = x
|
||||
@@ -454,12 +446,12 @@ def polygon_self_intersects(vertices, cell_size=32.0):
|
||||
return False
|
||||
|
||||
|
||||
def preview_fill_triangles(vertices, fill_rule="EVEN_ODD"):
|
||||
def preview_fill_triangles(vertices):
|
||||
if not polygon_self_intersects(vertices):
|
||||
triangles = triangulated_polygon_vertices(vertices)
|
||||
if triangles:
|
||||
return triangles
|
||||
return scanline_fill_triangles(vertices, fill_rule)
|
||||
return scanline_fill_triangles(vertices)
|
||||
|
||||
|
||||
def draw_polygon_fill(
|
||||
@@ -609,7 +601,6 @@ def image_line_half_plane_polygon(line, image_size):
|
||||
|
||||
class ImageSelectionGesture:
|
||||
active_tool_id = None
|
||||
preview_fill_rule = "EVEN_ODD"
|
||||
_path = None
|
||||
_cursor = None
|
||||
_handle = None
|
||||
@@ -658,9 +649,7 @@ class ImageSelectionGesture:
|
||||
if len(polygon) >= 3:
|
||||
if polygon != self._fill_polygon:
|
||||
self._fill_polygon = polygon
|
||||
self._fill_triangles = preview_fill_triangles(
|
||||
polygon, self.preview_fill_rule
|
||||
)
|
||||
self._fill_triangles = preview_fill_triangles(polygon)
|
||||
draw_polygon_fill(
|
||||
polygon,
|
||||
self._fill_triangles,
|
||||
@@ -725,6 +714,9 @@ class ImageSelectionGesture:
|
||||
def cancel(self, context):
|
||||
self._finish(context)
|
||||
|
||||
def _submit_selection_path(self, _context, _selection_path):
|
||||
raise NotImplementedError
|
||||
|
||||
def invoke(self, context, event):
|
||||
source_object, should_edit = resolve_image_edit_click(
|
||||
context, event, self.report
|
||||
@@ -740,7 +732,6 @@ class ImageSelectionGesture:
|
||||
self.invert = False
|
||||
self.source_object_name = source_object.name
|
||||
self.source_matrix_data = serialize_matrix(source_object.matrix_world)
|
||||
self.selection_path_json = ""
|
||||
point = (float(event.mouse_region_x), float(event.mouse_region_y))
|
||||
self._path = [point]
|
||||
self._cursor = point
|
||||
@@ -787,13 +778,12 @@ class ImageSelectionGesture:
|
||||
image_empty_bounds(source_object),
|
||||
tuple(source_object.data.size),
|
||||
)
|
||||
self.selection_path_json = SelectionPath(
|
||||
selection_path = SelectionPath(
|
||||
points=selection_path,
|
||||
invert=self.invert,
|
||||
fill_rule=self.preview_fill_rule,
|
||||
).to_json()
|
||||
)
|
||||
self._finish(context)
|
||||
return self.execute(context)
|
||||
return self._submit_selection_path(context, selection_path)
|
||||
|
||||
def modal(self, context, event):
|
||||
active_tool = active_view3d_tool_id(context)
|
||||
|
||||
Reference in New Issue
Block a user