diff --git a/croppa/main.py b/croppa/main.py index bae2162..de49c62 100644 --- a/croppa/main.py +++ b/croppa/main.py @@ -1103,114 +1103,120 @@ class VideoEditor: """Convert screen coordinates to video frame coordinates and set crop""" x, y, w, h = screen_rect - if self.current_display_frame is not None: - # Get the original frame dimensions - original_height, original_width = self.current_display_frame.shape[:2] - available_height = self.window_height - self.TIMELINE_HEIGHT + if self.current_display_frame is None: + return - # Calculate how the original frame is displayed (after crop/zoom/rotation) - display_frame = self.apply_crop_zoom_and_rotation( - self.current_display_frame.copy() - ) - if display_frame is None: - return + # Get the original frame dimensions + original_height, original_width = self.current_display_frame.shape[:2] + available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT) - display_height, display_width = display_frame.shape[:2] + # Calculate how the original frame is displayed (after crop/zoom/rotation) + display_frame = self.apply_crop_zoom_and_rotation( + self.current_display_frame.copy() + ) + if display_frame is None: + return - # Calculate scale for the display frame - scale = min( - self.window_width / display_width, available_height / display_height - ) - if scale < 1.0: - final_display_width = int(display_width * scale) - final_display_height = int(display_height * scale) - else: - final_display_width = display_width - final_display_height = display_height - scale = 1.0 + display_height, display_width = display_frame.shape[:2] - start_x = (self.window_width - final_display_width) // 2 - start_y = (available_height - final_display_height) // 2 + # Calculate scale for the display frame + scale = min( + self.window_width / display_width, available_height / display_height + ) + if scale < 1.0: + final_display_width = int(display_width * scale) + final_display_height = int(display_height * scale) + else: + final_display_width = display_width + final_display_height = display_height + scale = 1.0 - # Convert screen coordinates to display frame coordinates - display_x = (x - start_x) / scale - display_y = (y - start_y) / scale - display_w = w / scale - display_h = h / scale + start_x = (self.window_width - final_display_width) // 2 + start_y = (available_height - final_display_height) // 2 - # Clamp to display frame bounds - display_x = max(0, min(display_x, display_width)) - display_y = max(0, min(display_y, display_height)) - display_w = min(display_w, display_width - display_x) - display_h = min(display_h, display_height - display_y) + # Convert screen coordinates to display frame coordinates + display_x = (x - start_x) / scale + display_y = (y - start_y) / scale + display_w = w / scale + display_h = h / scale - # Convert display frame coordinates back to original frame coordinates - # This is the inverse of apply_crop_zoom_and_rotation - # The order is: crop -> rotation -> zoom - # So we need to reverse: zoom -> rotation -> crop + # Clamp to display frame bounds + display_x = max(0, min(display_x, display_width)) + display_y = max(0, min(display_y, display_height)) + display_w = min(display_w, display_width - display_x) + display_h = min(display_h, display_height - display_y) - # Step 1: Reverse zoom (zoom is applied to the rotated frame) - if self.zoom_factor != 1.0: - display_x = display_x / self.zoom_factor - display_y = display_y / self.zoom_factor - display_w = display_w / self.zoom_factor - display_h = display_h / self.zoom_factor + # Now we need to convert from the display frame coordinates back to original frame coordinates + # The display frame is the result of: original -> crop -> rotation -> zoom + + # Step 1: Reverse zoom + if self.zoom_factor != 1.0: + display_x = display_x / self.zoom_factor + display_y = display_y / self.zoom_factor + display_w = display_w / self.zoom_factor + display_h = display_h / self.zoom_factor - # Step 2: Reverse rotation (rotation is applied to the cropped frame) - if self.rotation_angle != 0: - # Get the dimensions after crop but before rotation - if self.crop_rect: - crop_w, crop_h = int(self.crop_rect[2]), int(self.crop_rect[3]) - else: - crop_w, crop_h = original_width, original_height - - # Apply inverse rotation to coordinates - if self.rotation_angle == 90: - # 90° clockwise -> 270° counter-clockwise - new_x = display_y - new_y = crop_w - display_x - display_w - new_w = display_h - new_h = display_w - elif self.rotation_angle == 180: - # 180° -> 180° (same) - new_x = crop_w - display_x - display_w - new_y = crop_h - display_y - display_h - new_w = display_w - new_h = display_h - elif self.rotation_angle == 270: - # 270° clockwise -> 90° counter-clockwise - new_x = crop_h - display_y - display_h - new_y = display_x - new_w = display_h - new_h = display_w - else: - new_x, new_y, new_w, new_h = display_x, display_y, display_w, display_h - - display_x, display_y, display_w, display_h = new_x, new_y, new_w, new_h - - # Step 3: Reverse crop (crop is applied to the original frame) - original_x = display_x - original_y = display_y - original_w = display_w - original_h = display_h - - # Add the crop offset to get back to original frame coordinates + # Step 2: Reverse rotation + if self.rotation_angle != 0: + # Get the dimensions of the frame after crop but before rotation if self.crop_rect: - crop_x, crop_y, crop_w, crop_h = self.crop_rect - original_x += crop_x - original_y += crop_y + crop_w, crop_h = int(self.crop_rect[2]), int(self.crop_rect[3]) + else: + crop_w, crop_h = original_width, original_height + + # Apply inverse rotation to coordinates + # The key insight: we need to use the dimensions of the ROTATED frame for the coordinate transformation + # because the coordinates we have are in the rotated coordinate system + if self.rotation_angle == 90: + # 90° clockwise rotation: (x,y) -> (y, rotated_width-x-w) + # The rotated frame has dimensions: height x width (swapped) + rotated_w, rotated_h = crop_h, crop_w + new_x = display_y + new_y = rotated_w - display_x - display_w + new_w = display_h + new_h = display_w + elif self.rotation_angle == 180: + # 180° rotation: (x,y) -> (width-x-w, height-y-h) + new_x = crop_w - display_x - display_w + new_y = crop_h - display_y - display_h + new_w = display_w + new_h = display_h + elif self.rotation_angle == 270: + # 270° clockwise rotation: (x,y) -> (rotated_height-y-h, x) + # The rotated frame has dimensions: height x width (swapped) + rotated_w, rotated_h = crop_h, crop_w + new_x = rotated_h - display_y - display_h + new_y = display_x + new_w = display_h + new_h = display_w + else: + new_x, new_y, new_w, new_h = display_x, display_y, display_w, display_h + + display_x, display_y, display_w, display_h = new_x, new_y, new_w, new_h - # Clamp to original frame bounds - original_x = max(0, min(original_x, original_width)) - original_y = max(0, min(original_y, original_height)) - original_w = min(original_w, original_width - original_x) - original_h = min(original_h, original_height - original_y) + # Step 3: Convert from cropped frame coordinates to original frame coordinates + original_x = display_x + original_y = display_y + original_w = display_w + original_h = display_h - if original_w > 10 and original_h > 10: # Minimum size check - # Save current crop for undo - if self.crop_rect: - self.crop_history.append(self.crop_rect) - self.crop_rect = (original_x, original_y, original_w, original_h) + # Add the crop offset to get back to original frame coordinates + if self.crop_rect: + crop_x, crop_y, crop_w, crop_h = self.crop_rect + original_x += crop_x + original_y += crop_y + + # Clamp to original frame bounds + original_x = max(0, min(original_x, original_width)) + original_y = max(0, min(original_y, original_height)) + original_w = min(original_w, original_width - original_x) + original_h = min(original_h, original_height - original_y) + + if original_w > 10 and original_h > 10: # Minimum size check + # Save current crop for undo + if self.crop_rect: + self.crop_history.append(self.crop_rect) + self.crop_rect = (original_x, original_y, original_w, original_h) def seek_to_timeline_position(self, mouse_x, bar_x_start, bar_width): """Seek to position based on mouse click on timeline"""