From 01e5e14f11891e3a6fc59987f58d35067ecfbf09 Mon Sep 17 00:00:00 2001 From: AMM Date: Tue, 28 Nov 2017 23:56:16 +0200 Subject: [PATCH] display_state: Handle nigh all display adjustments That is, panscan, zoom, video-unscaled, pan, align, aspect ratio, etc. Adapted from mpv's own video/out/aspect.c. Mirror or crops are not handled (yet?), but display state handling should now be perfect for sane users. --- src/asscropper.lua | 5 +- src/display_state.lua | 179 ++++++++++++++++++++++++++++++++---------- 2 files changed, 141 insertions(+), 43 deletions(-) diff --git a/src/asscropper.lua b/src/asscropper.lua index e561214..7b42cba 100644 --- a/src/asscropper.lua +++ b/src/asscropper.lua @@ -182,8 +182,9 @@ function ASSCropper:get_hitboxes(crop_box) local w, h = math.abs(x2 - x1), math.abs(y2 - y1) -- Corner and required corner size in videospace pixels - local videospace_corner_size = self.corner_size * self.display_state.scale_mult - local videospace_required_size = self.corner_required_size * self.display_state.scale_mult + local mult = math.min(self.display_state.scale.x, self.display_state.scale.y) + local videospace_corner_size = self.corner_size * mult + local videospace_required_size = self.corner_required_size * mult local handles_outside = (math.min(w, h) <= videospace_required_size) diff --git a/src/display_state.lua b/src/display_state.lua index b0331b7..462e7be 100644 --- a/src/display_state.lua +++ b/src/display_state.lua @@ -19,10 +19,11 @@ function DisplayState:reset() self.scale = {} -- video / screen self.bounds = {} -- Video rect within display - self.scale_mult = 1 - self.screen_ready = false self.video_ready = false + + -- Stores internal display state (panscan, align, zoom etc) + self.current_state = nil end function DisplayState:setup_events() @@ -36,69 +37,165 @@ end -- Turns screen-space XY to video XY (can go negative) function DisplayState:screen_to_video(x, y) - local nx = (x - self.bounds.left) * self.scale_mult - local ny = (y - self.bounds.top ) * self.scale_mult + local nx = (x - self.bounds.left) * self.scale.x + local ny = (y - self.bounds.top ) * self.scale.y return nx, ny end + -- Turns video-space XY to screen XY function DisplayState:video_to_screen(x, y) - local nx = (x / self.scale_mult) + self.bounds.left - local ny = (y / self.scale_mult) + self.bounds.top - + local nx = (x / self.scale.x) + self.bounds.left + local ny = (y / self.scale.y) + self.bounds.top return nx, ny end -function DisplayState:recalculate_bounds(forced) - -- OSD dimensions +function DisplayState:_collect_display_state() local screen_w, screen_h, screen_aspect = mp.get_osd_size() - local screen_changed = false - if (forced or self.screen.width ~= screen_w or self.screen.height ~= screen_h) then - self.screen.width = screen_w - self.screen.height = screen_h - self.screen.ratio = screen_w / screen_h + local state = { + screen_w = screen_w, + screen_h = screen_h, + screen_aspect = screen_aspect, + + video_w = mp.get_property_native("dwidth"), + video_h = mp.get_property_native("dheight"), + + video_w_raw = mp.get_property_native("video-out-params/w"), + video_h_raw = mp.get_property_native("video-out-params/h"), + + panscan = mp.get_property_native("panscan"), + video_zoom = mp.get_property_native("video-zoom"), + video_unscaled = mp.get_property_native("video-unscaled"), + + video_align_x = mp.get_property_native("video-align-x"), + video_align_y = mp.get_property_native("video-align-y"), + + video_pan_x = mp.get_property_native("video-pan-x"), + video_pan_y = mp.get_property_native("video-pan-y"), + + fullscreen = mp.get_property_native("fullscreen"), + keepaspect = mp.get_property_native("keepaspect"), + keepaspect_window = mp.get_property_native("keepaspect-window") + } - self.screen_ready = true - screen_changed = true + return state +end + +function DisplayState:_state_changed(state) + if self.current_state == nil then return true end + + for k in pairs(state) do + if state[k] ~= self.current_state[k] then return true end end + return false +end - -- Video dimensions - local vw = mp.get_property_native("dwidth") - local vh = mp.get_property_native("dheight") - if (vw and vh) and (forced or (self.video.width ~= vw and self.video.height ~= vh) or screen_changed) then - self.video.width = vw - self.video.height = vh - self.video.ratio = vw / vh +function DisplayState:recalculate_bounds(forced) + local new_state = self:_collect_display_state() + if not (forced or self:_state_changed(new_state)) then + -- Early out + return self.screen_ready + end + self.current_state = new_state + + -- Store screen dimensions + self.screen.width = new_state.screen_w + self.screen.height = new_state.screen_h + self.screen.ratio = new_state.screen_w / new_state.screen_h + self.screen_ready = true - self.scale.y = vh / self.screen.height - self.scale.x = vw / self.screen.width + -- Video dimensions + if new_state.video_w and new_state.video_h then + self.video.width = new_state.video_w + self.video.height = new_state.video_h + self.video.ratio = new_state.video_w / new_state.video_h - -- Round down for a bit looser matching - if round_dec(self.screen.ratio, 1) >= round_dec(self.video.ratio, 1) then -- Wide window - local sw = self.screen.height * self.video.ratio -- The width of the video, on screen - local left = (self.screen.width - sw) / 2 + -- This magic has been adapted from mpv's own video/out/aspect.c + + if new_state.keepaspect then + local scaled_w, scaled_h = self:_aspect_calc_panscan(new_state) + local video_left, video_right = self:_split_scaling(new_state.screen_w, scaled_w, new_state.video_zoom, new_state.video_align_x, new_state.video_pan_x) + local video_top, video_bottom = self:_split_scaling(new_state.screen_h, scaled_h, new_state.video_zoom, new_state.video_align_y, new_state.video_pan_y) self.bounds = { - left = left, - top = 0, - right = left + sw, - bot = self.screen.height + left = video_left, + right = video_right, + + top = video_top, + bottom = video_bottom, + + width = video_right - video_left, + height = video_bottom - video_top, } - self.scale_mult = self.scale.y - else -- Tall window - local sh = self.screen.width / self.video.ratio - local top = (self.screen.height - sh) / 2 + else self.bounds = { - left = 0, - top = top, + left = 0, + top = 0, right = self.screen.width, - bot = top + sh + bottom = self.screen.height, + + width = self.screen.width, + height = self.screen.height, } - self.scale_mult = self.scale.x end + self.scale.x = new_state.video_w_raw / self.bounds.width + self.scale.y = new_state.video_h_raw / self.bounds.height + self.video_ready = true end return self.screen_ready end + + +function DisplayState:_aspect_calc_panscan(state) + -- From video/out/aspect.c + local f_width = state.screen_w + local f_height = (state.screen_w / state.video_w) * state.video_h + + if f_height > state.screen_h or f_height < state.video_h_raw then + local tmp_w = (state.screen_h / state.video_h) * state.video_w + if tmp_w <= state.screen_w then + f_height = state.screen_h + f_width = tmp_w + end + end + + local vo_panscan_area = state.screen_h - f_height + + local f_w = f_width / f_height + local f_h = 1 + if (vo_panscan_area == 0) then + vo_panscan_area = state.screen_w - f_width + f_w = 1 + f_h = f_height / f_width + end + + if state.video_unscaled then + vo_panscan_area = 0 + if state.video_unscaled ~= "downscale-big" or ((state.video_w <= state.screen_w) and (state.video_h <= state.screen_h)) then + f_width = state.video_w + f_height = state.video_h + end + end + + local scaled_w = math.floor( f_width + vo_panscan_area * state.panscan * f_w ) + local scaled_h = math.floor( f_height + vo_panscan_area * state.panscan * f_h ) + return scaled_w, scaled_h +end + +function DisplayState:_split_scaling(dst_size, scaled_src_size, zoom, align, pan) + -- From video/out/aspect.c as well + scaled_src_size = math.floor(scaled_src_size * 2^zoom) + align = (align + 1) / 2 + + local dst_start = (dst_size - scaled_src_size) * align + pan * scaled_src_size + local dst_end = dst_start + scaled_src_size + + -- We don't actually want these - we want to go out of bounds! + -- dst_start = math.max(0, dst_start) + -- dst_end = math.min(dst_size, dst_end) + + return math.floor(dst_start), math.floor(dst_end) +end