Compare commits
3 Commits
0570256f65
...
b123b12d0d
Author | SHA1 | Date | |
---|---|---|---|
b123b12d0d | |||
1bd935646e | |||
c3e0088a60 |
106
croppa/main.py
106
croppa/main.py
@@ -510,7 +510,7 @@ class VideoEditor:
|
|||||||
CROP_SIZE_STEP = 15 # pixels to expand/contract crop
|
CROP_SIZE_STEP = 15 # pixels to expand/contract crop
|
||||||
|
|
||||||
# Motion tracking settings
|
# Motion tracking settings
|
||||||
TRACKING_POINT_THRESHOLD = 20 # pixels for delete/snap radius
|
TRACKING_POINT_THRESHOLD = 10 # pixels for delete/snap radius
|
||||||
|
|
||||||
# Seek frame counts
|
# Seek frame counts
|
||||||
SEEK_FRAMES_CTRL = 60 # Ctrl modifier: 60 frames
|
SEEK_FRAMES_CTRL = 60 # Ctrl modifier: 60 frames
|
||||||
@@ -2033,35 +2033,42 @@ class VideoEditor:
|
|||||||
prev_result = self._get_previous_tracking_point()
|
prev_result = self._get_previous_tracking_point()
|
||||||
next_result = self._get_next_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→current OR previous→next
|
||||||
if prev_result and next_result:
|
line_to_draw = None
|
||||||
prev_frame, prev_pts = prev_result
|
if prev_result and self.current_frame in self.tracking_points:
|
||||||
next_frame, next_pts = next_result
|
# Draw previous→current line (we're on a frame with tracking points)
|
||||||
|
line_to_draw = ("prev_current", prev_result, (self.current_frame, self.tracking_points[self.current_frame]))
|
||||||
|
elif prev_result and next_result:
|
||||||
|
# Draw previous→next line (we're between frames)
|
||||||
|
line_to_draw = ("prev_next", prev_result, next_result)
|
||||||
|
|
||||||
|
if line_to_draw:
|
||||||
|
line_type, (frame1, pts1), (frame2, pts2) = line_to_draw
|
||||||
|
|
||||||
# Draw lines between corresponding tracking points
|
# Draw lines between corresponding tracking points
|
||||||
for i, (prev_rx, prev_ry) in enumerate(prev_pts):
|
for i, (px1, py1) in enumerate(pts1):
|
||||||
if i < len(next_pts):
|
if i < len(pts2):
|
||||||
next_rx, next_ry = next_pts[i]
|
px2, py2 = pts2[i]
|
||||||
prev_sx, prev_sy = self._map_rotated_to_screen(prev_rx, prev_ry)
|
sx1, sy1 = self._map_rotated_to_screen(px1, py1)
|
||||||
next_sx, next_sy = self._map_rotated_to_screen(next_rx, next_ry)
|
sx2, sy2 = self._map_rotated_to_screen(px2, py2)
|
||||||
|
|
||||||
# Draw motion path line with arrow (thin and transparent)
|
# Draw motion path line with arrow (thin and transparent)
|
||||||
overlay = canvas.copy()
|
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
|
# Draw arrow head pointing from first to second point
|
||||||
angle = np.arctan2(next_sy - prev_sy, next_sx - prev_sx)
|
angle = np.arctan2(sy2 - sy1, sx2 - sx1)
|
||||||
arrow_length = 12
|
arrow_length = 12
|
||||||
arrow_angle = np.pi / 6 # 30 degrees
|
arrow_angle = np.pi / 6 # 30 degrees
|
||||||
|
|
||||||
# Calculate arrow head points
|
# Calculate arrow head points
|
||||||
arrow_x1 = int(next_sx - arrow_length * np.cos(angle - arrow_angle))
|
arrow_x1 = int(sx2 - arrow_length * np.cos(angle - arrow_angle))
|
||||||
arrow_y1 = int(next_sy - arrow_length * np.sin(angle - arrow_angle))
|
arrow_y1 = int(sy2 - arrow_length * np.sin(angle - arrow_angle))
|
||||||
arrow_x2 = int(next_sx - arrow_length * np.cos(angle + arrow_angle))
|
arrow_x2 = int(sx2 - arrow_length * np.cos(angle + arrow_angle))
|
||||||
arrow_y2 = int(next_sy - arrow_length * np.sin(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, (sx2, sy2), (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_x2, arrow_y2), (255, 255, 0), 1)
|
||||||
cv2.addWeighted(overlay, 0.3, canvas, 0.7, 0, canvas) # Very transparent
|
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
|
# 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_distance = distance
|
||||||
best_snap_point = (int(px), int(py))
|
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()
|
prev_result = self._get_previous_tracking_point()
|
||||||
next_result = self._get_next_tracking_point()
|
next_result = self._get_next_tracking_point()
|
||||||
|
|
||||||
print(f"DEBUG: Line snapping - prev_result: {prev_result}, next_result: {next_result}")
|
print(f"DEBUG: Line snapping - prev_result: {prev_result}, next_result: {next_result}")
|
||||||
|
|
||||||
if prev_result and next_result:
|
# Determine which line to check: previous→current OR previous→next
|
||||||
prev_frame, prev_pts = prev_result
|
line_to_check = None
|
||||||
next_frame, next_pts = next_result
|
if prev_result and self.current_frame in self.tracking_points:
|
||||||
|
# Check previous→current line (we're on a frame with tracking points)
|
||||||
|
line_to_check = ("prev_current", prev_result, (self.current_frame, self.tracking_points[self.current_frame]))
|
||||||
|
print(f"DEBUG: Checking prev->current line")
|
||||||
|
elif prev_result and next_result:
|
||||||
|
# Check previous→next line (we're between frames)
|
||||||
|
line_to_check = ("prev_next", prev_result, next_result)
|
||||||
|
print(f"DEBUG: Checking prev->next 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
|
||||||
|
for j in range(min(len(pts1), len(pts2))):
|
||||||
# Check each corresponding pair of points between previous and next
|
px1, py1 = pts1[j]
|
||||||
for j in range(min(len(prev_pts), len(next_pts))):
|
px2, py2 = pts2[j]
|
||||||
px1, py1 = prev_pts[j]
|
|
||||||
px2, py2 = next_pts[j]
|
|
||||||
|
|
||||||
# Convert to screen coordinates
|
# Convert to screen coordinates
|
||||||
sx1, sy1 = self._map_rotated_to_screen(px1, py1)
|
sx1, sy1 = self._map_rotated_to_screen(px1, py1)
|
||||||
@@ -2220,35 +2235,19 @@ class VideoEditor:
|
|||||||
# Calculate distance to infinite line and foot of perpendicular
|
# 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)
|
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:
|
if line_distance <= threshold and line_distance < best_snap_distance:
|
||||||
print(f"DEBUG: Line snap found! Distance {line_distance:.2f} <= threshold {threshold}")
|
print(f"DEBUG: Line snap found! Distance {line_distance:.2f} <= threshold {threshold}")
|
||||||
|
|
||||||
# Clamp the foot to the line segment
|
# Convert foot of perpendicular back to rotated coordinates (no clamping - infinite line)
|
||||||
# Calculate parameter t for the foot point
|
closest_rx, closest_ry = self._map_screen_to_rotated(int(foot_x), int(foot_y))
|
||||||
line_dx = sx2 - sx1
|
|
||||||
line_dy = sy2 - sy1
|
|
||||||
line_length_sq = line_dx * line_dx + line_dy * line_dy
|
|
||||||
|
|
||||||
if line_length_sq > 0:
|
best_snap_distance = line_distance
|
||||||
t = ((foot_x - sx1) * line_dx + (foot_y - sy1) * line_dy) / line_length_sq
|
best_snap_point = (int(closest_rx), int(closest_ry))
|
||||||
t = max(0, min(1, t)) # Clamp to [0, 1]
|
print(f"DEBUG: Best line snap point: ({closest_rx}, {closest_ry})")
|
||||||
|
|
||||||
# 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})")
|
|
||||||
else:
|
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
|
# Apply the best snap if found
|
||||||
if best_snap_point:
|
if best_snap_point:
|
||||||
@@ -2271,6 +2270,9 @@ class VideoEditor:
|
|||||||
|
|
||||||
self.clear_transformation_cache()
|
self.clear_transformation_cache()
|
||||||
self.save_state()
|
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)
|
# Handle scroll wheel: Ctrl+scroll -> zoom; plain scroll -> seek ±1 frame (independent of multiplier)
|
||||||
if event == cv2.EVENT_MOUSEWHEEL:
|
if event == cv2.EVENT_MOUSEWHEEL:
|
||||||
|
Reference in New Issue
Block a user