From 1bd935646ef27813e1cd950737721ae59b6c0448 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Wed, 17 Sep 2025 15:44:55 +0200 Subject: [PATCH] Refactor motion path and snapping logic in VideoEditor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit enhances the VideoEditor class by refining the logic for drawing motion paths and checking line snapping between tracking points. It introduces a unified approach to handle both previous→next and previous→current lines, improving the clarity of the visual representation. Additionally, debug statements have been updated to provide better insights during the snapping process, ensuring accurate tracking point interactions. The display update method is also called to refresh the visual state after changes. --- croppa/main.py | 100 +++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/croppa/main.py b/croppa/main.py index 7804abe..86fbfad 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -2033,35 +2033,42 @@ class VideoEditor: prev_result = self._get_previous_tracking_point() next_result = self._get_next_tracking_point() - # Draw motion path if we have both previous and next points + # Draw motion path - either previous→next OR previous→current + line_to_draw = None if prev_result and next_result: - prev_frame, prev_pts = prev_result - next_frame, next_pts = next_result + # Draw previous→next line + line_to_draw = ("prev_next", prev_result, next_result) + elif prev_result and self.current_frame in self.tracking_points: + # Draw previous→current line + line_to_draw = ("prev_current", prev_result, (self.current_frame, self.tracking_points[self.current_frame])) + + if line_to_draw: + line_type, (frame1, pts1), (frame2, pts2) = line_to_draw # Draw lines between corresponding tracking points - for i, (prev_rx, prev_ry) in enumerate(prev_pts): - if i < len(next_pts): - next_rx, next_ry = next_pts[i] - prev_sx, prev_sy = self._map_rotated_to_screen(prev_rx, prev_ry) - next_sx, next_sy = self._map_rotated_to_screen(next_rx, next_ry) + for i, (px1, py1) in enumerate(pts1): + if i < len(pts2): + px2, py2 = pts2[i] + sx1, sy1 = self._map_rotated_to_screen(px1, py1) + sx2, sy2 = self._map_rotated_to_screen(px2, py2) # Draw motion path line with arrow (thin and transparent) overlay = canvas.copy() - cv2.line(overlay, (prev_sx, prev_sy), (next_sx, next_sy), (255, 255, 0), 1) # Thin yellow line + cv2.line(overlay, (sx1, sy1), (sx2, sy2), (255, 255, 0), 1) # Thin yellow line - # Draw arrow head pointing from previous to next - angle = np.arctan2(next_sy - prev_sy, next_sx - prev_sx) + # Draw arrow head pointing from first to second point + angle = np.arctan2(sy2 - sy1, sx2 - sx1) arrow_length = 12 arrow_angle = np.pi / 6 # 30 degrees # Calculate arrow head points - arrow_x1 = int(next_sx - arrow_length * np.cos(angle - arrow_angle)) - arrow_y1 = int(next_sy - arrow_length * np.sin(angle - arrow_angle)) - arrow_x2 = int(next_sx - arrow_length * np.cos(angle + arrow_angle)) - arrow_y2 = int(next_sy - arrow_length * np.sin(angle + arrow_angle)) + arrow_x1 = int(sx2 - arrow_length * np.cos(angle - arrow_angle)) + arrow_y1 = int(sy2 - arrow_length * np.sin(angle - arrow_angle)) + arrow_x2 = int(sx2 - arrow_length * np.cos(angle + arrow_angle)) + arrow_y2 = int(sy2 - arrow_length * np.sin(angle + arrow_angle)) - cv2.line(overlay, (next_sx, next_sy), (arrow_x1, arrow_y1), (255, 255, 0), 1) - cv2.line(overlay, (next_sx, next_sy), (arrow_x2, arrow_y2), (255, 255, 0), 1) + cv2.line(overlay, (sx2, sy2), (arrow_x1, arrow_y1), (255, 255, 0), 1) + cv2.line(overlay, (sx2, sy2), (arrow_x2, arrow_y2), (255, 255, 0), 1) cv2.addWeighted(overlay, 0.3, canvas, 0.7, 0, canvas) # Very transparent # Previous tracking point (red) - from the most recent frame with tracking points before current @@ -2196,22 +2203,30 @@ class VideoEditor: best_snap_distance = distance best_snap_point = (int(px), int(py)) - # Check for line snapping between previous and next tracking points (the actual cyan arrows) + # Check for line snapping - either previous→next OR previous→current prev_result = self._get_previous_tracking_point() next_result = self._get_next_tracking_point() print(f"DEBUG: Line snapping - prev_result: {prev_result}, next_result: {next_result}") + # Determine which line to check: previous→next OR previous→current + line_to_check = None if prev_result and next_result: - prev_frame, prev_pts = prev_result - next_frame, next_pts = next_result + # Check previous→next line + line_to_check = ("prev_next", prev_result, next_result) + print(f"DEBUG: Checking prev→next line") + elif prev_result and self.current_frame in self.tracking_points: + # Check previous→current line + line_to_check = ("prev_current", prev_result, (self.current_frame, self.tracking_points[self.current_frame])) + print(f"DEBUG: Checking prev→current line") + + if line_to_check: + line_type, (frame1, pts1), (frame2, pts2) = line_to_check - print(f"DEBUG: Checking line between prev frame {prev_frame} and next frame {next_frame}") - - # Check each corresponding pair of points between previous and next - for j in range(min(len(prev_pts), len(next_pts))): - px1, py1 = prev_pts[j] - px2, py2 = next_pts[j] + # Check each corresponding pair of points + for j in range(min(len(pts1), len(pts2))): + px1, py1 = pts1[j] + px2, py2 = pts2[j] # Convert to screen coordinates sx1, sy1 = self._map_rotated_to_screen(px1, py1) @@ -2220,35 +2235,19 @@ class VideoEditor: # Calculate distance to infinite line and foot of perpendicular line_distance, (foot_x, foot_y) = self._point_to_line_distance_and_foot(x, y, sx1, sy1, sx2, sy2) - print(f"DEBUG: Line {j}: ({sx1},{sy1}) to ({sx2},{sy2}), distance to click ({x},{y}) = {line_distance:.2f}, foot = ({foot_x:.1f}, {foot_y:.1f})") + print(f"DEBUG: {line_type} Line {j}: ({sx1},{sy1}) to ({sx2},{sy2}), distance to click ({x},{y}) = {line_distance:.2f}, foot = ({foot_x:.1f}, {foot_y:.1f})") if line_distance <= threshold and line_distance < best_snap_distance: print(f"DEBUG: Line snap found! Distance {line_distance:.2f} <= threshold {threshold}") - # Clamp the foot to the line segment - # Calculate parameter t for the foot point - line_dx = sx2 - sx1 - line_dy = sy2 - sy1 - line_length_sq = line_dx * line_dx + line_dy * line_dy + # Convert foot of perpendicular back to rotated coordinates (no clamping - infinite line) + closest_rx, closest_ry = self._map_screen_to_rotated(int(foot_x), int(foot_y)) - if line_length_sq > 0: - t = ((foot_x - sx1) * line_dx + (foot_y - sy1) * line_dy) / line_length_sq - t = max(0, min(1, t)) # Clamp to [0, 1] - - # Calculate clamped foot point - clamped_foot_x = sx1 + t * line_dx - clamped_foot_y = sy1 + t * line_dy - - print(f"DEBUG: Clamped foot from ({foot_x:.1f}, {foot_y:.1f}) to ({clamped_foot_x:.1f}, {clamped_foot_y:.1f})") - - # Convert clamped foot back to rotated coordinates - closest_rx, closest_ry = self._map_screen_to_rotated(int(clamped_foot_x), int(clamped_foot_y)) - - best_snap_distance = line_distance - best_snap_point = (int(closest_rx), int(closest_ry)) - print(f"DEBUG: Best line snap point: ({closest_rx}, {closest_ry})") + best_snap_distance = line_distance + best_snap_point = (int(closest_rx), int(closest_ry)) + print(f"DEBUG: Best line snap point: ({closest_rx}, {closest_ry})") else: - print(f"DEBUG: No prev/next points found for line snapping") + print(f"DEBUG: No line found for snapping") # Apply the best snap if found if best_snap_point: @@ -2271,6 +2270,9 @@ class VideoEditor: self.clear_transformation_cache() self.save_state() + + # Force immediate display update to recalculate previous/next points and arrows + self.display_current_frame() # Handle scroll wheel: Ctrl+scroll -> zoom; plain scroll -> seek ±1 frame (independent of multiplier) if event == cv2.EVENT_MOUSEWHEEL: