diff --git a/.gitignore b/.gitignore index 5663554..babaa71 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__ croppa/build/lib croppa/croppa.egg-info +*.log diff --git a/croppa/main.py b/croppa/main.py index 00ee876..df65f8c 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -1120,8 +1120,18 @@ class VideoEditor: # Only apply offset if it's not zero if tracking_offset[0] != 0 or tracking_offset[1] != 0: - x += int(tracking_offset[0]) - y += int(tracking_offset[1]) + # Calculate the center of the crop rect + center_x = x + w // 2 + center_y = y + h // 2 + + # Apply offset to center + new_center_x = center_x + int(tracking_offset[0]) + new_center_y = center_y + int(tracking_offset[1]) + + # Calculate new top-left corner + x = new_center_x - w // 2 + y = new_center_y - h // 2 + print(f"apply_crop_zoom_and_rotation: adjusted crop position to ({x}, {y})") x, y, w, h = int(x), int(y), int(w), int(h) @@ -1192,7 +1202,6 @@ class VideoEditor: x, y = float(point[0]), float(point[1]) - # Log original point print(f"transform_point: original point ({x}, {y})") # Step 1: Apply crop (adjust point relative to crop origin) @@ -1200,8 +1209,7 @@ class VideoEditor: crop_x, crop_y, crop_w, crop_h = self.crop_rect print(f"transform_point: crop_rect = {self.crop_rect}") - # Check if point is inside the crop area - but don't filter out points - # We'll still transform them and let the drawing code decide visibility + # Check if point is inside the crop area is_inside = (crop_x <= x < crop_x + crop_w and crop_y <= y < crop_y + crop_h) print(f"transform_point: point ({x}, {y}) is {'inside' if is_inside else 'outside'} crop area") @@ -1214,10 +1222,11 @@ class VideoEditor: if self.rotation_angle != 0: # Get dimensions after crop if self.crop_rect: - crop_w, crop_h = self.crop_rect[2], self.crop_rect[3] + crop_w, crop_h = float(self.crop_rect[2]), float(self.crop_rect[3]) else: if self.current_display_frame is not None: crop_h, crop_w = self.current_display_frame.shape[:2] + crop_h, crop_w = float(crop_h), float(crop_w) else: return None @@ -1306,15 +1315,9 @@ class VideoEditor: y += crop_y print(f"untransform_point: after reverse crop ({x}, {y}), crop_rect = {self.crop_rect}") - # Ensure coordinates are within the frame bounds - if self.current_display_frame is not None: - height, width = self.current_display_frame.shape[:2] - orig_x, orig_y = x, y - x = max(0, min(width - 1, x)) - y = max(0, min(height - 1, y)) - if orig_x != x or orig_y != y: - print(f"untransform_point: clamped from ({orig_x}, {orig_y}) to ({x}, {y})") - + # Don't clamp coordinates - allow points outside frame bounds + # This is important for tracking points that may be outside the current crop + print(f"untransform_point: final result = ({x}, {y})") return (x, y) @@ -1495,11 +1498,14 @@ class VideoEditor: tracking_points = self.motion_tracker.get_tracking_points_for_frame(self.current_frame) print(f"draw_tracking_points: found {len(tracking_points)} tracking points for frame {self.current_frame}") + # Get current frame dimensions for bounds checking + frame_height, frame_width = self.current_display_frame.shape[:2] + for i, point in enumerate(tracking_points): print(f"draw_tracking_points: processing point {i}: {point}") - # Transform point from original frame coordinates to display coordinates - display_point = self.transform_point(point) + # Check if the point is within the frame bounds + is_in_frame = (0 <= point[0] < frame_width and 0 <= point[1] < frame_height) # Check if the point is within the crop area (if cropping is active) is_in_crop = True @@ -1507,8 +1513,11 @@ class VideoEditor: crop_x, crop_y, crop_w, crop_h = self.crop_rect is_in_crop = (crop_x <= point[0] < crop_x + crop_w and crop_y <= point[1] < crop_y + crop_h) - - if display_point: + + # Transform point from original frame coordinates to display coordinates + display_point = self.transform_point(point) + + if display_point is not None: print(f"draw_tracking_points: point {i} transformed to {display_point}") # Scale and offset the point to match the canvas @@ -1517,19 +1526,25 @@ class VideoEditor: print(f"draw_tracking_points: point {i} canvas position: ({x},{y})") - # Draw the point - use different colors based on whether it's in the crop area - if is_in_crop: - # Point is in crop area - draw normally - # Draw white border - cv2.circle(canvas, (x, y), self.tracking_point_radius + 2, (255, 255, 255), 2) - # Draw green circle - cv2.circle(canvas, (x, y), self.tracking_point_radius, (0, 255, 0), -1) + # Check if the point is within the canvas bounds + is_on_canvas = (0 <= x < self.window_width and 0 <= y < self.window_height) + + if is_on_canvas: + # Draw the point - use different colors based on whether it's in the crop area + if is_in_crop: + # Point is in crop area - draw normally + # Draw white border + cv2.circle(canvas, (x, y), self.tracking_point_radius + 2, (255, 255, 255), 2) + # Draw green circle + cv2.circle(canvas, (x, y), self.tracking_point_radius, (0, 255, 0), -1) + else: + # Point is outside crop area - draw with different color + # Draw gray border + cv2.circle(canvas, (x, y), self.tracking_point_radius + 2, (128, 128, 128), 2) + # Draw yellow circle + cv2.circle(canvas, (x, y), self.tracking_point_radius, (0, 255, 255), -1) else: - # Point is outside crop area - draw with different color (semi-transparent) - # Draw gray border - cv2.circle(canvas, (x, y), self.tracking_point_radius + 2, (128, 128, 128), 2) - # Draw yellow circle - cv2.circle(canvas, (x, y), self.tracking_point_radius, (0, 255, 255), -1) + print(f"draw_tracking_points: point {i} is outside canvas bounds") else: print(f"draw_tracking_points: point {i} not visible in current view") @@ -2112,6 +2127,8 @@ class VideoEditor: start_x = (self.window_width - final_display_width) // 2 start_y = (available_height - final_display_height) // 2 + print(f"mouse_callback: right-click at ({x}, {y}), display frame at ({start_x}, {start_y}), scale={scale}") + # Check if click is within the frame area if (start_x <= x < start_x + final_display_width and start_y <= y < start_y + final_display_height): @@ -2120,9 +2137,13 @@ class VideoEditor: display_x = (x - start_x) / scale display_y = (y - start_y) / scale + print(f"mouse_callback: converted to display coords ({display_x}, {display_y})") + # Now convert display coordinates to original frame coordinates original_point = self.untransform_point((display_x, display_y)) + print(f"mouse_callback: untransformed to original coords {original_point}") + if original_point: # Check if clicking on an existing tracking point to remove it removed = self.motion_tracker.remove_tracking_point( @@ -2132,13 +2153,16 @@ class VideoEditor: self.tracking_point_distance ) - if not removed: + if removed: + print(f"mouse_callback: removed tracking point at {original_point}") + else: # If no point was removed, add a new tracking point self.motion_tracker.add_tracking_point( self.current_frame, original_point[0], original_point[1] ) + print(f"mouse_callback: added tracking point at {original_point}") # Save state when tracking points change self.save_state() @@ -3115,7 +3139,6 @@ class VideoEditor: if self.motion_tracker.tracking_enabled: self.motion_tracker.stop_tracking() print("Motion tracking disabled") - print("Motion tracking disabled") else: # If we have tracking points, start tracking if self.motion_tracker.has_tracking_points(): @@ -3123,33 +3146,28 @@ class VideoEditor: current_pos = self.motion_tracker.get_interpolated_position(self.current_frame) print(f"Toggle tracking: interpolated position = {current_pos}") + # Always use the current position as the base zoom center if available + if current_pos: + base_zoom_center = current_pos + print(f"Toggle tracking: using interpolated position as base: {base_zoom_center}") # Use crop center if we have a crop rect - if self.crop_rect: + elif self.crop_rect: x, y, w, h = self.crop_rect - crop_center = (x + w//2, y + h//2) - print(f"Toggle tracking: crop_rect = {self.crop_rect}, crop_center = {crop_center}") - - # If we have a current position from tracking points, use that as base - if current_pos: - # The base zoom center is the current position - base_zoom_center = current_pos - print(f"Toggle tracking: using interpolated position as base: {base_zoom_center}") - else: - # Use crop center as fallback - base_zoom_center = crop_center - print(f"Toggle tracking: using crop center as base: {base_zoom_center}") + base_zoom_center = (x + w//2, y + h//2) + print(f"Toggle tracking: using crop center as base: {base_zoom_center}") + # No crop rect, use frame center + elif self.current_display_frame is not None: + h, w = self.current_display_frame.shape[:2] + base_zoom_center = (w // 2, h // 2) + print(f"Toggle tracking: using frame center as base: {base_zoom_center}") else: - # No crop rect, use frame center - if self.current_display_frame is not None: - h, w = self.current_display_frame.shape[:2] - base_zoom_center = (w // 2, h // 2) - print(f"Toggle tracking: using frame center as base: {base_zoom_center}") - else: - base_zoom_center = None - print("Toggle tracking: no base center available") + base_zoom_center = None + print("Toggle tracking: no base center available") + + # Use current crop rect as base + base_crop_rect = self.crop_rect # Create a crop rect if one doesn't exist - base_crop_rect = self.crop_rect if not base_crop_rect and current_pos and self.current_display_frame is not None: # Create a default crop rect centered on the current position h, w = self.current_display_frame.shape[:2] @@ -3157,6 +3175,8 @@ class VideoEditor: x = max(0, int(current_pos[0] - crop_size // 2)) y = max(0, int(current_pos[1] - crop_size // 2)) base_crop_rect = (x, y, crop_size, crop_size) + # Update the actual crop rect + self.crop_rect = base_crop_rect print(f"Toggle tracking: created default crop rect: {base_crop_rect}") self.motion_tracker.start_tracking( @@ -3164,10 +3184,8 @@ class VideoEditor: base_zoom_center ) print("Motion tracking enabled") - print(f"Motion tracking enabled with base_crop_rect={base_crop_rect}, base_zoom_center={base_zoom_center}") else: print("No tracking points available. Add tracking points with right-click first.") - print("Motion tracking not enabled - no tracking points available") self.save_state() else: # V - Clear all tracking points self.motion_tracker.clear_tracking_points() diff --git a/croppa/tracking.py b/croppa/tracking.py index e632e8e..46bb899 100644 --- a/croppa/tracking.py +++ b/croppa/tracking.py @@ -137,12 +137,16 @@ class MotionTracker: self.tracking_enabled = True self.base_crop_rect = base_crop_rect + print(f"start_tracking: base_crop_rect={base_crop_rect}, base_zoom_center={base_zoom_center}") + # If no base_zoom_center is provided, use the center of the crop rect if base_zoom_center is None and base_crop_rect is not None: x, y, w, h = base_crop_rect self.base_zoom_center = (x + w//2, y + h//2) + print(f"start_tracking: using crop center as base_zoom_center: {self.base_zoom_center}") else: self.base_zoom_center = base_zoom_center + print(f"start_tracking: using provided base_zoom_center: {self.base_zoom_center}") def stop_tracking(self): """Stop motion tracking"""