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: