Refine VideoEditor point transformation and crop handling with enhanced logging
This commit improves the point transformation methods in the VideoEditor class by incorporating interpolated positions for cropping and refining the handling of coordinates during transformations. It adds detailed logging to track the adjustments made to crop centers and the verification of transformed points, ensuring better debugging and visibility into the state of the video editing process. Additionally, it enhances bounds checking for transformed points, maintaining consistency with the original frame dimensions.
This commit is contained in:
126
croppa/main.py
126
croppa/main.py
@@ -1124,9 +1124,18 @@ class VideoEditor:
|
|||||||
center_x = x + w // 2
|
center_x = x + w // 2
|
||||||
center_y = y + h // 2
|
center_y = y + h // 2
|
||||||
|
|
||||||
# Apply offset to center
|
# Get the interpolated position for the current frame
|
||||||
new_center_x = center_x + int(tracking_offset[0])
|
current_pos = self.motion_tracker.get_interpolated_position(self.current_frame)
|
||||||
new_center_y = center_y + int(tracking_offset[1])
|
if current_pos:
|
||||||
|
# If we have a current position, center the crop directly on it
|
||||||
|
new_center_x = int(current_pos[0])
|
||||||
|
new_center_y = int(current_pos[1])
|
||||||
|
print(f"apply_crop_zoom_and_rotation: centering crop on interpolated position {current_pos}")
|
||||||
|
else:
|
||||||
|
# Otherwise use the tracking offset
|
||||||
|
new_center_x = center_x + int(tracking_offset[0])
|
||||||
|
new_center_y = center_y + int(tracking_offset[1])
|
||||||
|
print(f"apply_crop_zoom_and_rotation: applying offset to crop center: ({center_x}, {center_y}) -> ({new_center_x}, {new_center_y})")
|
||||||
|
|
||||||
# Calculate new top-left corner
|
# Calculate new top-left corner
|
||||||
x = new_center_x - w // 2
|
x = new_center_x - w // 2
|
||||||
@@ -1196,10 +1205,14 @@ class VideoEditor:
|
|||||||
1. Crop
|
1. Crop
|
||||||
2. Rotation
|
2. Rotation
|
||||||
3. Zoom
|
3. Zoom
|
||||||
|
|
||||||
|
The key insight is that we need to handle coordinates in the same way
|
||||||
|
the frame is processed in apply_crop_zoom_and_rotation.
|
||||||
"""
|
"""
|
||||||
if point is None:
|
if point is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Get original coordinates and convert to float for precise calculations
|
||||||
orig_x, orig_y = float(point[0]), float(point[1])
|
orig_x, orig_y = float(point[0]), float(point[1])
|
||||||
print(f"transform_point: original point ({orig_x}, {orig_y})")
|
print(f"transform_point: original point ({orig_x}, {orig_y})")
|
||||||
|
|
||||||
@@ -1209,12 +1222,8 @@ class VideoEditor:
|
|||||||
|
|
||||||
frame_height, frame_width = self.current_display_frame.shape[:2]
|
frame_height, frame_width = self.current_display_frame.shape[:2]
|
||||||
|
|
||||||
# Step 1: Check if point is within frame bounds
|
# STEP 1: Apply crop
|
||||||
if not (0 <= orig_x < frame_width and 0 <= orig_y < frame_height):
|
# If we're cropped, check if the point is within the crop area
|
||||||
print(f"transform_point: point is outside frame bounds ({frame_width}x{frame_height})")
|
|
||||||
# We'll still transform it, but it might not be visible
|
|
||||||
|
|
||||||
# Step 1: Apply crop (adjust point relative to crop origin)
|
|
||||||
x, y = orig_x, orig_y
|
x, y = orig_x, orig_y
|
||||||
if self.crop_rect:
|
if self.crop_rect:
|
||||||
crop_x, crop_y, crop_w, crop_h = self.crop_rect
|
crop_x, crop_y, crop_w, crop_h = self.crop_rect
|
||||||
@@ -1227,37 +1236,37 @@ class VideoEditor:
|
|||||||
# Adjust coordinates relative to crop origin
|
# Adjust coordinates relative to crop origin
|
||||||
x -= crop_x
|
x -= crop_x
|
||||||
y -= crop_y
|
y -= crop_y
|
||||||
|
|
||||||
|
# If point is outside crop area, it will have negative coordinates or coordinates > crop dimensions
|
||||||
|
# We'll still transform it for consistent behavior
|
||||||
print(f"transform_point: after crop adjustment ({x}, {y})")
|
print(f"transform_point: after crop adjustment ({x}, {y})")
|
||||||
|
|
||||||
# Step 2: Apply rotation
|
# Update dimensions for rotation calculations
|
||||||
if self.rotation_angle != 0:
|
frame_width, frame_height = crop_w, crop_h
|
||||||
# Get dimensions after crop
|
|
||||||
if self.crop_rect:
|
|
||||||
crop_w, crop_h = float(self.crop_rect[2]), float(self.crop_rect[3])
|
|
||||||
else:
|
|
||||||
crop_h, crop_w = float(frame_height), float(frame_width)
|
|
||||||
|
|
||||||
print(f"transform_point: rotation_angle = {self.rotation_angle}, dimensions = ({crop_w}, {crop_h})")
|
# STEP 2: Apply rotation
|
||||||
|
if self.rotation_angle != 0:
|
||||||
|
print(f"transform_point: rotation_angle = {self.rotation_angle}, dimensions = ({frame_width}, {frame_height})")
|
||||||
|
|
||||||
# Apply rotation to coordinates
|
# Apply rotation to coordinates
|
||||||
if self.rotation_angle == 90:
|
if self.rotation_angle == 90:
|
||||||
# 90° clockwise: (x,y) -> (y, width-x)
|
# 90° clockwise: (x,y) -> (y, width-x)
|
||||||
new_x = y
|
new_x = y
|
||||||
new_y = crop_w - x
|
new_y = frame_width - x
|
||||||
x, y = new_x, new_y
|
x, y = new_x, new_y
|
||||||
elif self.rotation_angle == 180:
|
elif self.rotation_angle == 180:
|
||||||
# 180° rotation: (x,y) -> (width-x, height-y)
|
# 180° rotation: (x,y) -> (width-x, height-y)
|
||||||
x = crop_w - x
|
x = frame_width - x
|
||||||
y = crop_h - y
|
y = frame_height - y
|
||||||
elif self.rotation_angle == 270:
|
elif self.rotation_angle == 270:
|
||||||
# 270° clockwise: (x,y) -> (height-y, x)
|
# 270° clockwise: (x,y) -> (height-y, x)
|
||||||
new_x = crop_h - y
|
new_x = frame_height - y
|
||||||
new_y = x
|
new_y = x
|
||||||
x, y = new_x, new_y
|
x, y = new_x, new_y
|
||||||
|
|
||||||
print(f"transform_point: after rotation ({x}, {y})")
|
print(f"transform_point: after rotation ({x}, {y})")
|
||||||
|
|
||||||
# Step 3: Apply zoom
|
# STEP 3: Apply zoom
|
||||||
if self.zoom_factor != 1.0:
|
if self.zoom_factor != 1.0:
|
||||||
x *= self.zoom_factor
|
x *= self.zoom_factor
|
||||||
y *= self.zoom_factor
|
y *= self.zoom_factor
|
||||||
@@ -1273,62 +1282,65 @@ class VideoEditor:
|
|||||||
1. Reverse zoom
|
1. Reverse zoom
|
||||||
2. Reverse rotation
|
2. Reverse rotation
|
||||||
3. Reverse crop
|
3. Reverse crop
|
||||||
|
|
||||||
|
The key insight is that we need to handle coordinates in the exact reverse
|
||||||
|
order as they are processed in apply_crop_zoom_and_rotation.
|
||||||
"""
|
"""
|
||||||
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
|
||||||
|
|
||||||
|
# Get display coordinates and convert to float for precise calculations
|
||||||
display_x, display_y = float(point[0]), float(point[1])
|
display_x, display_y = float(point[0]), float(point[1])
|
||||||
print(f"untransform_point: original display point ({display_x}, {display_y})")
|
print(f"untransform_point: original display point ({display_x}, {display_y})")
|
||||||
|
|
||||||
# Get frame dimensions
|
# Get frame dimensions
|
||||||
frame_height, frame_width = self.current_display_frame.shape[:2]
|
orig_frame_height, orig_frame_width = self.current_display_frame.shape[:2]
|
||||||
|
|
||||||
# Step 1: Reverse zoom
|
# Get dimensions of the frame after crop (if any)
|
||||||
|
if self.crop_rect:
|
||||||
|
frame_width, frame_height = float(self.crop_rect[2]), float(self.crop_rect[3])
|
||||||
|
else:
|
||||||
|
frame_width, frame_height = float(orig_frame_width), float(orig_frame_height)
|
||||||
|
|
||||||
|
# STEP 1: Reverse zoom
|
||||||
x, y = display_x, display_y
|
x, y = display_x, display_y
|
||||||
if self.zoom_factor != 1.0:
|
if self.zoom_factor != 1.0:
|
||||||
x /= self.zoom_factor
|
x /= self.zoom_factor
|
||||||
y /= self.zoom_factor
|
y /= self.zoom_factor
|
||||||
print(f"untransform_point: after reverse zoom ({x}, {y}), zoom_factor = {self.zoom_factor}")
|
print(f"untransform_point: after reverse zoom ({x}, {y}), zoom_factor = {self.zoom_factor}")
|
||||||
|
|
||||||
# Step 2: Reverse rotation
|
# STEP 2: Reverse rotation
|
||||||
if self.rotation_angle != 0:
|
if self.rotation_angle != 0:
|
||||||
# Get dimensions after crop but before rotation
|
print(f"untransform_point: rotation_angle = {self.rotation_angle}, dimensions = ({frame_width}, {frame_height})")
|
||||||
if self.crop_rect:
|
|
||||||
crop_w, crop_h = float(self.crop_rect[2]), float(self.crop_rect[3])
|
|
||||||
else:
|
|
||||||
crop_h, crop_w = float(frame_height), float(frame_width)
|
|
||||||
|
|
||||||
print(f"untransform_point: rotation_angle = {self.rotation_angle}, dimensions = ({crop_w}, {crop_h})")
|
|
||||||
|
|
||||||
# Apply inverse rotation to coordinates
|
# Apply inverse rotation to coordinates
|
||||||
if self.rotation_angle == 90:
|
if self.rotation_angle == 90:
|
||||||
# Reverse 90° clockwise: (x,y) -> (width-y, x)
|
# Reverse 90° clockwise: (x,y) -> (width-y, x)
|
||||||
new_x = crop_w - y
|
new_x = frame_width - y
|
||||||
new_y = x
|
new_y = x
|
||||||
x, y = new_x, new_y
|
x, y = new_x, new_y
|
||||||
elif self.rotation_angle == 180:
|
elif self.rotation_angle == 180:
|
||||||
# Reverse 180° rotation: (x,y) -> (width-x, height-y)
|
# Reverse 180° rotation: (x,y) -> (width-x, height-y)
|
||||||
x = crop_w - x
|
x = frame_width - x
|
||||||
y = crop_h - y
|
y = frame_height - y
|
||||||
elif self.rotation_angle == 270:
|
elif self.rotation_angle == 270:
|
||||||
# Reverse 270° clockwise: (x,y) -> (y, height-x)
|
# Reverse 270° clockwise: (x,y) -> (y, height-x)
|
||||||
new_x = y
|
new_x = y
|
||||||
new_y = crop_h - x
|
new_y = frame_height - x
|
||||||
x, y = new_x, new_y
|
x, y = new_x, new_y
|
||||||
|
|
||||||
print(f"untransform_point: after reverse rotation ({x}, {y})")
|
print(f"untransform_point: after reverse rotation ({x}, {y})")
|
||||||
|
|
||||||
# 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 = float(self.crop_rect[0]), float(self.crop_rect[1])
|
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
|
||||||
print(f"untransform_point: after reverse crop ({x}, {y}), crop_rect = {self.crop_rect}")
|
print(f"untransform_point: after reverse crop ({x}, {y}), crop_rect = {self.crop_rect}")
|
||||||
|
|
||||||
# Check if the resulting point is within frame bounds
|
# Ensure the point is within the original frame bounds
|
||||||
if not (0 <= x < frame_width and 0 <= y < frame_height):
|
x = max(0, min(x, orig_frame_width - 1))
|
||||||
print(f"untransform_point: result is outside frame bounds ({frame_width}x{frame_height})")
|
y = max(0, min(y, orig_frame_height - 1))
|
||||||
# We'll still return it, but it might not be visible
|
|
||||||
|
|
||||||
print(f"untransform_point: final result = ({x}, {y})")
|
print(f"untransform_point: final result = ({x}, {y})")
|
||||||
return (x, y)
|
return (x, y)
|
||||||
@@ -2191,17 +2203,6 @@ class VideoEditor:
|
|||||||
print(f"mouse_callback: untransformed to original coords {original_point}")
|
print(f"mouse_callback: untransformed to original coords {original_point}")
|
||||||
|
|
||||||
if original_point:
|
if original_point:
|
||||||
# Get original frame dimensions for validation
|
|
||||||
frame_height, frame_width = self.current_display_frame.shape[:2]
|
|
||||||
|
|
||||||
# Ensure point is within the original frame bounds
|
|
||||||
x_clamped = max(0, min(frame_width - 1, original_point[0]))
|
|
||||||
y_clamped = max(0, min(frame_height - 1, original_point[1]))
|
|
||||||
|
|
||||||
if x_clamped != original_point[0] or y_clamped != original_point[1]:
|
|
||||||
print(f"mouse_callback: clamped point from {original_point} to ({x_clamped}, {y_clamped})")
|
|
||||||
original_point = (x_clamped, y_clamped)
|
|
||||||
|
|
||||||
# Check if clicking on an existing tracking point to remove it
|
# Check if clicking on an existing tracking point to remove it
|
||||||
removed = self.motion_tracker.remove_tracking_point(
|
removed = self.motion_tracker.remove_tracking_point(
|
||||||
self.current_frame,
|
self.current_frame,
|
||||||
@@ -2221,12 +2222,21 @@ class VideoEditor:
|
|||||||
)
|
)
|
||||||
print(f"mouse_callback: added tracking point at {original_point}")
|
print(f"mouse_callback: added tracking point at {original_point}")
|
||||||
|
|
||||||
# Draw a debug marker at the exact point for visualization
|
# Perform a round-trip verification to ensure our coordinate system is consistent
|
||||||
if display_frame is not None:
|
# This will help debug any issues with the transformation
|
||||||
# Transform the point back to display coordinates for verification
|
verification_point = self.transform_point(original_point)
|
||||||
verification_point = self.transform_point(original_point)
|
if verification_point:
|
||||||
if verification_point:
|
print(f"mouse_callback: verification - point transforms back to {verification_point}")
|
||||||
print(f"mouse_callback: verification - point transforms back to {verification_point}")
|
|
||||||
|
# Calculate expected canvas position for verification
|
||||||
|
expected_x = int(offset_x + verification_point[0] * scale)
|
||||||
|
expected_y = int(offset_y + verification_point[1] * scale)
|
||||||
|
print(f"mouse_callback: verification - expected canvas position: ({expected_x}, {expected_y}), actual: ({x}, {y})")
|
||||||
|
|
||||||
|
# Calculate the error between click and expected position
|
||||||
|
error_x = abs(expected_x - x)
|
||||||
|
error_y = abs(expected_y - y)
|
||||||
|
print(f"mouse_callback: verification - position error: ({error_x}, {error_y}) pixels")
|
||||||
|
|
||||||
# Save state when tracking points change
|
# Save state when tracking points change
|
||||||
self.save_state()
|
self.save_state()
|
||||||
|
Reference in New Issue
Block a user