From 5c44d147b09ae4ec8a5dc379edc8fd809fee728e Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Mon, 22 Dec 2025 12:30:10 +0100 Subject: [PATCH] Enhance video editing controls: add Shift+scroll functionality to expand/contract crop uniformly and implement adjust_crop_size_uniform method --- croppa/main.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/croppa/main.py b/croppa/main.py index c9df22f..e4c3dee 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -4025,7 +4025,7 @@ class VideoEditor: # 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; Shift+scroll -> expand/contract crop; plain scroll -> seek ±1 frame if event == cv2.EVENT_MOUSEWHEEL: if flags & cv2.EVENT_FLAG_CTRLKEY: if flags > 0: # Scroll up -> zoom in @@ -4033,6 +4033,12 @@ class VideoEditor: else: # Scroll down -> zoom out self.zoom_factor = max(self.MIN_ZOOM, self.zoom_factor - self.ZOOM_INCREMENT) self.clear_transformation_cache() + elif flags & cv2.EVENT_FLAG_SHIFTKEY: + # Shift+scroll -> expand/contract crop uniformly + if flags > 0: # Scroll up -> expand + self.adjust_crop_size_uniform(expand=True) + else: # Scroll down -> contract + self.adjust_crop_size_uniform(expand=False) else: if not self.is_image_mode: direction = 1 if flags > 0 else -1 @@ -4265,6 +4271,45 @@ class VideoEditor: self.clear_transformation_cache() self.save_state() # Save state when crop is adjusted + def adjust_crop_size_uniform(self, expand: bool, amount: int = None): + """Expand or contract crop uniformly in all directions + expand=False: expand (like uppercase HJKL) + expand=True: contract (like lowercase hjkl) + """ + if amount is None: + amount = self.crop_size_step + if not self.crop_rect: + center_x = self.frame_width // 2 + center_y = self.frame_height // 2 + default_size = min(self.frame_width, self.frame_height) // 4 + self.crop_rect = ( + center_x - default_size // 2, + center_y - default_size // 2, + default_size, + default_size + ) + return + + x, y, w, h = self.crop_rect + + if not expand: # expand=False means expand + # Expand uniformly from center + new_x = max(0, x - amount) + new_y = max(0, y - amount) + new_w = min(self.frame_width - new_x, w + (x - new_x) + amount) + new_h = min(self.frame_height - new_y, h + (y - new_y) + amount) + else: # expand=True means contract + # Contract uniformly toward center + contract_amount = min(amount, (w - 10) // 2, (h - 10) // 2) + new_x = x + contract_amount + new_y = y + contract_amount + new_w = max(10, w - contract_amount * 2) + new_h = max(10, h - contract_amount * 2) + + self.crop_rect = (new_x, new_y, new_w, new_h) + self.clear_transformation_cache() + self.save_state() + def render_video(self, output_path: str): """Render video or save image with current edits applied""" if self.is_image_mode: