This commit is contained in:
vb
2025-11-07 13:24:30 +01:00
parent 5aba41a4f9
commit 0a83e847ca
7 changed files with 614 additions and 21 deletions

View File

@@ -49,7 +49,7 @@ class AreaSelectionHandler:
# Selection state
self._selectionMode = False
self._selectionType = "rectangle" # "rectangle", "circle", or "lasso"
self._selectionType = "rectangle" # "rectangle", "circle", "lasso", or "polygon"
self._selectionStartX = None
self._selectionStartY = None
self._selectionEndX = None
@@ -57,6 +57,9 @@ class AreaSelectionHandler:
self._selectionRectangle = None
self._lassoPath = [] # List of canvas coordinates for lasso drawing
self._lassoLine = None # Canvas line item for lasso path
self._polygonPath = [] # List of canvas coordinates for polygon points
self._polygonLines = [] # List of canvas line items for polygon drawing
self._polygonPoints = [] # List of canvas point markers for polygon vertices
# Pending operation callback (called when selection is completed)
self._pendingOperationCallback = None
@@ -120,6 +123,72 @@ class AreaSelectionHandler:
"""Set selection to lasso (free-form) mode and enable selection."""
self.set_selection_shape("lasso")
def set_polygon_selection(self) -> None:
"""Set selection to polygon mode and enable selection."""
self.set_selection_shape("polygon")
def _complete_polygon_selection(self) -> None:
"""Complete the polygon selection by converting canvas coordinates to image coordinates."""
if not self._polygonPath or len(self._polygonPath) < 3:
return
current_image = self._get_current_image()
if current_image is None:
return
pil_image = current_image.getImage()
if pil_image is None:
return
img_width, img_height = pil_image.size
# Get canvas dimensions for scaling
canvas_width, canvas_height = self._get_canvas_dimensions()
if canvas_width <= 0 or canvas_height <= 0:
return
# Calculate scaling factors
scale_x = img_width / canvas_width
scale_y = img_height / canvas_height
# Convert canvas coordinates to image coordinates
image_path = [(int(x * scale_x), int(y * scale_y)) for x, y in self._polygonPath]
# Set the polygon path in selection area
self._selectionArea.set_polygon_path(image_path)
# Clear polygon drawing elements
for line in self._polygonLines:
self._canvas.delete(line)
for point in self._polygonPoints:
self._canvas.delete(point)
self._polygonLines = []
self._polygonPoints = []
# Redraw the selection highlight to ensure it's visible
self._render_image()
# Check if there's a pending operation callback and execute it
if self._pendingOperationCallback and self._selectionArea.is_valid():
callback = self._pendingOperationCallback
self._pendingOperationCallback = None # Clear callback to prevent multiple calls
callback()
def on_double_click(self, event) -> bool:
"""Handle double-click to complete polygon selection.
Returns:
True if the double-click was handled, False otherwise
"""
if not self._selectionMode or self._selectionType != "polygon":
return False
if len(self._polygonPath) >= 3:
self._complete_polygon_selection()
return True
return False
def set_pending_operation_callback(self, callback: Optional[Callable]) -> None:
"""Set a callback to be executed when a selection is completed.
@@ -153,6 +222,45 @@ class AreaSelectionHandler:
self._lassoPath.append((canvas_x, canvas_y))
return True
# Handle polygon mode
if self._selectionType == "polygon":
# If this is the first point, clear any existing polygon
if not self._polygonPath:
# Clear any existing selection
if self._selectionArea.is_active:
self._selectionArea.clear()
# Clear any existing polygon drawing elements
for line in self._polygonLines:
self._canvas.delete(line)
for point in self._polygonPoints:
self._canvas.delete(point)
self._polygonLines = []
self._polygonPoints = []
canvas_x = self._canvas.canvasx(event.x)
canvas_y = self._canvas.canvasy(event.y)
# Add point to polygon
self._polygonPath.append((canvas_x, canvas_y))
# Draw point marker
point_marker = self._canvas.create_oval(
canvas_x - 3, canvas_y - 3, canvas_x + 3, canvas_y + 3,
fill="red", outline="red", width=2
)
self._polygonPoints.append(point_marker)
# Draw lines connecting points
if len(self._polygonPath) > 1:
prev_point = self._polygonPath[-2]
line = self._canvas.create_line(
prev_point[0], prev_point[1], canvas_x, canvas_y,
fill="red", width=2
)
self._polygonLines.append(line)
return True
# Handle rectangle/circle mode
# Clear any existing selection
self.clear_selection()
@@ -262,6 +370,10 @@ class AreaSelectionHandler:
callback()
return True
# Handle polygon mode - complete polygon on double-click
# (Polygon completion happens on double-click, not on mouse release)
return True
# Handle rectangle/circle mode
if self._selectionStartX is None:
return False
@@ -544,7 +656,14 @@ class AreaSelectionHandler:
if self._lassoLine:
self._canvas.delete(self._lassoLine)
self._lassoLine = None
for line in self._polygonLines:
self._canvas.delete(line)
for point in self._polygonPoints:
self._canvas.delete(point)
self._lassoPath = []
self._polygonPath = []
self._polygonLines = []
self._polygonPoints = []
self._selectionStartX = None
self._selectionStartY = None
self._selectionEndX = None
@@ -599,6 +718,16 @@ class AreaSelectionHandler:
*[coord for point in closed_path for coord in point],
fill="red", width=3, smooth=False
)
elif self._selectionArea.shape == "polygon" and self._selectionArea.polygon_path:
# Convert polygon path from image coordinates to canvas coordinates
canvas_path = [(int(x * scale_x), int(y * scale_y)) for x, y in self._selectionArea.polygon_path]
if len(canvas_path) >= 3:
# Close the path by adding the first point at the end
closed_path = canvas_path + [canvas_path[0]]
self._canvas.create_line(
*[coord for point in closed_path for coord in point],
fill="red", width=3, smooth=False
)
elif self._selectionArea.shape == "circle":
self._canvas.create_oval(
canvas_left, canvas_top, canvas_right, canvas_bottom,