Implement unified display parameters in VideoEditor for improved cropping and zoom handling

This commit introduces a new method, _get_display_params, in the VideoEditor class to centralize the calculation of display parameters, including effective crop dimensions, offsets, and scaling factors. The coordinate mapping methods have been updated to utilize these unified parameters, enhancing the accuracy of point mapping between original and screen coordinates during zoom and cropping operations. This refactor improves code readability and maintainability while ensuring a consistent user experience in video editing.
This commit is contained in:
2025-09-17 08:06:52 +02:00
parent 1d987a341a
commit f1d4145e43

View File

@@ -1195,6 +1195,38 @@ class VideoEditor:
return (x1 + t * (x2 - x1), y1 + t * (y2 - y1)) return (x1 + t * (x2 - x1), y1 + t * (y2 - y1))
return None return None
def _get_display_params(self):
"""Unified display transform parameters for current frame in rotated space."""
eff_x, eff_y, eff_w, eff_h = self._get_effective_crop_rect_for_frame(getattr(self, 'current_frame', 0))
new_w = int(eff_w * self.zoom_factor)
new_h = int(eff_h * self.zoom_factor)
cropped_due_to_zoom = (self.zoom_factor != 1.0) and (new_w > self.window_width or new_h > self.window_height)
if cropped_due_to_zoom:
offx_max = max(0, new_w - self.window_width)
offy_max = max(0, new_h - self.window_height)
offx = max(0, min(int(self.display_offset[0]), offx_max))
offy = max(0, min(int(self.display_offset[1]), offy_max))
visible_w = self.window_width
visible_h = self.window_height
else:
offx = 0
offy = 0
visible_w = new_w
visible_h = new_h
available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
scale_raw = min(self.window_width / max(1, visible_w), available_height / max(1, visible_h))
scale = scale_raw if scale_raw < 1.0 else 1.0
final_w = int(visible_w * scale)
final_h = int(visible_h * scale)
start_x = (self.window_width - final_w) // 2
start_y = (available_height - final_h) // 2
return {
'eff_x': eff_x, 'eff_y': eff_y, 'eff_w': eff_w, 'eff_h': eff_h,
'offx': offx, 'offy': offy,
'scale': scale,
'start_x': start_x, 'start_y': start_y
}
def _map_original_to_screen(self, ox, oy): def _map_original_to_screen(self, ox, oy):
"""Map a point in original frame coords to canvas screen coords.""" """Map a point in original frame coords to canvas screen coords."""
frame_number = getattr(self, 'current_frame', 0) frame_number = getattr(self, 'current_frame', 0)
@@ -1208,46 +1240,16 @@ class VideoEditor:
rx, ry = self.frame_height - 1 - oy, ox rx, ry = self.frame_height - 1 - oy, ox
else: else:
rx, ry = ox, oy rx, ry = ox, oy
# Now account for crop in rotated space # Now account for crop/zoom/offset using unified params
cx, cy, cw, ch = self._get_effective_crop_rect_for_frame(frame_number) params = self._get_display_params()
rx -= cx rx -= params['eff_x']
ry -= cy ry -= params['eff_y']
# Dimensions after rotation
if angle in (90, 270):
rotated_w, rotated_h = ch, cw
else:
rotated_w, rotated_h = cw, ch
# Zoom
zx = rx * self.zoom_factor zx = rx * self.zoom_factor
zy = ry * self.zoom_factor zy = ry * self.zoom_factor
# Zoomed dimensions inframe_x = zx - params['offx']
new_w = int(rotated_w * self.zoom_factor) inframe_y = zy - params['offy']
new_h = int(rotated_h * self.zoom_factor) sx = int(round(params['start_x'] + inframe_x * params['scale']))
# Whether apply_crop_zoom_and_rotation cropped due to zoom sy = int(round(params['start_y'] + inframe_y * params['scale']))
cropped_due_to_zoom = (self.zoom_factor != 1.0) and (new_w > self.window_width or new_h > self.window_height)
# Display offset in zoomed space only applies if cropped_due_to_zoom
if cropped_due_to_zoom:
offx_max = max(0, new_w - self.window_width)
offy_max = max(0, new_h - self.window_height)
offx = max(0, min(int(self.display_offset[0]), offx_max))
offy = max(0, min(int(self.display_offset[1]), offy_max))
else:
offx = 0
offy = 0
inframe_x = zx - offx
inframe_y = zy - offy
# Visible dimensions before canvas scaling (what display_current_frame receives)
visible_w = new_w if not cropped_due_to_zoom else min(new_w, self.window_width)
visible_h = new_h if not cropped_due_to_zoom else min(new_h, self.window_height)
# Canvas scale and placement (matches display_current_frame downscale and centering)
available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
scale_canvas = min(self.window_width / max(1, visible_w), available_height / max(1, visible_h))
final_w = int(visible_w * scale_canvas)
final_h = int(visible_h * scale_canvas)
start_x_canvas = (self.window_width - final_w) // 2
start_y_canvas = (available_height - final_h) // 2
sx = int(round(start_x_canvas + inframe_x * scale_canvas))
sy = int(round(start_y_canvas + inframe_y * scale_canvas))
return sx, sy return sx, sy
def _map_screen_to_original(self, sx, sy): def _map_screen_to_original(self, sx, sy):
@@ -1269,7 +1271,8 @@ class VideoEditor:
visible_h = new_h if not cropped_due_to_zoom else min(new_h, self.window_height) visible_h = new_h if not cropped_due_to_zoom else min(new_h, self.window_height)
# Canvas scale and placement # Canvas scale and placement
available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT) available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
scale_canvas = min(self.window_width / max(1, visible_w), available_height / max(1, visible_h)) scale_raw = min(self.window_width / max(1, visible_w), available_height / max(1, visible_h))
scale_canvas = scale_raw if scale_raw < 1.0 else 1.0
final_w = int(visible_w * scale_canvas) final_w = int(visible_w * scale_canvas)
final_h = int(visible_h * scale_canvas) final_h = int(visible_h * scale_canvas)
start_x_canvas = (self.window_width - final_w) // 2 start_x_canvas = (self.window_width - final_w) // 2
@@ -1345,38 +1348,19 @@ class VideoEditor:
"""Map a point on canvas screen coords back to ROTATED frame coords (pre-crop).""" """Map a point on canvas screen coords back to ROTATED frame coords (pre-crop)."""
frame_number = getattr(self, 'current_frame', 0) frame_number = getattr(self, 'current_frame', 0)
angle = self.rotation_angle angle = self.rotation_angle
# Use EFFECTIVE crop dims as the displayed base after rotation # Use unified display params
cx, cy, cw, ch = self._get_effective_crop_rect_for_frame(frame_number) params = self._get_display_params()
new_w = int(cw * self.zoom_factor)
new_h = int(ch * self.zoom_factor)
cropped_due_to_zoom = (self.zoom_factor != 1.0) and (new_w > self.window_width or new_h > self.window_height)
visible_w = new_w if not cropped_due_to_zoom else min(new_w, self.window_width)
visible_h = new_h if not cropped_due_to_zoom else min(new_h, self.window_height)
available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
scale_canvas = min(self.window_width / max(1, visible_w), available_height / max(1, visible_h))
final_w = int(visible_w * scale_canvas)
final_h = int(visible_h * scale_canvas)
start_x_canvas = (self.window_width - final_w) // 2
start_y_canvas = (available_height - final_h) // 2
# Back to processed (zoomed+cropped) space # Back to processed (zoomed+cropped) space
zx = (sx - start_x_canvas) / max(1e-6, scale_canvas) zx = (sx - params['start_x']) / max(1e-6, params['scale'])
zy = (sy - start_y_canvas) / max(1e-6, scale_canvas) zy = (sy - params['start_y']) / max(1e-6, params['scale'])
if cropped_due_to_zoom: zx += params['offx']
offx_max = max(0, new_w - self.window_width) zy += params['offy']
offy_max = max(0, new_h - self.window_height)
offx = max(0, min(int(self.display_offset[0]), offx_max))
offy = max(0, min(int(self.display_offset[1]), offy_max))
else:
offx = 0
offy = 0
zx += offx
zy += offy
# Reverse zoom # Reverse zoom
rx = zx / max(1e-6, self.zoom_factor) rx = zx / max(1e-6, self.zoom_factor)
ry = zy / max(1e-6, self.zoom_factor) ry = zy / max(1e-6, self.zoom_factor)
# Unapply current EFFECTIVE crop to get PRE-crop rotated coords # Unapply current EFFECTIVE crop to get PRE-crop rotated coords
rx = rx + cx rx = rx + params['eff_x']
ry = ry + cy ry = ry + params['eff_y']
return int(round(rx)), int(round(ry)) return int(round(rx)), int(round(ry))
def clear_transformation_cache(self): def clear_transformation_cache(self):