Refactor motion path and snapping logic in VideoEditor
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.
This commit is contained in:
100
croppa/main.py
100
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:
|
||||
|
Reference in New Issue
Block a user