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:
116
croppa/main.py
116
croppa/main.py
@@ -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):
|
||||||
|
Reference in New Issue
Block a user