summary refs log tree commit diff
path: root/.config/mvi/scripts/image-positioning.lua
diff options
context:
space:
mode:
authorRobert Günzler <r@gnzler.io>2020-12-03 12:56:51 +0100
committerRobert Günzler <r@gnzler.io>2020-12-07 19:28:05 +0100
commit0eb11ed2c3dc235349b87d20bf657f2fb9729994 (patch)
treeceba665d591328bb47418ab82bb0de31abff98e5 /.config/mvi/scripts/image-positioning.lua
parente93cee56264c7e1d07b422d66430d996fd8b0c40 (diff)
Add bin/mvi and config; mpv-based image viewer
Diffstat (limited to '.config/mvi/scripts/image-positioning.lua')
-rw-r--r--.config/mvi/scripts/image-positioning.lua341
1 files changed, 341 insertions, 0 deletions
diff --git a/.config/mvi/scripts/image-positioning.lua b/.config/mvi/scripts/image-positioning.lua
new file mode 100644
index 0000000..08fa1e7
--- /dev/null
+++ b/.config/mvi/scripts/image-positioning.lua
@@ -0,0 +1,341 @@
+local opts = {
+    pan_follows_cursor_margin = 50,
+    pan_follows_cursor_move_if_full_view = false,
+
+    drag_to_pan_margin = 50,
+    drag_to_pan_move_if_full_view = false,
+}
+(require 'mp.options').read_options(opts)
+
+function clamp(value, low, high)
+    if value <= low then
+        return low
+    elseif value >= high then
+        return high
+    else
+        return value
+    end
+end
+
+local msg = require 'mp.msg'
+local assdraw = require 'mp.assdraw'
+
+function get_video_dimensions()
+    -- this function is very much ripped from video/out/aspect.c in mpv's source
+    local video_params = mp.get_property_native("video-out-params")
+    if not video_params then
+        _video_dimensions = nil
+        return nil
+    end
+    _video_dimensions = {
+        top_left = { 0,  0 },
+        bottom_right = { 0,  0 },
+        size = { 0,  0 },
+        ratios = { 0,  0 }, -- by how much the original video got scaled
+    }
+    local keep_aspect = mp.get_property_bool("keepaspect")
+    local w = video_params["w"]
+    local h = video_params["h"]
+    local dw = video_params["dw"]
+    local dh = video_params["dh"]
+    if mp.get_property_number("video-rotate") % 180 == 90 then
+        w, h = h,w
+        dw, dh = dh, dw
+    end
+    local window_w, window_h = mp.get_osd_size()
+
+    if keep_aspect then
+        local unscaled = mp.get_property_native("video-unscaled")
+        local panscan = mp.get_property_number("panscan")
+
+        local fwidth = window_w
+        local fheight = math.floor(window_w / dw * dh)
+        if fheight > window_h or fheight < h then
+            local tmpw = math.floor(window_h / dh * dw)
+            if tmpw <= window_w then
+                fheight = window_h
+                fwidth = tmpw
+            end
+        end
+        local vo_panscan_area = window_h - fheight
+        local f_w = fwidth / fheight
+        local f_h = 1
+        if vo_panscan_area == 0 then
+            vo_panscan_area = window_h - fwidth
+            f_w = 1
+            f_h = fheight / fwidth
+        end
+        if unscaled or unscaled == "downscale-big" then
+            vo_panscan_area = 0
+            if unscaled or (dw <= window_w and dh <= window_h) then
+                fwidth = dw
+                fheight = dh
+            end
+        end
+
+        local scaled_width = fwidth + math.floor(vo_panscan_area * panscan * f_w)
+        local scaled_height = fheight + math.floor(vo_panscan_area * panscan * f_h)
+
+        local split_scaling = function (dst_size, scaled_src_size, zoom, align, pan)
+            scaled_src_size = math.floor(scaled_src_size * 2 ^ zoom)
+            align = (align + 1) / 2
+            local dst_start = math.floor((dst_size - scaled_src_size) * align + pan * scaled_src_size)
+            if dst_start < 0 then
+                --account for C int cast truncating as opposed to flooring
+                dst_start = dst_start + 1
+            end
+            local dst_end = dst_start + scaled_src_size;
+            if dst_start >= dst_end then
+                dst_start = 0
+                dst_end = 1
+            end
+            return dst_start, dst_end
+        end
+        local zoom = mp.get_property_number("video-zoom")
+
+        local align_x = mp.get_property_number("video-align-x")
+        local pan_x = mp.get_property_number("video-pan-x")
+        _video_dimensions.top_left[1], _video_dimensions.bottom_right[1] = split_scaling(window_w, scaled_width, zoom, align_x, pan_x)
+
+        local align_y = mp.get_property_number("video-align-y")
+        local pan_y = mp.get_property_number("video-pan-y")
+        _video_dimensions.top_left[2], _video_dimensions.bottom_right[2] = split_scaling(window_h,  scaled_height, zoom, align_y, pan_y)
+    else
+        _video_dimensions.top_left[1] = 0
+        _video_dimensions.bottom_right[1] = window_w
+        _video_dimensions.top_left[2] = 0
+        _video_dimensions.bottom_right[2] = window_h
+    end
+    _video_dimensions.size[1] = _video_dimensions.bottom_right[1] - _video_dimensions.top_left[1]
+    _video_dimensions.size[2] = _video_dimensions.bottom_right[2] - _video_dimensions.top_left[2]
+    _video_dimensions.ratios[1] = _video_dimensions.size[1] / w
+    _video_dimensions.ratios[2] = _video_dimensions.size[2] / h
+    return _video_dimensions
+end
+
+local cleanup = nil -- function set up by drag-to-pan/pan-follows cursor and must be called to clean lingering state
+
+function drag_to_pan_handler(table)
+    if cleanup then
+        cleanup()
+        cleanup = nil
+    end
+    if table["event"] == "down" then
+        local video_dimensions = get_video_dimensions()
+        if not video_dimensions then return end
+        local window_w, window_h = mp.get_osd_size()
+        local mouse_pos_origin, video_pan_origin = {}, {}
+        local moved = false
+        mouse_pos_origin[1], mouse_pos_origin[2] = mp.get_mouse_pos()
+        video_pan_origin[1] = mp.get_property_number("video-pan-x")
+        video_pan_origin[2] = mp.get_property_number("video-pan-y")
+        local margin = opts.drag_to_pan_margin
+        local move_up = true
+        local move_lateral = true
+        if not opts.drag_to_pan_move_if_full_view then
+            if video_dimensions.size[1] <= window_w then
+                move_lateral = false
+            end
+            if video_dimensions.size[2] <= window_h then
+                move_up = false
+            end
+        end
+        if not move_up and not move_lateral then return end
+        local idle = function()
+            if moved then
+                local mX, mY = mp.get_mouse_pos()
+                local pX = video_pan_origin[1]
+                local pY = video_pan_origin[2]
+                if move_lateral then
+                    pX = video_pan_origin[1] + (mX - mouse_pos_origin[1]) / video_dimensions.size[1]
+                    if video_dimensions.size[1] + 2 * margin > window_w then
+                        pX = clamp(pX,
+                            (-margin + window_w / 2) / video_dimensions.size[1] - 0.5,
+                            (margin - window_w / 2) / video_dimensions.size[1] + 0.5)
+                    else
+                        pX = clamp(pX,
+                            (margin - window_w / 2) / video_dimensions.size[1] + 0.5,
+                            (-margin + window_w / 2) / video_dimensions.size[1] - 0.5)
+                    end
+                end
+                if move_up then
+                    pY = video_pan_origin[2] + (mY - mouse_pos_origin[2]) / video_dimensions.size[2]
+                    if video_dimensions.size[2] + 2 * margin > window_h then
+                        pY = clamp(pY,
+                            (-margin + window_h / 2) / video_dimensions.size[2] - 0.5,
+                            (margin - window_h / 2) / video_dimensions.size[2] + 0.5)
+                    else
+                        pY = clamp(pY,
+                            (margin - window_h / 2) / video_dimensions.size[2] + 0.5,
+                            (-margin + window_h / 2) / video_dimensions.size[2] - 0.5)
+                    end
+                end
+                mp.command("no-osd set video-pan-x " .. clamp(pX, -3, 3) .. "; no-osd set video-pan-y " .. clamp(pY, -3, 3))
+                moved = false
+            end
+        end
+        mp.register_idle(idle)
+        mp.add_forced_key_binding("mouse_move", "image-viewer-mouse-move", function() moved = true end)
+        cleanup = function()
+            mp.remove_key_binding("image-viewer-mouse-move")
+            mp.unregister_idle(idle)
+        end
+    end
+end
+
+function pan_follows_cursor_handler(table)
+    if cleanup then
+        cleanup()
+        cleanup = nil
+    end
+    if table["event"] == "down" then
+        local video_dimensions = get_video_dimensions()
+        if not video_dimensions then return end
+        local window_w, window_h = mp.get_osd_size()
+        local moved = true
+        local idle = function()
+            if moved then
+                local mX, mY = mp.get_mouse_pos()
+                local x = math.min(1, math.max(- 2 * mX / window_w + 1, -1))
+                local y = math.min(1, math.max(- 2 * mY / window_h + 1, -1))
+                local command = ""
+                local margin, move_full = opts.pan_follows_cursor_margin, opts.pan_follows_cursor_move_if_full_view
+                if (not move_full and window_w < video_dimensions.size[1]) then
+                    command = command .. "no-osd set video-pan-x " .. clamp(x * (video_dimensions.size[1] - window_w + 2 * margin) / (2 * video_dimensions.size[1]), -3, 3) .. ";"
+                elseif mp.get_property_number("video-pan-x") ~= 0 then
+                    command = command .. "no-osd set video-pan-x " .. "0;"
+                end
+                if (not move_full and window_h < video_dimensions.size[2]) then
+                    command = command .. "no-osd set video-pan-y " .. clamp(y * (video_dimensions.size[2] - window_h + 2 * margin) / (2 * video_dimensions.size[2]), -3, 3) .. ";"
+                elseif mp.get_property_number("video-pan-y") ~= 0 then
+                    command = command .. "no-osd set video-pan-y " .. "0;"
+                end
+                if command ~= "" then
+                    mp.command(command)
+                end
+                moved = false
+            end
+        end
+        mp.register_idle(idle)
+        mp.add_forced_key_binding("mouse_move", "image-viewer-mouse-move", function() moved = true end)
+        cleanup = function()
+            mp.remove_key_binding("image-viewer-mouse-move")
+            mp.unregister_idle(idle)
+        end
+    end
+end
+
+function cursor_centric_zoom_handler(amt)
+    local zoom_inc = tonumber(amt)
+    if not zoom_inc or zoom_inc == 0 then return end
+    local video_dimensions = get_video_dimensions()
+    if not video_dimensions then return end
+    local mouse_pos_origin, video_pan_origin = {}, {}
+    mouse_pos_origin[1], mouse_pos_origin[2] = mp.get_mouse_pos()
+    video_pan_origin[1] = mp.get_property("video-pan-x")
+    video_pan_origin[2] = mp.get_property("video-pan-y")
+    local zoom_origin = mp.get_property("video-zoom")
+    -- how far the cursor is form the middle of the video (in percentage)
+    local rx = (video_dimensions.top_left[1] + video_dimensions.size[1] / 2 - mouse_pos_origin[1]) / (video_dimensions.size[1] / 2)
+    local ry = (video_dimensions.top_left[2] + video_dimensions.size[2] / 2 - mouse_pos_origin[2]) / (video_dimensions.size[2] / 2)
+
+    -- the size in pixels of the (in|de)crement
+    local diffHeight = (2 ^ zoom_inc - 1) * video_dimensions.size[2]
+    local diffWidth  = (2 ^ zoom_inc - 1) * video_dimensions.size[1]
+    local newPanX = (video_pan_origin[1] * video_dimensions.size[1] + rx * diffWidth / 2) / (video_dimensions.size[1] + diffWidth)
+    local newPanY = (video_pan_origin[2] * video_dimensions.size[2] + ry * diffHeight / 2) / (video_dimensions.size[2] + diffHeight)
+    mp.command("no-osd set video-zoom " .. zoom_origin + zoom_inc .. "; no-osd set video-pan-x " .. clamp(newPanX, -3, 3) .. "; no-osd set video-pan-y " .. clamp(newPanY, -3, 3))
+end
+
+function align_border(x, y)
+    local video_dimensions = get_video_dimensions()
+    if not video_dimensions then return end
+    local window_w, window_h = mp.get_osd_size()
+    local x, y = tonumber(x), tonumber(y)
+    local command = ""
+    if x then
+        command = command .. "no-osd set video-pan-x " .. clamp(x * (video_dimensions.size[1] - window_w) / (2 * video_dimensions.size[1]), -3, 3) .. ";"
+    end
+    if y then
+        command = command .. "no-osd set video-pan-y " .. clamp(y * (video_dimensions.size[2] - window_h) / (2 * video_dimensions.size[2]), -3, 3) .. ";"
+    end
+    if command ~= "" then
+        mp.command(command)
+    end
+end
+
+function pan_image(axis, amount, zoom_invariant, image_constrained)
+    amount = tonumber(amount)
+    if not amount or amount == 0 or axis ~= "x" and axis ~= "y" then return end
+    if zoom_invariant == "yes" then
+        amount = amount / 2 ^ mp.get_property_number("video-zoom")
+    end
+    local prop = "video-pan-" .. axis
+    local old_pan = mp.get_property_number(prop)
+    axis = (axis == "x") and 1 or 2
+    if image_constrained == "yes" then
+        local video_dimensions = get_video_dimensions()
+        if not video_dimensions then return end
+        local window = {}
+        window[1], window[2] = mp.get_osd_size()
+        local pixels_moved = amount * video_dimensions.size[axis]
+        -- should somehow refactor this
+        if pixels_moved > 0 then
+            if window[axis] > video_dimensions.size[axis] then
+                if video_dimensions.bottom_right[axis] >= window[axis] then return end
+                if video_dimensions.bottom_right[axis] + pixels_moved > window[axis] then
+                    amount = (window[axis] - video_dimensions.bottom_right[axis]) / video_dimensions.size[axis]
+                end
+            else
+                if video_dimensions.top_left[axis] >= 0 then return end
+                if video_dimensions.top_left[axis] + pixels_moved > 0 then
+                    amount = (0 - video_dimensions.top_left[axis]) / video_dimensions.size[axis]
+                end
+            end
+        else
+            if window[axis] > video_dimensions.size[axis] then
+                if video_dimensions.top_left[axis] <= 0 then return end
+                if video_dimensions.top_left[axis] + pixels_moved < 0 then
+                    amount = (0 - video_dimensions.top_left[axis]) / video_dimensions.size[axis]
+                end
+            else
+                if video_dimensions.bottom_right[axis] <= window[axis] then return end
+                if video_dimensions.bottom_right[axis] + pixels_moved < window[axis] then
+                    amount = (window[axis] - video_dimensions.bottom_right[axis]) / video_dimensions.size[axis]
+                end
+            end
+        end
+    end
+    mp.set_property_number(prop, old_pan + amount)
+end
+
+function rotate_video(amt)
+    local rot = mp.get_property_number("video-rotate")
+    rot = (rot + amt) % 360
+    mp.set_property_number("video-rotate", rot)
+end
+
+function reset_pan_if_visible()
+    local video_dimensions = get_video_dimensions()
+    if not video_dimensions then return end
+    local window_w, window_h = mp.get_osd_size()
+    local command = ""
+    if (window_w >= video_dimensions.size[1]) then
+        command = command .. "no-osd set video-pan-x 0" .. ";"
+    end
+    if (window_h >= video_dimensions.size[2]) then
+        command = command .. "no-osd set video-pan-y 0" .. ";"
+    end
+    if command ~= "" then
+        mp.command(command)
+    end
+end
+
+mp.add_key_binding(nil, "drag-to-pan", drag_to_pan_handler, {complex = true})
+mp.add_key_binding(nil, "pan-follows-cursor", pan_follows_cursor_handler, {complex = true})
+mp.add_key_binding(nil, "cursor-centric-zoom", cursor_centric_zoom_handler)
+mp.add_key_binding(nil, "align-border", align_border)
+mp.add_key_binding(nil, "pan-image", pan_image)
+mp.add_key_binding(nil, "rotate-video", rotate_video)
+mp.add_key_binding(nil, "reset-pan-if-visible", reset_pan_if_visible)
+mp.add_key_binding(nil, "force-print-filename", force_print_filename)