Enhance VideoEditor with improved point transformation and tracking logic
This commit refines the point transformation process in the VideoEditor class by ensuring coordinates are converted to floats and validating their positions relative to the crop area. It also updates the right-click event handling to accurately convert display coordinates to original frame coordinates, allowing for better interaction with tracking points. Additionally, the MotionTracker class is modified to set a default zoom center based on the crop rect if none is provided, improving the tracking functionality.
This commit is contained in:
141
croppa/main.py
141
croppa/main.py
@@ -1175,11 +1175,18 @@ class VideoEditor:
|
|||||||
if point is None:
|
if point is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
x, y = point
|
x, y = float(point[0]), float(point[1])
|
||||||
|
|
||||||
# Step 1: Apply crop (adjust point relative to crop origin)
|
# Step 1: Apply crop (adjust point relative to crop origin)
|
||||||
if self.crop_rect:
|
if self.crop_rect:
|
||||||
crop_x, crop_y, _, _ = self.crop_rect
|
crop_x, crop_y, crop_w, crop_h = self.crop_rect
|
||||||
|
|
||||||
|
# Check if point is inside the crop area
|
||||||
|
if not (crop_x <= x < crop_x + crop_w and crop_y <= y < crop_y + crop_h):
|
||||||
|
# Point is outside the crop area
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Adjust coordinates relative to crop origin
|
||||||
x -= crop_x
|
x -= crop_x
|
||||||
y -= crop_y
|
y -= crop_y
|
||||||
|
|
||||||
@@ -1228,7 +1235,7 @@ class VideoEditor:
|
|||||||
if point is None or self.current_display_frame is None:
|
if point is None or self.current_display_frame is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
x, y = point
|
x, y = float(point[0]), float(point[1])
|
||||||
|
|
||||||
# Step 1: Reverse zoom
|
# Step 1: Reverse zoom
|
||||||
if self.zoom_factor != 1.0:
|
if self.zoom_factor != 1.0:
|
||||||
@@ -1239,9 +1246,10 @@ class VideoEditor:
|
|||||||
if self.rotation_angle != 0:
|
if self.rotation_angle != 0:
|
||||||
# Get dimensions after crop but before rotation
|
# Get dimensions after crop but before rotation
|
||||||
if self.crop_rect:
|
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:
|
else:
|
||||||
crop_h, crop_w = self.current_display_frame.shape[:2]
|
crop_h, crop_w = self.current_display_frame.shape[:2]
|
||||||
|
crop_h, crop_w = float(crop_h), float(crop_w)
|
||||||
|
|
||||||
# Apply inverse rotation to coordinates
|
# Apply inverse rotation to coordinates
|
||||||
if self.rotation_angle == 90:
|
if self.rotation_angle == 90:
|
||||||
@@ -1261,10 +1269,16 @@ class VideoEditor:
|
|||||||
|
|
||||||
# Step 3: Reverse crop (add crop offset)
|
# Step 3: Reverse crop (add crop offset)
|
||||||
if self.crop_rect:
|
if self.crop_rect:
|
||||||
crop_x, crop_y, _, _ = self.crop_rect
|
crop_x, crop_y = float(self.crop_rect[0]), float(self.crop_rect[1])
|
||||||
x += crop_x
|
x += crop_x
|
||||||
y += crop_y
|
y += crop_y
|
||||||
|
|
||||||
|
# Ensure coordinates are within the frame bounds
|
||||||
|
if self.current_display_frame is not None:
|
||||||
|
height, width = self.current_display_frame.shape[:2]
|
||||||
|
x = max(0, min(width - 1, x))
|
||||||
|
y = max(0, min(height - 1, y))
|
||||||
|
|
||||||
return (x, y)
|
return (x, y)
|
||||||
|
|
||||||
|
|
||||||
@@ -2003,29 +2017,60 @@ class VideoEditor:
|
|||||||
|
|
||||||
# Handle tracking points (Right-click)
|
# Handle tracking points (Right-click)
|
||||||
if event == cv2.EVENT_RBUTTONDOWN:
|
if event == cv2.EVENT_RBUTTONDOWN:
|
||||||
# Convert display coordinates to original frame coordinates
|
# First, calculate the canvas offset and scale for the current frame
|
||||||
original_point = self.untransform_point((x, y))
|
if self.current_display_frame is not None:
|
||||||
|
# Get dimensions of the transformed frame
|
||||||
if original_point:
|
display_frame = self.apply_crop_zoom_and_rotation(self.current_display_frame)
|
||||||
# Check if clicking on an existing tracking point to remove it
|
if display_frame is None:
|
||||||
removed = self.motion_tracker.remove_tracking_point(
|
return
|
||||||
self.current_frame,
|
|
||||||
original_point[0],
|
|
||||||
original_point[1],
|
|
||||||
self.tracking_point_distance
|
|
||||||
)
|
|
||||||
|
|
||||||
if not removed:
|
|
||||||
# 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]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Save state when tracking points change
|
display_height, display_width = display_frame.shape[:2]
|
||||||
self.save_state()
|
available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
|
||||||
self.display_needs_update = True
|
|
||||||
|
# Calculate scale and offset
|
||||||
|
scale = min(self.window_width / display_width, available_height / display_height)
|
||||||
|
if scale < 1.0:
|
||||||
|
final_display_width = int(display_width * scale)
|
||||||
|
final_display_height = int(display_height * scale)
|
||||||
|
else:
|
||||||
|
final_display_width = display_width
|
||||||
|
final_display_height = display_height
|
||||||
|
scale = 1.0
|
||||||
|
|
||||||
|
start_x = (self.window_width - final_display_width) // 2
|
||||||
|
start_y = (available_height - final_display_height) // 2
|
||||||
|
|
||||||
|
# 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):
|
||||||
|
|
||||||
|
# Convert screen coordinates to display frame coordinates
|
||||||
|
display_x = (x - start_x) / scale
|
||||||
|
display_y = (y - start_y) / scale
|
||||||
|
|
||||||
|
# Now convert display coordinates to original frame coordinates
|
||||||
|
original_point = self.untransform_point((display_x, display_y))
|
||||||
|
|
||||||
|
if original_point:
|
||||||
|
# Check if clicking on an existing tracking point to remove it
|
||||||
|
removed = self.motion_tracker.remove_tracking_point(
|
||||||
|
self.current_frame,
|
||||||
|
original_point[0],
|
||||||
|
original_point[1],
|
||||||
|
self.tracking_point_distance
|
||||||
|
)
|
||||||
|
|
||||||
|
if not removed:
|
||||||
|
# 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]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save state when tracking points change
|
||||||
|
self.save_state()
|
||||||
|
self.display_needs_update = True
|
||||||
|
|
||||||
# Handle zoom center (Ctrl + click)
|
# Handle zoom center (Ctrl + click)
|
||||||
if flags & cv2.EVENT_FLAG_CTRLKEY and event == cv2.EVENT_LBUTTONDOWN:
|
if flags & cv2.EVENT_FLAG_CTRLKEY and event == cv2.EVENT_LBUTTONDOWN:
|
||||||
@@ -2999,18 +3044,38 @@ class VideoEditor:
|
|||||||
self.motion_tracker.stop_tracking()
|
self.motion_tracker.stop_tracking()
|
||||||
print("Motion tracking disabled")
|
print("Motion tracking disabled")
|
||||||
else:
|
else:
|
||||||
# Start tracking with current crop and zoom center
|
# If we have tracking points, start tracking
|
||||||
base_zoom_center = self.zoom_center
|
if self.motion_tracker.has_tracking_points():
|
||||||
if not base_zoom_center and self.current_display_frame is not None:
|
# Get the current interpolated position to use as base
|
||||||
# Use frame center if no zoom center is set
|
current_pos = self.motion_tracker.get_interpolated_position(self.current_frame)
|
||||||
h, w = self.current_display_frame.shape[:2]
|
|
||||||
base_zoom_center = (w // 2, h // 2)
|
|
||||||
|
|
||||||
self.motion_tracker.start_tracking(
|
# Use crop center if we have a crop rect
|
||||||
self.crop_rect,
|
if self.crop_rect:
|
||||||
base_zoom_center
|
x, y, w, h = self.crop_rect
|
||||||
)
|
crop_center = (x + w//2, y + h//2)
|
||||||
print("Motion tracking enabled")
|
|
||||||
|
# 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
|
||||||
|
else:
|
||||||
|
# Use crop center as fallback
|
||||||
|
base_zoom_center = crop_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)
|
||||||
|
else:
|
||||||
|
base_zoom_center = None
|
||||||
|
|
||||||
|
self.motion_tracker.start_tracking(
|
||||||
|
self.crop_rect,
|
||||||
|
base_zoom_center
|
||||||
|
)
|
||||||
|
print("Motion tracking enabled")
|
||||||
|
else:
|
||||||
|
print("No tracking points available. Add tracking points with right-click first.")
|
||||||
self.save_state()
|
self.save_state()
|
||||||
else: # V - Clear all tracking points
|
else: # V - Clear all tracking points
|
||||||
self.motion_tracker.clear_tracking_points()
|
self.motion_tracker.clear_tracking_points()
|
||||||
|
@@ -126,7 +126,13 @@ class MotionTracker:
|
|||||||
"""Start motion tracking with base positions"""
|
"""Start motion tracking with base positions"""
|
||||||
self.tracking_enabled = True
|
self.tracking_enabled = True
|
||||||
self.base_crop_rect = base_crop_rect
|
self.base_crop_rect = base_crop_rect
|
||||||
self.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)
|
||||||
|
else:
|
||||||
|
self.base_zoom_center = base_zoom_center
|
||||||
|
|
||||||
def stop_tracking(self):
|
def stop_tracking(self):
|
||||||
"""Stop motion tracking"""
|
"""Stop motion tracking"""
|
||||||
|
Reference in New Issue
Block a user