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 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):
"""Map a point in original frame coords to canvas screen coords."""
frame_number = getattr(self, 'current_frame', 0)
@@ -1208,46 +1240,16 @@ class VideoEditor:
rx, ry = self.frame_height - 1 - oy, ox
else:
rx, ry = ox, oy
# Now account for crop in rotated space
cx, cy, cw, ch = self._get_effective_crop_rect_for_frame(frame_number)
rx -= cx
ry -= cy
# Dimensions after rotation
if angle in (90, 270):
rotated_w, rotated_h = ch, cw
else:
rotated_w, rotated_h = cw, ch
# Zoom
# Now account for crop/zoom/offset using unified params
params = self._get_display_params()
rx -= params['eff_x']
ry -= params['eff_y']
zx = rx * self.zoom_factor
zy = ry * self.zoom_factor
# Zoomed dimensions
new_w = int(rotated_w * self.zoom_factor)
new_h = int(rotated_h * self.zoom_factor)
# Whether apply_crop_zoom_and_rotation cropped due to zoom
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))
inframe_x = zx - params['offx']
inframe_y = zy - params['offy']
sx = int(round(params['start_x'] + inframe_x * params['scale']))
sy = int(round(params['start_y'] + inframe_y * params['scale']))
return 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)
# Canvas scale and placement
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_h = int(visible_h * scale_canvas)
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)."""
frame_number = getattr(self, 'current_frame', 0)
angle = self.rotation_angle
# Use EFFECTIVE crop dims as the displayed base after rotation
cx, cy, cw, ch = self._get_effective_crop_rect_for_frame(frame_number)
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
# Use unified display params
params = self._get_display_params()
# Back to processed (zoomed+cropped) space
zx = (sx - start_x_canvas) / max(1e-6, scale_canvas)
zy = (sy - start_y_canvas) / max(1e-6, scale_canvas)
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
zx += offx
zy += offy
zx = (sx - params['start_x']) / max(1e-6, params['scale'])
zy = (sy - params['start_y']) / max(1e-6, params['scale'])
zx += params['offx']
zy += params['offy']
# Reverse zoom
rx = zx / max(1e-6, self.zoom_factor)
ry = zy / max(1e-6, self.zoom_factor)
# Unapply current EFFECTIVE crop to get PRE-crop rotated coords
rx = rx + cx
ry = ry + cy
rx = rx + params['eff_x']
ry = ry + params['eff_y']
return int(round(rx)), int(round(ry))
def clear_transformation_cache(self):