Add crop border dragging functionality to VideoEditor class

Implement mouse event handling for dragging crop borders, allowing users to adjust crop dimensions interactively. Introduce methods to detect border proximity and update crop rectangle based on mouse movements, ensuring proper clamping to frame bounds. This enhances the user experience by providing more precise control over cropping in video editing.
This commit is contained in:
2025-12-23 09:02:06 +01:00
parent a369b84d39
commit 99fbfa3201

View File

@@ -806,6 +806,10 @@ class VideoEditor:
self.crop_start_point = None self.crop_start_point = None
self.crop_preview_rect = None self.crop_preview_rect = None
self.crop_history = [] # For undo self.crop_history = [] # For undo
self.crop_border_dragging = False
self.crop_border_drag_edge = None # 'left', 'right', 'top', 'bottom'
self.crop_border_drag_start_pos = None # (screen_x, screen_y) when drag started
self.crop_border_drag_start_rect = None # (x, y, w, h) in rotated coords when drag started
# Zoom settings # Zoom settings
self.zoom_factor = 1.0 self.zoom_factor = 1.0
@@ -3767,6 +3771,85 @@ class VideoEditor:
self.mouse_dragging = False self.mouse_dragging = False
return return
# Handle crop border dragging (only when Shift is NOT pressed)
if not (flags & cv2.EVENT_FLAG_SHIFTKEY) and self.crop_rect:
border_threshold = 10 # pixels
# Get effective crop in rotated coords and map to screen
eff_x, eff_y, eff_w, eff_h = self._get_effective_crop_rect_for_frame(getattr(self, 'current_frame', 0))
sx1, sy1 = self._map_rotated_to_screen(eff_x, eff_y)
sx2, sy2 = self._map_rotated_to_screen(eff_x + eff_w, eff_y + eff_h)
# Detect which border is near
def detect_border():
if abs(x - sx1) < border_threshold and sy1 <= y <= sy2:
return 'left'
elif abs(x - sx2) < border_threshold and sy1 <= y <= sy2:
return 'right'
elif abs(y - sy1) < border_threshold and sx1 <= x <= sx2:
return 'top'
elif abs(y - sy2) < border_threshold and sx1 <= x <= sx2:
return 'bottom'
return None
if event == cv2.EVENT_LBUTTONDOWN:
edge = detect_border()
if edge:
self.crop_border_dragging = True
self.crop_border_drag_edge = edge
self.crop_border_drag_start_pos = (x, y)
self.crop_border_drag_start_rect = (eff_x, eff_y, eff_w, eff_h)
elif event == cv2.EVENT_MOUSEMOVE and self.crop_border_dragging:
if self.crop_border_drag_edge and self.crop_border_drag_start_pos and self.crop_border_drag_start_rect:
# Convert mouse movement from screen to rotated coords
start_sx, start_sy = self.crop_border_drag_start_pos
start_rx, start_ry = self._map_screen_to_rotated(start_sx, start_sy)
curr_rx, curr_ry = self._map_screen_to_rotated(x, y)
dx_r = curr_rx - start_rx
dy_r = curr_ry - start_ry
# Adjust the appropriate border
new_x, new_y, new_w, new_h = self.crop_border_drag_start_rect
# Get rotated frame dimensions
if self.rotation_angle in (90, 270):
rot_w, rot_h = self.frame_height, self.frame_width
else:
rot_w, rot_h = self.frame_width, self.frame_height
if self.crop_border_drag_edge == 'left':
new_x = max(0, new_x + dx_r)
new_w = new_w - dx_r
if new_w < 10:
new_w = 10
new_x = self.crop_border_drag_start_rect[0] + self.crop_border_drag_start_rect[2] - 10
elif self.crop_border_drag_edge == 'right':
new_w = max(10, new_w + dx_r)
if new_x + new_w > rot_w:
new_w = rot_w - new_x
elif self.crop_border_drag_edge == 'top':
new_y = max(0, new_y + dy_r)
new_h = new_h - dy_r
if new_h < 10:
new_h = 10
new_y = self.crop_border_drag_start_rect[1] + self.crop_border_drag_start_rect[3] - 10
elif self.crop_border_drag_edge == 'bottom':
new_h = max(10, new_h + dy_r)
if new_y + new_h > rot_h:
new_h = rot_h - new_y
# Convert back from rotated to original frame coords
self._set_crop_from_rotated_rect((new_x, new_y, new_w, new_h))
self.clear_transformation_cache()
self.display_current_frame()
elif event == cv2.EVENT_LBUTTONUP and self.crop_border_dragging:
self.crop_border_dragging = False
self.crop_border_drag_edge = None
self.crop_border_drag_start_pos = None
self.crop_border_drag_start_rect = None
self.save_state()
# Handle crop selection (Shift + click and drag) # Handle crop selection (Shift + click and drag)
if flags & cv2.EVENT_FLAG_SHIFTKEY: if flags & cv2.EVENT_FLAG_SHIFTKEY:
@@ -4044,6 +4127,32 @@ class VideoEditor:
direction = 1 if flags > 0 else -1 direction = 1 if flags > 0 else -1
self.seek_video_exact_frame(direction) self.seek_video_exact_frame(direction)
def _set_crop_from_rotated_rect(self, rotated_rect):
"""Set crop_rect from a rectangle in rotated frame coordinates"""
rx, ry, rw, rh = rotated_rect
# Convert from rotated coords to original frame coords
# Rotation is applied clockwise: 90° means ROTATE_90_CLOCKWISE
if self.rotation_angle == 0:
self.crop_rect = (rx, ry, rw, rh)
elif self.rotation_angle == 90:
# 90° clockwise: (rx, ry, rw, rh) rotated -> (ry, frame_height - rx - rw, rh, rw) original
self.crop_rect = (ry, self.frame_height - rx - rw, rh, rw)
elif self.rotation_angle == 180:
# 180°: (rx, ry, rw, rh) rotated -> (frame_width - rx - rw, frame_height - ry - rh, rw, rh) original
self.crop_rect = (self.frame_width - rx - rw, self.frame_height - ry - rh, rw, rh)
elif self.rotation_angle == 270:
# 270° (90° counterclockwise): (rx, ry, rw, rh) rotated -> (frame_width - ry - rh, rx, rh, rw) original
self.crop_rect = (self.frame_width - ry - rh, rx, rh, rw)
# Clamp to frame bounds
x, y, w, h = self.crop_rect
x = max(0, min(x, self.frame_width - 1))
y = max(0, min(y, self.frame_height - 1))
w = min(w, self.frame_width - x)
h = min(h, self.frame_height - y)
self.crop_rect = (x, y, w, h)
def set_crop_from_screen_coords(self, screen_rect): def set_crop_from_screen_coords(self, screen_rect):
"""Convert screen coordinates to video frame coordinates and set crop""" """Convert screen coordinates to video frame coordinates and set crop"""
x, y, w, h = screen_rect x, y, w, h = screen_rect