Refine freehand exclusion polygons
Store freehand paths as closed polygons with thin outlines and fill the interior when masking, so free-drawn shapes behave like custom rectangles.
This commit is contained in:
parent
f678c403b7
commit
464855f365
|
|
@ -14,7 +14,6 @@ class ExclusionMixin:
|
|||
y = max(0, min(self.preview_img.height - 1, int(event.y)))
|
||||
if mode == "free":
|
||||
self._current_stroke = [(x, y)]
|
||||
width = int(getattr(self, "free_draw_width", 14))
|
||||
preview_id = getattr(self, "_stroke_preview_id", None)
|
||||
if preview_id:
|
||||
try:
|
||||
|
|
@ -27,15 +26,13 @@ class ExclusionMixin:
|
|||
x,
|
||||
y,
|
||||
fill="yellow",
|
||||
width=width,
|
||||
width=2,
|
||||
smooth=True,
|
||||
capstyle="round",
|
||||
joinstyle="round",
|
||||
)
|
||||
self._rubber_start = None
|
||||
return
|
||||
x = max(0, min(self.preview_img.width - 1, int(event.x)))
|
||||
y = max(0, min(self.preview_img.height - 1, int(event.y)))
|
||||
self._rubber_start = (x, y)
|
||||
if self._rubber_id:
|
||||
try:
|
||||
|
|
@ -70,13 +67,12 @@ class ExclusionMixin:
|
|||
mode = getattr(self, "exclude_mode", "rect")
|
||||
if mode == "free":
|
||||
stroke = getattr(self, "_current_stroke", None)
|
||||
if stroke and len(stroke) > 1:
|
||||
cleaned = self._compress_stroke(stroke)
|
||||
if len(cleaned) > 1:
|
||||
if stroke and len(stroke) > 2:
|
||||
polygon = self._close_polygon(self._compress_stroke(stroke))
|
||||
if len(polygon) >= 3:
|
||||
shape = {
|
||||
"kind": "stroke",
|
||||
"points": cleaned,
|
||||
"width": int(getattr(self, "free_draw_width", 14)),
|
||||
"kind": "polygon",
|
||||
"points": polygon,
|
||||
}
|
||||
self.exclude_shapes.append(shape)
|
||||
stamper = getattr(self, "_stamp_shape_on_mask", None)
|
||||
|
|
@ -190,5 +186,15 @@ class ExclusionMixin:
|
|||
compressed.append(point)
|
||||
return compressed
|
||||
|
||||
@staticmethod
|
||||
def _close_polygon(points: list[tuple[int, int]]) -> list[tuple[int, int]]:
|
||||
"""Ensure the polygon is closed by repeating the start if necessary."""
|
||||
if len(points) < 3:
|
||||
return points
|
||||
closed = list(points)
|
||||
if closed[0] != closed[-1]:
|
||||
closed.append(closed[0])
|
||||
return closed
|
||||
|
||||
|
||||
__all__ = ["ExclusionMixin"]
|
||||
|
|
|
|||
|
|
@ -266,12 +266,12 @@ class ImageProcessingMixin:
|
|||
if shape.get("kind") == "rect":
|
||||
x0, y0, x1, y1 = shape["coords"] # type: ignore[index]
|
||||
outline.rectangle([x0, y0, x1, y1], outline=(255, 215, 0, 200), width=3)
|
||||
elif shape.get("kind") == "stroke":
|
||||
elif shape.get("kind") == "polygon":
|
||||
points = shape.get("points", [])
|
||||
if len(points) < 2:
|
||||
continue
|
||||
width_px = int(shape.get("width", 8))
|
||||
outline.line(points, fill=(255, 215, 0, 200), width=width_px, joint="round")
|
||||
path = points if points[0] == points[-1] else points + [points[0]]
|
||||
outline.line(path, fill=(255, 215, 0, 200), width=2, joint="round")
|
||||
return merged
|
||||
|
||||
def compute_stats_preview(self):
|
||||
|
|
@ -423,14 +423,12 @@ class ImageProcessingMixin:
|
|||
],
|
||||
fill=255,
|
||||
)
|
||||
elif kind == "stroke":
|
||||
elif kind == "polygon":
|
||||
points = shape.get("points")
|
||||
if not points or len(points) < 2:
|
||||
return
|
||||
base_width = float(shape.get("width", 8)) # type: ignore[arg-type]
|
||||
width_px = max(1, int(round(base_width * (scale_x + scale_y) / 2.0)))
|
||||
scaled = [(px * scale_x, py * scale_y) for px, py in points] # type: ignore[misc]
|
||||
draw.line(scaled, fill=255, width=width_px, joint="round")
|
||||
draw.polygon(scaled, fill=255)
|
||||
|
||||
def _render_exclusion_overlays(self) -> None:
|
||||
if not hasattr(self, "canvas_orig"):
|
||||
|
|
@ -449,16 +447,16 @@ class ImageProcessingMixin:
|
|||
x0, y0, x1, y1, outline="yellow", width=3
|
||||
)
|
||||
self._exclude_canvas_ids.append(item)
|
||||
elif kind == "stroke":
|
||||
elif kind == "polygon":
|
||||
points = shape.get("points")
|
||||
if not points or len(points) < 2:
|
||||
continue
|
||||
width_px = int(shape.get("width", 8))
|
||||
coords = [coord for point in points for coord in point] # type: ignore[misc]
|
||||
closed = points if points[0] == points[-1] else points + [points[0]] # type: ignore[operator]
|
||||
coords = [coord for point in closed for coord in point] # type: ignore[misc]
|
||||
item = self.canvas_orig.create_line(
|
||||
*coords,
|
||||
fill="yellow",
|
||||
width=width_px,
|
||||
width=2,
|
||||
smooth=True,
|
||||
capstyle="round",
|
||||
joinstyle="round",
|
||||
|
|
|
|||
Loading…
Reference in New Issue