diff --git a/portable_config/script-opts/uosc.conf b/portable_config/script-opts/uosc.conf index 4f037d14..82ed9101 100644 --- a/portable_config/script-opts/uosc.conf +++ b/portable_config/script-opts/uosc.conf @@ -130,7 +130,7 @@ stream_quality_options=4320,2160,1440,1080,720,480,360,240,144 # (加载文件/导入视频音频轨时)文件浏览器的扩展名过滤列表。默认值覆盖极广,此预设精简为常见的视频和音频格式 video_types=avi,flv,m2ts,m4v,mkv,mov,mp4,mpeg,mpg,ogv,rm,rmvb,ts,vob,webm,wmv -audio_types=aac,ape,dsf,dts,flac,m4a,mka,mp3,ogg,opus,wav,wma,wv +audio_types=aac,ac3,ape,dsf,dts,flac,m4a,mka,mp3,ogg,opus,wav,wma,wv image_types=apng,avif,bmp,gif,jfif,jpeg,jpg,jxl,png,svg,tif,tiff,webp # (导入字幕时)文件浏览器的扩展名过滤列表。默认值覆盖极广,此预设精简为常见的字幕格式 subtitle_types=ass,idx,lrc,mks,pgs,sup,srt,ssa,txt,vtt diff --git a/portable_config/scripts/uosc/elements/Button.lua b/portable_config/scripts/uosc/elements/Button.lua index 140563cf..ed06568f 100644 --- a/portable_config/scripts/uosc/elements/Button.lua +++ b/portable_config/scripts/uosc/elements/Button.lua @@ -23,19 +23,20 @@ function Button:init(id, props) end function Button:on_coordinates() self.font_size = round((self.by - self.ay) * 0.7) end -function Button:on_mbtn_left_down() - -- Don't accept clicks while hidden. - if self:get_visibility() <= 0 then return end +function Button:handle_cursor_down() -- We delay the callback to next tick, otherwise we are risking race -- conditions as we are in the middle of event dispatching. -- For example, handler might add a menu to the end of the element stack, and that - -- than picks up this click even we are in right now, and instantly closes itself. + -- than picks up this click event we are in right now, and instantly closes itself. mp.add_timeout(0.01, self.on_click) end function Button:render() local visibility = self:get_visibility() if visibility <= 0 then return end + if self.proximity_raw == 0 then + cursor.on_primary_down = function() self:handle_cursor_down() end + end local ass = assdraw.ass_new() local is_hover = self.proximity_raw == 0 @@ -54,7 +55,6 @@ function Button:render() -- Tooltip on hover if is_hover and self.tooltip then ass:tooltip(self, self.tooltip) end - -- Badge local icon_clip if self.badge then diff --git a/portable_config/scripts/uosc/elements/Element.lua b/portable_config/scripts/uosc/elements/Element.lua index d8f1e22a..bc3125b9 100644 --- a/portable_config/scripts/uosc/elements/Element.lua +++ b/portable_config/scripts/uosc/elements/Element.lua @@ -15,7 +15,7 @@ function Element:init(id, props) -- Relative proximity from `0` - mouse outside `proximity_max` range, to `1` - mouse within `proximity_min` range. self.proximity = 0 -- Raw proximity in pixels. - self.proximity_raw = infinity + self.proximity_raw = INFINITY ---@type number `0-1` factor to force min visibility. Used for toggling element's permanent visibility. self.min_visibility = 0 ---@type number `0-1` factor to force a visibility value. Used for flashing, fading out, and other animations @@ -44,7 +44,7 @@ function Element:destroy() Elements:remove(self) end -function Element:reset_proximity() self.proximity, self.proximity_raw = 0, infinity end +function Element:reset_proximity() self.proximity, self.proximity_raw = 0, INFINITY end ---@param ax number ---@param ay number diff --git a/portable_config/scripts/uosc/elements/Elements.lua b/portable_config/scripts/uosc/elements/Elements.lua index f6fba012..186b9e93 100644 --- a/portable_config/scripts/uosc/elements/Elements.lua +++ b/portable_config/scripts/uosc/elements/Elements.lua @@ -29,8 +29,6 @@ function Elements:remove(idOrElement) end function Elements:update_proximities() - local capture_mbtn_left = false - local capture_wheel = false local menu_only = Elements.menu ~= nil local mouse_leave_elements = {} local mouse_enter_elements = {} @@ -42,26 +40,13 @@ function Elements:update_proximities() -- If menu is open, all other elements have to be disabled if menu_only then - if element.ignores_menu then - capture_mbtn_left = true - capture_wheel = true - element:update_proximity() - else - element:reset_proximity() - end + if element.ignores_menu then element:update_proximity() + else element:reset_proximity() end else element:update_proximity() end - -- Element has global forced key listeners - if element.on_global_mbtn_left_down then capture_mbtn_left = true end - if element.on_global_wheel_up or element.on_global_wheel_down then capture_wheel = true end - if element.proximity_raw == 0 then - -- Element has local forced key listeners - if element.on_mbtn_left_down then capture_mbtn_left = true end - if element.on_wheel_up or element.on_wheel_up then capture_wheel = true end - -- Mouse entered element area if previous_proximity_raw ~= 0 then mouse_enter_elements[#mouse_enter_elements + 1] = element @@ -75,10 +60,6 @@ function Elements:update_proximities() end end - -- Enable key group captures requested by elements - mp[capture_mbtn_left and 'enable_key_bindings' or 'disable_key_bindings']('mbtn_left') - mp[capture_wheel and 'enable_key_bindings' or 'disable_key_bindings']('wheel') - -- Trigger `mouse_leave` and `mouse_enter` events for _, element in ipairs(mouse_leave_elements) do element:trigger('mouse_leave') end for _, element in ipairs(mouse_enter_elements) do element:trigger('mouse_enter') end @@ -142,26 +123,4 @@ end function Elements:has(id) return self[id] ~= nil end function Elements:ipairs() return ipairs(self.itable) end ----@param name string Event name. -function Elements:create_proximity_dispatcher(name) - return function(...) self:proximity_trigger(name, ...) end -end - -mp.set_key_bindings({ - { - 'mbtn_left', - Elements:create_proximity_dispatcher('mbtn_left_up'), - function(...) - update_mouse_pos(nil, mp.get_property_native('mouse-pos')) - Elements:proximity_trigger('mbtn_left_down', ...) - end, - }, - {'mbtn_left_dbl', 'ignore'}, -}, 'mbtn_left', 'force') - -mp.set_key_bindings({ - {'wheel_up', Elements:create_proximity_dispatcher('wheel_up')}, - {'wheel_down', Elements:create_proximity_dispatcher('wheel_down')}, -}, 'wheel', 'force') - return Elements diff --git a/portable_config/scripts/uosc/elements/Menu.lua b/portable_config/scripts/uosc/elements/Menu.lua index 8dcb7182..4048b76b 100644 --- a/portable_config/scripts/uosc/elements/Menu.lua +++ b/portable_config/scripts/uosc/elements/Menu.lua @@ -4,10 +4,10 @@ local Element = require('elements/Element') ---@alias MenuData {type?: string; title?: string; hint?: string; keep_open?: boolean; separator?: boolean; items?: MenuDataItem[]; selected_index?: integer;} ---@alias MenuDataItem MenuDataValue|MenuData ---@alias MenuDataValue {title?: string; hint?: string; icon?: string; value: any; bold?: boolean; italic?: boolean; muted?: boolean; active?: boolean; keep_open?: boolean; separator?: boolean;} ----@alias MenuOptions {mouse_nav?: boolean; on_open?: fun(); on_close?: fun(); on_back?: fun()} +---@alias MenuOptions {mouse_nav?: boolean; on_open?: fun(); on_close?: fun(); on_back?: fun(); on_move_item?: fun(from_index: integer, to_index: integer, submenu_path: integer[]); on_delete_item?: fun(index: integer, submenu_path: integer[])} -- Internal data structure created from `Menu`. ----@alias MenuStack {id?: string; type?: string; title?: string; hint?: string; selected_index?: number; keep_open?: boolean; separator?: boolean; items: MenuStackItem[]; parent_menu?: MenuStack; active?: boolean; width: number; height: number; top: number; scroll_y: number; scroll_height: number; title_width: number; hint_width: number; max_width: number; is_root?: boolean; fling?: Fling} +---@alias MenuStack {id?: string; type?: string; title?: string; hint?: string; selected_index?: number; keep_open?: boolean; separator?: boolean; items: MenuStackItem[]; parent_menu?: MenuStack; submenu_path: integer[]; active?: boolean; width: number; height: number; top: number; scroll_y: number; scroll_height: number; title_width: number; hint_width: number; max_width: number; is_root?: boolean; fling?: Fling} ---@alias MenuStackItem MenuStackValue|MenuStack ---@alias MenuStackValue {title?: string; hint?: string; icon?: string; value: any; active?: boolean; bold?: boolean; italic?: boolean; muted?: boolean; keep_open?: boolean; separator?: boolean; title_width: number; hint_width: number} ---@alias Fling {y: number, distance: number, time: number, easing: fun(x: number), duration: number, update_cursor?: boolean} @@ -56,6 +56,7 @@ function Menu:close(immediate, callback) menu.is_closing, menu.stack, menu.current, menu.all, menu.by_id = false, nil, nil, {}, {} menu:disable_key_bindings() Elements:update_proximities() + cursor.queue_autohide() if callback then callback() end request_render() end @@ -135,7 +136,7 @@ end function Menu:update(data) self.type = data.type - local new_root = {is_root = true} + local new_root = {is_root = true, submenu_path = {}} local new_all = {} local new_by_id = {} local menus_to_serialize = {{new_root, data}} @@ -169,6 +170,7 @@ function Menu:update(data) -- Submenu if item_data.items then item.parent_menu = menu + item.submenu_path = itable_join(menu.submenu_path, {i}) menus_to_serialize[#menus_to_serialize + 1] = {item, item_data} end @@ -210,8 +212,10 @@ function Menu:update_content_dimensions() local hint_opts = {size = self.font_size_hint} for _, menu in ipairs(self.all) do + title_opts.bold, title_opts.italic = true, false + local max_width = text_width(menu.title, title_opts) + 2 * self.item_padding + -- Estimate width of a widest item - local max_width = 0 for _, item in ipairs(menu.items) do local icon_width = item.icon and self.font_size or 0 item.title_width = text_width(item.title, title_opts) @@ -223,11 +227,6 @@ function Menu:update_content_dimensions() if estimated_width > max_width then max_width = estimated_width end end - -- Also check menu title - title_opts.bold, title_opts.italic = true, false - local menu_title_width = text_width(menu.title, title_opts) - if menu_title_width > max_width then max_width = menu_title_width end - menu.max_width = max_width end @@ -236,8 +235,8 @@ end function Menu:update_dimensions() -- Coordinates and sizes are of the scrollable area to make - -- consuming values in rendering and collisions easier. Title drawn above this, so - -- we need to account for that in max_height and ay position. + -- consuming values in rendering and collisions easier. Title is rendered + -- above it, so we need to account for that in max_height and ay position. local min_width = state.fullormaxed and options.menu_min_width_fullscreen or options.menu_min_width for _, menu in ipairs(self.all) do @@ -252,6 +251,11 @@ function Menu:update_dimensions() self:scroll_to(menu.scroll_y, menu) -- clamps scroll_y to scroll limits end + self:update_coordinates() +end + +-- Updates element coordinates to match currently open (sub)menu. +function Menu:update_coordinates() local ax = round((display.width - self.current.width) / 2) + self.offset_x self:set_coordinates(ax, self.current.top, ax + self.current.width, self.current.top + self.current.height) end @@ -359,7 +363,7 @@ end function Menu:select_value(value, menu) menu = menu or self.current local index = itable_find(menu.items, function(item) return item.value == value end) - self:select_index(index, 5) + self:select_index(index) end ---@param menu? MenuStack @@ -400,16 +404,23 @@ function Menu:activate_one_value(value, menu) self:activate_one_index(index, menu) end ----@param id string -function Menu:activate_submenu(id) - local submenu = self.by_id[id] - if submenu then - self.current = submenu +---@param menu MenuStack One of menus in `self.all`. +function Menu:activate_menu(menu) + if itable_index_of(self.all, menu) then + self.current = menu + self:update_coordinates() + self:reset_navigation() request_render() else - msg.error(string.format('Requested submenu id "%s" doesn\'t exist', id)) + msg.error('Attempt to open a menu not in `self.all` list.') end - self:reset_navigation() +end + +---@param id string +function Menu:activate_submenu(id) + local submenu = self.by_id[id] + if submenu then self:activate_menu(submenu) + else msg.error(string.format('Requested submenu id "%s" doesn\'t exist', id)) end end ---@param index? integer @@ -456,8 +467,7 @@ function Menu:back() if parent then menu.selected_index = nil - self.current = parent - self:update_dimensions() + self:activate_menu(parent) self:tween(self.offset_x - menu.width / 2, 0, function(offset) self:set_offset_x(offset) end) self.opacity = 1 -- in case tween above canceled fade in animation else @@ -473,11 +483,10 @@ function Menu:open_selected_item(opts) local item = menu.items[menu.selected_index] -- Is submenu if item.items then - self.current = item if opts.preselect_submenu_item then item.selected_index = #item.items > 0 and 1 or nil end - self:update_dimensions() + self:activate_menu(item) self:tween(self.offset_x + menu.width / 2, 0, function(offset) self:set_offset_x(offset) end) self.opacity = 1 -- in case tween above canceled fade in animation else @@ -491,10 +500,33 @@ function Menu:open_selected_item_soft() self:open_selected_item({keep_open = tru function Menu:open_selected_item_preselect() self:open_selected_item({preselect_submenu_item = true}) end function Menu:select_item_below_cursor() self.current.selected_index = self:get_item_index_below_cursor() end +---@param index integer +function Menu:move_selected_item_to(index) + local from, callback = self.current.selected_index, self.opts.on_move_item + if callback and from and from ~= index and index >= 1 and index <= #self.current.items then + callback(from, index, self.current.submenu_path) + self.current.selected_index = index + request_render() + end +end + +function Menu:move_selected_item_up() + if self.current.selected_index then self:move_selected_item_to(self.current.selected_index - 1) end +end + +function Menu:move_selected_item_down() + if self.current.selected_index then self:move_selected_item_to(self.current.selected_index + 1) end +end + +function Menu:delete_selected_item() + local index, callback = self.current.selected_index, self.opts.on_delete_item + if callback and index then callback(index, self.current.submenu_path) end +end + function Menu:on_display() self:update_dimensions() end function Menu:on_prop_fullormaxed() self:update_content_dimensions() end -function Menu:on_global_mbtn_left_down() +function Menu:handle_cursor_down() if self.proximity_raw == 0 then self.drag_data = {{y = cursor.y, time = mp.get_time()}} self.current.fling = nil @@ -514,7 +546,7 @@ function Menu:fling_distance() return #self.drag_data < 2 and 0 or ((first.y - last.y) / ((first.time - last.time) / 0.03)) * 10 end -function Menu:on_global_mbtn_left_up() +function Menu:handle_cursor_up() if self.proximity_raw == 0 and self.drag_data and not self.is_dragging then self:select_item_below_cursor() self:open_selected_item({preselect_submenu_item = false, keep_open = self.modifiers and self.modifiers.shift}) @@ -546,8 +578,8 @@ function Menu:on_global_mouse_move() request_render() end -function Menu:on_wheel_up() self:scroll_by(self.scroll_step * -3, nil, {update_cursor = true}) end -function Menu:on_wheel_down() self:scroll_by(self.scroll_step * 3, nil, {update_cursor = true}) end +function Menu:handle_wheel_up() self:scroll_by(self.scroll_step * -3, nil, {update_cursor = true}) end +function Menu:handle_wheel_down() self:scroll_by(self.scroll_step * 3, nil, {update_cursor = true}) end function Menu:on_pgup() local menu = self.current @@ -585,6 +617,8 @@ function Menu:enable_key_bindings() -- doesn't support 'repeatable' flag, so we are stuck with this monster. self:add_key_binding('up', 'menu-prev1', self:create_key_action('prev'), 'repeatable') self:add_key_binding('down', 'menu-next1', self:create_key_action('next'), 'repeatable') + self:add_key_binding('ctrl+up', 'menu-move-up', self:create_key_action('move_selected_item_up'), 'repeatable') + self:add_key_binding('ctrl+down', 'menu-move-down', self:create_key_action('move_selected_item_down'), 'repeatable') self:add_key_binding('left', 'menu-back1', self:create_key_action('back')) self:add_key_binding('right', 'menu-select1', self:create_key_action('open_selected_item_preselect')) self:add_key_binding('shift+right', 'menu-select-soft1', @@ -608,6 +642,7 @@ function Menu:enable_key_bindings() self:add_key_binding('pgdwn', 'menu-page-down', self:create_key_action('on_pgdwn'), 'repeatable') self:add_key_binding('home', 'menu-home', self:create_key_action('on_home')) self:add_key_binding('end', 'menu-end', self:create_key_action('on_end')) + self:add_key_binding('del', 'menu-delete-item', self:create_key_action('delete_selected_item')) end function Menu:disable_key_bindings() @@ -620,8 +655,8 @@ function Menu:create_modified_mbtn_left_handler(modifiers) return function() self.mouse_nav = true self.modifiers = modifiers - self:on_global_mbtn_left_down() - self:on_global_mbtn_left_up() + self:handle_cursor_down() + self:handle_cursor_up() self.modifiers = nil end end @@ -650,6 +685,13 @@ function Menu:render() end if update_cursor then self:select_item_below_cursor() end + cursor.on_primary_down = function() self:handle_cursor_down() end + cursor.on_primary_up = function() self:handle_cursor_up() end + if self.proximity_raw == 0 then + cursor.on_wheel_down = function() self:handle_wheel_down() end + cursor.on_wheel_up = function() self:handle_wheel_up() end + end + local ass = assdraw.ass_new() local opacity = options.menu_opacity * self.opacity local spacing = self.item_padding diff --git a/portable_config/scripts/uosc/elements/Speed.lua b/portable_config/scripts/uosc/elements/Speed.lua index 10d89a06..bc4f4405 100644 --- a/portable_config/scripts/uosc/elements/Speed.lua +++ b/portable_config/scripts/uosc/elements/Speed.lua @@ -44,9 +44,7 @@ function Speed:speed_step(speed, up) end end -function Speed:on_mbtn_left_down() - -- Don't accept clicks while hidden. - if self:get_visibility() <= 0 then return end +function Speed:handle_cursor_down() self:tween_stop() -- Stop and cleanup possible ongoing animations self.dragging = { start_time = mp.get_time(), @@ -87,14 +85,13 @@ function Speed:on_global_mouse_move() end end -function Speed:on_mbtn_left_up() - -- Reset speed on short clicks - if self.dragging and math.abs(self.dragging.distance) < 6 and mp.get_time() - self.dragging.start_time < 0.15 then - mp.set_property_native('speed', 1) +function Speed:handle_cursor_up() + if self.proximity_raw == 0 then + -- Reset speed on short clicks + if self.dragging and math.abs(self.dragging.distance) < 6 and mp.get_time() - self.dragging.start_time < 0.15 then + mp.set_property_native('speed', 1) + end end -end - -function Speed:on_global_mbtn_left_up() self.dragging = nil request_render() end @@ -104,8 +101,8 @@ function Speed:on_global_mouse_leave() request_render() end -function Speed:on_wheel_up() mp.set_property_native('speed', self:speed_step(state.speed, true)) end -function Speed:on_wheel_down() mp.set_property_native('speed', self:speed_step(state.speed, false)) end +function Speed:handle_wheel_up() mp.set_property_native('speed', self:speed_step(state.speed, true)) end +function Speed:handle_wheel_down() mp.set_property_native('speed', self:speed_step(state.speed, false)) end function Speed:render() local visibility = self:get_visibility() @@ -113,6 +110,18 @@ function Speed:render() if opacity <= 0 then return end + if self.proximity_raw == 0 then + cursor.on_primary_down = function() + self:handle_cursor_down() + cursor.on_primary_up = function() self:handle_cursor_up() end + end + cursor.on_wheel_down = function() self:handle_wheel_down() end + cursor.on_wheel_up = function() self:handle_wheel_up() end + end + if self.dragging then + cursor.on_primary_up = function() self:handle_cursor_up() end + end + local ass = assdraw.ass_new() -- Background diff --git a/portable_config/scripts/uosc/elements/Timeline.lua b/portable_config/scripts/uosc/elements/Timeline.lua index 92884def..a4730a28 100644 --- a/portable_config/scripts/uosc/elements/Timeline.lua +++ b/portable_config/scripts/uosc/elements/Timeline.lua @@ -6,6 +6,7 @@ local Timeline = class(Element) function Timeline:new() return Class.new(self) --[[@as Timeline]] end function Timeline:init() Element.init(self, 'timeline') + ---@type false|{pause: boolean, distance: number, last: {x: number, y: number}} self.pressed = false self.obstructed = false self.size_max = 0 @@ -13,7 +14,8 @@ function Timeline:init() self.size_min_override = options.timeline_start_hidden and 0 or nil self.font_size = 0 self.top_border = options.timeline_border - self.hovered_chapter = nil + self.is_hovered = false + self.has_thumbnail = false -- Release any dragging when file gets unloaded mp.register_event('end-file', function() self.pressed = false end) @@ -44,7 +46,7 @@ function Timeline:get_effective_line_width() return state.fullormaxed and options.timeline_line_width_fullscreen or options.timeline_line_width end -function Timeline:get_is_hovered() return self.enabled and (self.proximity_raw == 0 or self.hovered_chapter ~= nil) end +function Timeline:get_is_hovered() return self.enabled and self.is_hovered end function Timeline:update_dimensions() if state.fullormaxed then @@ -89,69 +91,48 @@ function Timeline:set_from_cursor(fast) mp.commandv('seek', self:get_time_at_x(cursor.x), fast and 'absolute+keyframes' or 'absolute+exact') end end -function Timeline:clear_thumbnail() mp.commandv('script-message-to', 'thumbfast', 'clear') end -function Timeline:determine_chapter_click_handler() - if self.hovered_chapter then - if not self.on_global_mbtn_left_down then - self.on_global_mbtn_left_down = function() - if self.hovered_chapter then mp.commandv('seek', self.hovered_chapter.time, 'absolute+exact') end - end - end - else - if self.on_global_mbtn_left_down then - self.on_global_mbtn_left_down = nil - if self.proximity_raw ~= 0 then self:clear_thumbnail() end - end - end +function Timeline:clear_thumbnail() + mp.commandv('script-message-to', 'thumbfast', 'clear') + self.has_thumbnail = false end -function Timeline:on_mbtn_left_down() - -- `self.on_global_mbtn_left_down` has precedent - if self.on_global_mbtn_left_down then return end - - self.pressed = true - self.pressed_pause = state.pause +function Timeline:handle_cursor_down() + self.pressed = {pause = state.pause, distance = 0, last = {x = cursor.x, y = cursor.y}} mp.set_property_native('pause', true) self:set_from_cursor() + cursor.on_primary_up = function() self:handle_cursor_up() end end function Timeline:on_prop_duration() self:decide_enabled() end function Timeline:on_prop_time() self:decide_enabled() end function Timeline:on_prop_border() self:update_dimensions() end function Timeline:on_prop_fullormaxed() self:update_dimensions() end function Timeline:on_display() self:update_dimensions() end -function Timeline:on_mouse_leave() - if not self.hovered_chapter then self:clear_thumbnail() end -end -function Timeline:on_global_mbtn_left_up() - if thumbnail.pause then thumbnail.pause = false end +function Timeline:handle_cursor_up() if self.pressed then - mp.set_property_native('pause', self.pressed_pause) + mp.set_property_native('pause', self.pressed.pause) self.pressed = false end - self:clear_thumbnail() end function Timeline:on_global_mouse_leave() self.pressed = false - self:clear_thumbnail() end Timeline.seek_timer = mp.add_timeout(0.05, function() Elements.timeline:set_from_cursor() end) Timeline.seek_timer:kill() function Timeline:on_global_mouse_move() if self.pressed then - thumbnail.pause = true - self:clear_thumbnail() + self.pressed.distance = self.pressed.distance + get_point_to_point_proximity(self.pressed.last, cursor) + self.pressed.last.x, self.pressed.last.y = cursor.x, cursor.y if self.width / state.duration < 10 then self:set_from_cursor(true) self.seek_timer:kill() self.seek_timer:resume() else self:set_from_cursor() end end - self:determine_chapter_click_handler() end -function Timeline:on_wheel_up() mp.commandv('seek', options.timeline_step) end -function Timeline:on_wheel_down() mp.commandv('seek', -options.timeline_step) end +function Timeline:handle_wheel_up() mp.commandv('seek', options.timeline_step) end +function Timeline:handle_wheel_down() mp.commandv('seek', -options.timeline_step) end function Timeline:render() if self.size_max == 0 then return end @@ -159,8 +140,23 @@ function Timeline:render() local size_min = self:get_effective_size_min() local size = self:get_effective_size() local visibility = self:get_visibility() + self.is_hovered = false - if size < 1 then return end + if size < 1 then + if self.has_thumbnail then self:clear_thumbnail() end + return + end + + if self.proximity_raw == 0 then + self.is_hovered = true + cursor.on_primary_down = function() self:handle_cursor_down() end + cursor.on_wheel_down = function() self:handle_wheel_down() end + cursor.on_wheel_up = function() self:handle_wheel_up() end + end + + if self.pressed then + cursor.on_primary_up = function() self:handle_cursor_up() end + end local ass = assdraw.ass_new() @@ -251,7 +247,7 @@ function Timeline:render() end -- Chapters - self.hovered_chapter = nil + local hovered_chapter = nil if (options.timeline_chapters_opacity > 0 and (#state.chapters > 0 or state.ab_loop_a or state.ab_loop_b) ) then @@ -277,7 +273,7 @@ function Timeline:render() if #state.chapters > 0 then -- Find hovered chapter indicator - local hovered_chapter, closest_delta = nil, infinity + local closest_delta = INFINITY if self.proximity_raw < diamond_radius_hovered then for i, chapter in ipairs(state.chapters) do @@ -285,6 +281,10 @@ function Timeline:render() local cursor_chapter_delta = math.sqrt((cursor.x - chapter_x) ^ 2 + (cursor.y - chapter_y) ^ 2) if cursor_chapter_delta <= diamond_radius_hovered and cursor_chapter_delta < closest_delta then hovered_chapter, closest_delta = chapter, cursor_chapter_delta + self.is_hovered = true + cursor.on_primary_down = function() + mp.commandv('seek', hovered_chapter.time, 'absolute+exact') + end end end end @@ -294,11 +294,7 @@ function Timeline:render() end -- Render hovered chapter above others - if hovered_chapter then - draw_chapter(hovered_chapter.time, diamond_radius_hovered) - self.hovered_chapter = hovered_chapter - self:determine_chapter_click_handler() - end + if hovered_chapter then draw_chapter(hovered_chapter.time, diamond_radius_hovered) end end -- A-B loop indicators @@ -366,10 +362,11 @@ function Timeline:render() end -- Hovered time and chapter - if (self.proximity_raw == 0 or self.pressed or self.hovered_chapter) and + local rendered_thumbnail = false + if (self.proximity_raw == 0 or self.pressed or hovered_chapter) and not (Elements.speed and Elements.speed.dragging) then - local cursor_x = self.hovered_chapter and t2x(self.hovered_chapter.time) or cursor.x - local hovered_seconds = self.hovered_chapter and self.hovered_chapter.time or self:get_time_at_x(cursor.x) + local cursor_x = hovered_chapter and t2x(hovered_chapter.time) or cursor.x + local hovered_seconds = hovered_chapter and hovered_chapter.time or self:get_time_at_x(cursor.x) -- Cursor line -- 0.5 to switch when the pixel is half filled in @@ -387,7 +384,11 @@ function Timeline:render() tooltip_anchor.ay = tooltip_anchor.ay - self.font_size - offset -- Thumbnail - if not thumbnail.disabled and thumbnail.width ~= 0 and thumbnail.height ~= 0 and not thumbnail.pause then + if not thumbnail.disabled + and (not self.pressed or self.pressed.distance < 5) + and thumbnail.width ~= 0 + and thumbnail.height ~= 0 + then local scale_x, scale_y = display.scale_x, display.scale_y local border, margin_x, margin_y = math.ceil(2 * scale_x), round(10 * scale_x), round(5 * scale_y) local thumb_x_margin, thumb_y_margin = border + margin_x, border + margin_y @@ -401,6 +402,7 @@ function Timeline:render() local bx, by = (thumb_x + thumb_width + border) / scale_x, (thumb_y + thumb_height + border) / scale_y ass:rect(ax, ay, bx, by, {color = bg, border = 1, border_color = fg, border_opacity = 0.08, radius = 2}) mp.commandv('script-message-to', 'thumbfast', 'thumb', hovered_seconds, thumb_x, thumb_y) + self.has_thumbnail, rendered_thumbnail = true, true tooltip_anchor.ax, tooltip_anchor.bx, tooltip_anchor.ay = ax, bx, ay end @@ -416,6 +418,9 @@ function Timeline:render() end end + -- Clear thumbnail + if not rendered_thumbnail and self.has_thumbnail then self:clear_thumbnail() end + return ass end diff --git a/portable_config/scripts/uosc/elements/TopBar.lua b/portable_config/scripts/uosc/elements/TopBar.lua index ac59d8be..dba3c390 100644 --- a/portable_config/scripts/uosc/elements/TopBar.lua +++ b/portable_config/scripts/uosc/elements/TopBar.lua @@ -16,7 +16,7 @@ function TopBarButton:init(id, props) self.command = props.command end -function TopBarButton:on_mbtn_left_down() +function TopBarButton:handle_cursor_down() mp.command(type(self.command) == 'function' and self.command() or self.command) end @@ -28,6 +28,7 @@ function TopBarButton:render() -- Background on hover if self.proximity_raw == 0 then ass:rect(self.ax, self.ay, self.bx, self.by, {color = self.background, opacity = visibility}) + cursor.on_primary_down = function() self:handle_cursor_down() end end local width, height = self.bx - self.ax, self.by - self.ay @@ -52,8 +53,9 @@ function TopBar:init() self.size_min_override = options.timeline_start_hidden and 0 or nil self.top_border = options.timeline_border self.show_alt_title = false + self.main_title, self.alt_title = nil, nil - local function decide_maximized_command() + local function get_maximized_command() return state.border and (state.fullscreen and 'set fullscreen no;cycle window-maximized' or 'cycle window-maximized') or 'set window-maximized no;cycle fullscreen' @@ -62,9 +64,11 @@ function TopBar:init() -- Order aligns from right to left self.buttons = { TopBarButton:new('tb_close', {icon = 'close', background = '2311e8', command = 'quit'}), - TopBarButton:new('tb_max', {icon = 'crop_square', background = '222222', command = decide_maximized_command}), + TopBarButton:new('tb_max', {icon = 'crop_square', background = '222222', command = get_maximized_command}), TopBarButton:new('tb_min', {icon = 'minimize', background = '222222', command = 'cycle window-minimized'}), } + + self:decide_titles() end function TopBar:decide_enabled() @@ -79,6 +83,32 @@ function TopBar:decide_enabled() end end +function TopBar:decide_titles() + self.alt_title = state.alt_title ~= '' and state.alt_title or nil + self.main_title = state.title ~= '' and state.title or nil + + -- Fall back to alt title if main is empty + if not self.main_title then + self.main_title, self.alt_title = self.alt_title, nil + end + + -- Deduplicate the main and alt titles by checking if one completely + -- contains the other, and using only the longer one. + if self.main_title and self.alt_title and not self.show_alt_title then + local longer_title, shorter_title + if #self.main_title < #self.alt_title then + longer_title, shorter_title = self.alt_title, self.main_title + else + longer_title, shorter_title = self.main_title, self.alt_title + end + + local escaped_shorter_title = string.gsub(shorter_title --[[@as string]], "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1") + if string.match(longer_title --[[@as string]], escaped_shorter_title) then + self.main_title, self.alt_title = longer_title, nil + end + end +end + function TopBar:update_dimensions() self.size = state.fullormaxed and options.top_bar_size_fullscreen or options.top_bar_size self.icon_size = round(self.size * 0.5) @@ -104,6 +134,9 @@ function TopBar:toggle_title() self.show_alt_title = not self.show_alt_title end +function TopBar:on_prop_title() self:decide_titles() end +function TopBar:on_prop_alt_title() self:decide_titles() end + function TopBar:on_prop_border() self:decide_enabled() self:update_dimensions() @@ -119,10 +152,6 @@ function TopBar:on_prop_maximized() self:update_dimensions() end -function TopBar:on_mbtn_left_down() - if cursor.x < self.title_bx then self:toggle_title() end -end - function TopBar:on_display() self:update_dimensions() end function TopBar:render() @@ -148,55 +177,71 @@ function TopBar:render() ass:rect(title_ax, title_ay, bx, self.by - bg_margin, {color = fg, opacity = visibility, radius = 2}) ass:txt(title_ax + (bx - title_ax) / 2, self.ay + (self.size / 2), 5, formatted_text, opts) title_ax = bx + bg_margin - end - - -- Title - local text = self.show_alt_title and state.alt_title or state.title - if max_bx - title_ax > self.font_size * 3 and text and text ~= '' then - local opts = { - size = self.font_size, wrap = 2, color = bgt, border = 1, border_color = bg, opacity = visibility, - clip = string.format('\\clip(%d, %d, %d, %d)', self.ax, self.ay, max_bx, self.by), - } - local bx = math.min(max_bx, title_ax + text_width(text, opts) + padding * 2) - local by = self.by - bg_margin - ass:rect(title_ax, title_ay, bx, by, { - color = bg, opacity = visibility * options.top_bar_title_opacity, radius = 2, - }) - ass:txt(title_ax + padding, self.ay + (self.size / 2), 4, text, opts) - title_ay = by + 1 - end + local rect = {ax = self.ax, ay = self.ay, bx = bx, by = self.by} - -- Alt title - if state.alt_title and options.top_bar_alt_title_place == 'below' and state.alt_title ~= state.title then - local font_size = self.font_size * 0.9 - local height = font_size * 1.3 - local by = title_ay + height - local opts = {size = font_size, wrap = 2, color = bgt, border = 1, border_color = bg, opacity = visibility} - local bx = math.min(max_bx, title_ax + text_width(state.alt_title, opts) + padding * 2) - opts.clip = string.format('\\clip(%d, %d, %d, %d)', title_ax, title_ay, bx, by) - ass:rect(title_ax, title_ay, bx, by, { - color = bg, opacity = visibility * options.top_bar_title_opacity, radius = 2, - }) - ass:txt(title_ax + padding, title_ay + height / 2, 4, state.alt_title, opts) - title_ay = by + 1 + if get_point_to_rectangle_proximity(cursor, rect) == 0 then + cursor.on_primary_down = function() mp.command('script-binding uosc/playlist') end + end end - -- Subtitle: current chapter - if state.current_chapter and max_bx - title_ax > self.font_size * 3 then - local font_size = self.font_size * 0.8 - local height = font_size * 1.3 - local text = '└ ' .. state.current_chapter.index .. ': ' .. state.current_chapter.title - local by = title_ay + height - local opts = { - size = font_size, italic = true, wrap = 2, color = bgt, - border = 1, border_color = bg, opacity = visibility * 0.8, - } - local bx = math.min(max_bx, title_ax + text_width(text, opts) + padding * 2) - opts.clip = string.format('\\clip(%d, %d, %d, %d)', title_ax, title_ay, bx, by) - ass:rect(title_ax, title_ay, bx, by, { - color = bg, opacity = visibility * options.top_bar_title_opacity, radius = 2, - }) - ass:txt(title_ax + padding, title_ay + height / 2, 4, text, opts) + -- Skip rendering titles if there's not enough horizontal space + if max_bx - title_ax > self.font_size * 3 then + -- Main title + local main_title = self.show_alt_title and self.alt_title or self.main_title + if main_title then + local opts = { + size = self.font_size, wrap = 2, color = bgt, border = 1, border_color = bg, opacity = visibility, + clip = string.format('\\clip(%d, %d, %d, %d)', self.ax, self.ay, max_bx, self.by), + } + local bx = math.min(max_bx, title_ax + text_width(main_title, opts) + padding * 2) + local by = self.by - bg_margin + local rect = {ax = title_ax, ay = self.ay, bx = self.title_bx, by = self.by} + + if get_point_to_rectangle_proximity(cursor, rect) == 0 then + cursor.on_primary_down = function() self:toggle_title() end + end + + ass:rect(title_ax, title_ay, bx, by, { + color = bg, opacity = visibility * options.top_bar_title_opacity, radius = 2, + }) + ass:txt(title_ax + padding, self.ay + (self.size / 2), 4, main_title, opts) + title_ay = by + 1 + end + + -- Alt title + if self.alt_title and options.top_bar_alt_title_place == 'below' then + local font_size = self.font_size * 0.9 + local height = font_size * 1.3 + local by = title_ay + height + local opts = { + size = font_size, wrap = 2, color = bgt, border = 1, border_color = bg, opacity = visibility + } + local bx = math.min(max_bx, title_ax + text_width(self.alt_title, opts) + padding * 2) + opts.clip = string.format('\\clip(%d, %d, %d, %d)', title_ax, title_ay, bx, by) + ass:rect(title_ax, title_ay, bx, by, { + color = bg, opacity = visibility * options.top_bar_title_opacity, radius = 2, + }) + ass:txt(title_ax + padding, title_ay + height / 2, 4, self.alt_title, opts) + title_ay = by + 1 + end + + -- Subtitle: current chapter + if state.current_chapter then + local font_size = self.font_size * 0.8 + local height = font_size * 1.3 + local text = '└ ' .. state.current_chapter.index .. ': ' .. state.current_chapter.title + local by = title_ay + height + local opts = { + size = font_size, italic = true, wrap = 2, color = bgt, + border = 1, border_color = bg, opacity = visibility * 0.8, + } + local bx = math.min(max_bx, title_ax + text_width(text, opts) + padding * 2) + opts.clip = string.format('\\clip(%d, %d, %d, %d)', title_ax, title_ay, bx, by) + ass:rect(title_ax, title_ay, bx, by, { + color = bg, opacity = visibility * options.top_bar_title_opacity, radius = 2, + }) + ass:txt(title_ax + padding, title_ay + height / 2, 4, text, opts) + end end end diff --git a/portable_config/scripts/uosc/elements/Volume.lua b/portable_config/scripts/uosc/elements/Volume.lua index 8934f8b6..356feca9 100644 --- a/portable_config/scripts/uosc/elements/Volume.lua +++ b/portable_config/scripts/uosc/elements/Volume.lua @@ -7,10 +7,12 @@ local MuteButton = class(Element) ---@param props? ElementProps function MuteButton:new(props) return Class.new(self, 'volume_mute', props) --[[@as MuteButton]] end function MuteButton:get_visibility() return Elements.volume:get_visibility(self) end -function MuteButton:on_mbtn_left_down() mp.commandv('cycle', 'mute') end function MuteButton:render() local visibility = self:get_visibility() if visibility <= 0 then return end + if self.proximity_raw == 0 then + cursor.on_primary_down = function() mp.commandv('cycle', 'mute') end + end local ass = assdraw.ass_new() local icon_name = state.mute and 'volume_off' or 'volume_up' local width = self.bx - self.ax @@ -58,17 +60,11 @@ function VolumeSlider:on_coordinates() self.spacing = round(width * 0.2) self.radius = math.max(2, (self.bx - self.ax) / 10) end -function VolumeSlider:on_mbtn_left_down() - self.pressed = true - self:set_from_cursor() -end -function VolumeSlider:on_global_mbtn_left_up() self.pressed = false end -function VolumeSlider:on_global_mouse_leave() self.pressed = false end function VolumeSlider:on_global_mouse_move() if self.pressed then self:set_from_cursor() end end -function VolumeSlider:on_wheel_up() self:set_volume(state.volume + options.volume_step) end -function VolumeSlider:on_wheel_down() self:set_volume(state.volume - options.volume_step) end +function VolumeSlider:handle_wheel_up() self:set_volume(state.volume + options.volume_step) end +function VolumeSlider:handle_wheel_down() self:set_volume(state.volume - options.volume_step) end function VolumeSlider:render() local visibility = self:get_visibility() @@ -77,8 +73,21 @@ function VolumeSlider:render() if width <= 0 or height <= 0 or visibility <= 0 then return end + if self.proximity_raw == 0 then + cursor.on_primary_down = function() + self.pressed = true + self:set_from_cursor() + cursor.on_primary_up = function() self.pressed = false end + end + cursor.on_wheel_down = function() self:handle_wheel_down() end + cursor.on_wheel_up = function() self:handle_wheel_up() end + end + if self.pressed then cursor.on_primary_up = function() + self.pressed = false end + end + local ass = assdraw.ass_new() - local nudge_y, nudge_size = self.draw_nudge and self.nudge_y or -infinity, self.nudge_size + local nudge_y, nudge_size = self.draw_nudge and self.nudge_y or -INFINITY, self.nudge_size local volume_y = self.ay + options.volume_border + ((height - (options.volume_border * 2)) * (1 - math.min(state.volume / state.volume_max, 1))) @@ -90,7 +99,7 @@ function VolumeSlider:render() local ax, bx, by = ax + p, bx - p, by - p local r = math.max(1, self.radius - p) local d, rh = r * 2, r / 2 - local nudge_size = ((quarter_pi_sin * (nudge_size - p)) + p) / quarter_pi_sin + local nudge_size = ((QUARTER_PI_SIN * (nudge_size - p)) + p) / QUARTER_PI_SIN local path = assdraw.ass_new() path:move_to(bx - r, by) path:line_to(ax + r, by) diff --git a/portable_config/scripts/uosc/lib/menus.lua b/portable_config/scripts/uosc/lib/menus.lua index 3c2de237..fce1443b 100644 --- a/portable_config/scripts/uosc/lib/menus.lua +++ b/portable_config/scripts/uosc/lib/menus.lua @@ -12,7 +12,7 @@ function open_command_menu(data, opts) ---@type MenuOptions local menu_opts = {} if opts then - menu_opts.submenu, menu_opts.mouse_nav = opts.submenu, opts.mouse_nav + menu_opts.mouse_nav = opts.mouse_nav if opts.on_close then menu_opts.on_close = function() run_command(opts.on_close) end end end local menu = Menu:open(data, run_command, menu_opts) @@ -26,7 +26,7 @@ function toggle_menu_with_items(opts) else open_command_menu({type = 'menu', items = config.menu_items}, opts) end end ----@param options {type: string; title: string; list_prop: string; active_prop?: string; serializer: fun(list: any, active: any): MenuDataItem[]; on_select: fun(value: any)} +---@param options {type: string; title: string; list_prop: string; active_prop?: string; serializer: fun(list: any, active: any): MenuDataItem[]; on_select: fun(value: any); on_move_item?: fun(from_index: integer, to_index: integer, submenu_path: integer[]); on_delete_item?: fun(index: integer, submenu_path: integer[])} function create_self_updating_menu_opener(options) return function() if Menu:is_open(options.type) then Menu:close() return end @@ -65,6 +65,8 @@ function create_self_updating_menu_opener(options) mp.unobserve_property(handle_list_prop_change) mp.unobserve_property(handle_active_prop_change) end, + on_move_item = options.on_move_item, + on_delete_item = options.on_delete_item, }) end end @@ -178,7 +180,7 @@ function open_file_navigation_menu(directory_path, handle_select, opts) local items = {} if is_root then - if state.os == 'windows' then + if state.platform == 'windows' then items[#items + 1] = {title = '..', hint = '驱动器列表', value = '{drives}', separator = true} end else diff --git a/portable_config/scripts/uosc/lib/text.lua b/portable_config/scripts/uosc/lib/text.lua index e310f2b6..b18a7515 100644 --- a/portable_config/scripts/uosc/lib/text.lua +++ b/portable_config/scripts/uosc/lib/text.lua @@ -251,8 +251,8 @@ do local unicode = utf8_to_unicode(char, 1) for _, block in ipairs(zero_width_blocks) do if unicode >= block[1] and unicode <= block[2] then - char_widths[char] = {0, infinity} - return 0, infinity + char_widths[char] = {0, INFINITY} + return 0, INFINITY end end @@ -302,7 +302,7 @@ end ---@return number, integer local function character_based_width(text, bold) local max_width = 0 - local min_px = infinity + local min_px = INFINITY for line in tostring(text):gmatch("([^\n]*)\n?") do local total_width = 0 for _, char in utf8_iter(line) do diff --git a/portable_config/scripts/uosc/lib/utils.lua b/portable_config/scripts/uosc/lib/utils.lua index 73f86290..e07c10d3 100644 --- a/portable_config/scripts/uosc/lib/utils.lua +++ b/portable_config/scripts/uosc/lib/utils.lua @@ -5,7 +5,7 @@ sort_filenames = (function() local symbol_order local default_order - if state.os == 'windows' then + if state.platform == 'windows' then symbol_order = { ['!'] = 1, ['#'] = 2, ['$'] = 3, ['%'] = 4, ['&'] = 5, ['('] = 6, [')'] = 6, [','] = 7, ['.'] = 8, ["'"] = 9, ['-'] = 10, [';'] = 11, ['@'] = 12, ['['] = 13, [']'] = 13, ['^'] = 14, @@ -93,6 +93,18 @@ function get_point_to_rectangle_proximity(point, rect) return math.sqrt(dx * dx + dy * dy) end +---@param point_a {x: number; y: number} +---@param point_b {x: number; y: number} +function get_point_to_point_proximity(point_a, point_b) + local dx, dy = point_a.x - point_b.x, point_a.y - point_b.y + return math.sqrt(dx * dx + dy * dy) +end + +-- Call function with args if it exists +function call_maybe(fn, ...) + if type(fn) == 'function' then fn(...) end +end + -- Extracts the properties used by property expansion of that string. ---@param str string ---@param res { [string] : boolean } | nil @@ -155,7 +167,7 @@ function opacity_to_alpha(opacity) end path_separator = (function() - local os_separator = state.os == 'windows' and '\\' or '/' + local os_separator = state.platform == 'windows' and '\\' or '/' -- Get appropriate path separator for the given path. ---@param path string @@ -181,7 +193,7 @@ end ---@return boolean function is_absolute(path) if path:sub(1, 2) == '\\\\' then return true - elseif state.os == 'windows' then return path:find('^%a+:') ~= nil + elseif state.platform == 'windows' then return path:find('^%a+:') ~= nil else return path:sub(1, 1) == '/' end end @@ -199,7 +211,7 @@ end function trim_trailing_separator(path) local separator = path_separator(path) path = trim_end(path, separator) - if state.os == 'windows' then + if state.platform == 'windows' then -- Drive letters on windows need trailing backslash if path:sub(#path) == ':' then path = path .. '\\' end else @@ -226,12 +238,12 @@ function normalize_path(path) path = ensure_absolute(path) local is_unc = path:sub(1, 2) == '\\\\' - if state.os == 'windows' or is_unc then path = path:gsub('/', '\\') end + if state.platform == 'windows' or is_unc then path = path:gsub('/', '\\') end path = trim_trailing_separator(path) --Deduplication of path separators if is_unc then path = path:gsub('(.\\)\\+', '%1') - elseif state.os == 'windows' then path = path:gsub('\\\\+', '\\') + elseif state.platform == 'windows' then path = path:gsub('\\\\+', '\\') else path = path:gsub('//+', '/') end return path @@ -393,7 +405,7 @@ end -- `status:number(<0=error), stdout, stderr, error_string, killed_by_us:boolean` ---@param path string function delete_file(path) - if state.os == 'windows' then + if state.platform == 'windows' then if options.use_trash then local ps_code = [[ Add-Type -AssemblyName Microsoft.VisualBasic @@ -428,10 +440,23 @@ end function serialize_chapter_ranges(normalized_chapters) local ranges = {} local simple_ranges = { - {name = 'openings', patterns = {'^op ', '^op$', ' op$', 'opening$'}, requires_next_chapter = true}, - {name = 'intros', patterns = {'^intro$'}, requires_next_chapter = true}, - {name = 'endings', patterns = {'^ed ', '^ed$', ' ed$', 'ending$', 'closing$'}}, - {name = 'outros', patterns = {'^outro$'}}, + {name = 'openings', patterns = { + '^op ', '^op$', ' op$', + '^opening$', ' opening$' + }, requires_next_chapter = true}, + {name = 'intros', patterns = { + '^intro$', ' intro$', + '^avant$', '^prologue$' + }, requires_next_chapter = true}, + {name = 'endings', patterns = { + '^ed ', '^ed$', ' ed$', + '^ending ', '^ending$', ' ending$', + }}, + {name = 'outros', patterns = { + '^outro$', ' outro$', + '^closing$', '^closing ', + '^preview$', '^pv$', + }}, } local sponsor_ranges = {} @@ -455,7 +480,7 @@ function serialize_chapter_ranges(normalized_chapters) if next_chapter or not meta.requires_next_chapter then ranges[#ranges + 1] = table_assign({ start = chapter.time, - ['end'] = next_chapter and next_chapter.time or infinity, + ['end'] = next_chapter and next_chapter.time or INFINITY, }, config.chapter_ranges[meta.name]) end end @@ -484,7 +509,7 @@ function serialize_chapter_ranges(normalized_chapters) local next_chapter = chapters[i + 1] ranges[#ranges + 1] = table_assign({ start = chapter.time, - ['end'] = next_chapter and next_chapter.time or infinity, + ['end'] = next_chapter and next_chapter.time or INFINITY, }, config.chapter_ranges.ads) end end @@ -540,6 +565,8 @@ function render() if not display.initialized then return end state.render_last_time = mp.get_time() + cursor.reset_handlers() + -- Actual rendering local ass = assdraw.ass_new() @@ -553,6 +580,8 @@ function render() end end + cursor.decide_keybinds() + -- submit if osd.res_x == display.width and osd.res_y == display.height and osd.data == ass.text then return diff --git a/portable_config/scripts/uosc/main.lua b/portable_config/scripts/uosc/main.lua index 3eefae1c..4b003286 100644 --- a/portable_config/scripts/uosc/main.lua +++ b/portable_config/scripts/uosc/main.lua @@ -1,6 +1,6 @@ --[[ SOURCE_ https://github.com/tomasklaen/uosc/tree/main/scripts -COMMIT_ 94ec120923cfdc973cb30a5acdb192c8ae005c19 +COMMIT_ 808fa5941842e3fdf115e3433d828ee56c1e4456 极简主义设计驱动的多功能界面脚本群组,兼容 thumbfast 新缩略图引擎 ]]-- @@ -13,8 +13,8 @@ opt = require('mp.options') utils = require('mp.utils') msg = require('mp.msg') osd = mp.create_osd_overlay('ass-events') -infinity = 1e309 -quarter_pi_sin = math.sin(math.pi / 4) +INFINITY = 1e309 +QUARTER_PI_SIN = math.sin(math.pi / 4) --[[ OPTIONS ]] @@ -101,7 +101,7 @@ defaults = { curtain_opacity = 0.5, stream_quality_options = '4320,2160,1440,1080,720,480,360,240,144', video_types= '3g2,3gp,asf,avi,f4v,flv,h264,h265,m2ts,m4v,mkv,mov,mp4,mp4v,mpeg,mpg,ogm,ogv,rm,rmvb,ts,vob,webm,wmv,y4m', - audio_types= 'aac,aiff,ape,au,dsf,dts,flac,m4a,mid,midi,mka,mp3,mp4a,oga,ogg,opus,spx,tak,tta,wav,weba,wma,wv', + audio_types= 'aac,ac3,aiff,ape,au,dsf,dts,flac,m4a,mid,midi,mka,mp3,mp4a,oga,ogg,opus,spx,tak,tta,wav,weba,wma,wv', image_types= 'apng,avif,bmp,gif,j2k,jp2,jfif,jpeg,jpg,jxl,mj2,png,svg,tga,tif,tiff,webp', subtitle_types = 'aqt,ass,gsub,idx,jss,lrc,mks,pgs,pjs,psb,rt,slt,smi,sub,sup,srt,ssa,ssf,ttxt,txt,usf,vt,vtt', default_directory = '~/', @@ -316,12 +316,62 @@ end --[[ STATE ]] display = {width = 1280, height = 720, scale_x = 1, scale_y = 1, initialized = false} -cursor = {hidden = true, hover_raw = false, x = 0, y = 0} +cursor = { + x = 0, + y = 0, + hidden = true, + hover_raw = false, + -- Event handlers that are only fired on cursor, bound during render loop. Guidelines: + -- - element activations (clicks) go to `mbtn_left_down` handler + -- - `mbtn_button_up` is only for clearing dragging/swiping + on_primary_down = nil, + on_primary_up = nil, + on_wheel_down = nil, + on_wheel_up = nil, + -- Called at the beginning of each render + reset_handlers = function() + cursor.on_primary_down, cursor.on_primary_up = nil, nil + cursor.on_wheel_down, cursor.on_wheel_up = nil, nil + end, + mbtn_left_enabled = nil, + wheel_enabled = nil, + -- Enables pointer key group captures needed by handlers (called at the end of each render) + decide_keybinds = function() + local enable_mbtn_left = (cursor.on_primary_down or cursor.on_primary_up) ~= nil + local enable_wheel = (cursor.on_wheel_down or cursor.on_wheel_up) ~= nil + if enable_mbtn_left ~= cursor.mbtn_left_enabled then + mp[(enable_mbtn_left and 'enable' or 'disable') .. '_key_bindings']('mbtn_left') + cursor.mbtn_left_enabled = enable_mbtn_left + end + if enable_wheel ~= cursor.wheel_enabled then + mp[(enable_wheel and 'enable' or 'disable') .. '_key_bindings']('wheel') + cursor.wheel_enabled = enable_wheel + end + end, + -- Cursor auto-hiding after period of inactivity + autohide = function() + if not Menu:is_open() then handle_mouse_leave() end + end, + autohide_timer = mp.add_timeout(mp.get_property_native('cursor-autohide') / 1000, function() + cursor.autohide() + end), + queue_autohide = function() + if options.autohide then + cursor.autohide_timer:kill() + cursor.autohide_timer:resume() + end + end +} state = { - os = (function() - if os.getenv('windir') ~= nil then return 'windows' end - local homedir = os.getenv('HOME') - if homedir ~= nil and string.sub(homedir, 1, 6) == '/Users' then return 'macos' end + platform = (function() + local platform = mp.get_property_native('platform') + if platform then + if itable_index_of({'windows', 'darwin'}, platform) then return platform end + else + if os.getenv('windir') ~= nil then return 'windows' end + local homedir = os.getenv('HOME') + if homedir ~= nil and string.sub(homedir, 1, 6) == '/Users' then return 'darwin' end + end return 'linux' end)(), cwd = mp.get_property('working-directory'), @@ -356,10 +406,6 @@ state = { has_chapter = false, has_playlist = false, shuffle = options.shuffle, - cursor_autohide_timer = mp.add_timeout(mp.get_property_native('cursor-autohide') / 1000, function() - if not options.autohide then return end - handle_mouse_leave() - end), mouse_bindings_enabled = false, uncached_ranges = nil, cache = nil, @@ -375,7 +421,7 @@ state = { margin_bottom = 0, hidpi_scale = 1, } -thumbnail = {width = 0, height = 0, disabled = false, pause = false} +thumbnail = {width = 0, height = 0, disabled = false} external = {} -- Properties set by external scripts key_binding_overwrites = {} -- Table of key_binding:mpv_command Elements = require('elements/Elements') @@ -420,6 +466,7 @@ function update_fullormaxed() state.fullormaxed = state.fullscreen or state.maximized update_display_dimensions() Elements:trigger('prop_fullormaxed', state.fullormaxed) + handle_mouse_move(INFINITY, INFINITY) end function update_human_times() @@ -482,7 +529,7 @@ function update_cursor_position(x, y) -- we receive a first real mouse move event with coordinates other than 0,0. if not state.first_real_mouse_move_received then if x > 0 and y > 0 then state.first_real_mouse_move_received = true - else x, y = infinity, infinity end + else x, y = INFINITY, INFINITY end end -- add 0.5 to be in the middle of the pixel @@ -518,12 +565,7 @@ function handle_mouse_move(x, y) update_cursor_position(x, y) Elements:proximity_trigger('mouse_move') request_render() - - -- Restart timer that hides UI when mouse is autohidden - if options.autohide then - state.cursor_autohide_timer:kill() - state.cursor_autohide_timer:resume() - end + cursor.queue_autohide() end function handle_file_end() @@ -687,16 +729,15 @@ mp.observe_property('duration', 'number', create_state_setter('duration', update mp.observe_property('speed', 'number', create_state_setter('speed', update_human_times)) mp.observe_property('track-list', 'native', function(name, value) -- checks the file dispositions - local is_image = false - local types = {sub = 0, audio = 0, video = 0} + local types = {sub = 0, image = 0, audio = 0, video = 0} for _, track in ipairs(value) do if track.type == 'video' then - is_image = track.image - if not is_image and not track.albumart then types.video = types.video + 1 end + if track.image or track.albumart then types.image = types.image + 1 + else types.video = types.video + 1 end elseif types[track.type] then types[track.type] = types[track.type] + 1 end end set_state('is_audio', types.video == 0 and types.audio > 0) - set_state('is_image', is_image) + set_state('is_image', types.image > 0 and types.video == 0 and types.audio == 0) set_state('has_audio', types.audio > 0) set_state('has_many_audio', types.audio > 1) set_state('has_sub', types.sub > 0) @@ -800,6 +841,23 @@ mp.observe_property('core-idle', 'native', create_state_setter('core_idle')) --[[ KEY BINDS ]] +-- Pointer related binding groups +mp.set_key_bindings({ + { + 'mbtn_left', + function(...) call_maybe(cursor.on_primary_up, ...) end, + function(...) + update_mouse_pos(nil, mp.get_property_native('mouse-pos')) + call_maybe(cursor.on_primary_down, ...) + end, + }, + {'mbtn_left_dbl', 'ignore'}, +}, 'mbtn_left', 'force') +mp.set_key_bindings({ + {'wheel_up', function(...) call_maybe(cursor.on_wheel_up, ...) end}, + {'wheel_down', function(...) call_maybe(cursor.on_wheel_down, ...) end}, +}, 'wheel', 'force') + -- Adds a key binding that respects rerouting set by `key_binding_overwrites` table. ---@param name string ---@param callback fun(event: table) @@ -889,6 +947,10 @@ bind_command('playlist', create_self_updating_menu_opener({ return items end, on_select = function(index) mp.commandv('set', 'playlist-pos-1', tostring(index)) end, + on_move_item = function(from, to) + mp.commandv('playlist-move', tostring(math.max(from, to) - 1), tostring(math.min(from, to) - 1)) + end, + on_delete_item = function(index) mp.commandv('playlist-remove', tostring(index - 1)) end, })) bind_command('chapters', create_self_updating_menu_opener({ title = '章节列表', @@ -933,11 +995,11 @@ bind_command('show-in-directory', function() -- Ignore URLs if not state.path or is_protocol(state.path) then return end - if state.os == 'windows' then + if state.platform == 'windows' then utils.subprocess_detached({args = {'explorer', '/select,', state.path}, cancellable = false}) - elseif state.os == 'macos' then + elseif state.platform == 'macos' then utils.subprocess_detached({args = {'open', '-R', state.path}, cancellable = false}) - elseif state.os == 'linux' then + elseif state.platform == 'linux' then local result = utils.subprocess({args = {'nautilus', state.path}, cancellable = false}) -- Fallback opens the folder with xdg-open instead @@ -1118,11 +1180,11 @@ bind_command('open-config-directory', function() if config then local args - if state.os == 'windows' then + if state.platform == 'windows' then args = {'explorer', '/select,', config.path} - elseif state.os == 'macos' then + elseif state.platform == 'macos' then args = {'open', '-R', config.path} - elseif state.os == 'linux' then + elseif state.platform == 'linux' then args = {'xdg-open', config.dirname} end diff --git a/portable_config/shaders/gaussianBlur_next.glsl b/portable_config/shaders/gaussianBlur_next.glsl new file mode 100644 index 00000000..424444eb --- /dev/null +++ b/portable_config/shaders/gaussianBlur_next.glsl @@ -0,0 +1,65 @@ +//!HOOK MAIN +//!BIND HOOKED +//!SAVE PASS0 +//!DESC gaussian blur pass0 + +vec4 hook() { + return linearize(textureLod(HOOKED_raw, HOOKED_pos, 0.0) * HOOKED_mul); +} + +//!HOOK MAIN +//!BIND PASS0 +//!SAVE PASS1 +//!DESC gaussian blur pass1 + +//////////////////////////////////////////////////////////////////////// +// USER CONFIGURABLE, PASS 1 (blur in y axis) +// +// CAUTION! probably should use the same settings for "USER CONFIGURABLE, PASS 2" below +// +#define SIGMA 1.0 //blur spread or amount, (0.0, 10+] +#define RADIUS 3.0 //kernel radius (integer as float, e.g. 3.0), (0.0, 10+]; probably should set it to ceil(3 * SIGMA) +// +//////////////////////////////////////////////////////////////////////// + +#define get_weight(x) (exp(-x * x / (2.0 * SIGMA * SIGMA))) + +vec4 hook() { + float weight; + vec4 csum = textureLod(PASS0_raw, PASS0_pos, 0.0) * PASS0_mul; + float wsum = 1.0; + for(float i = 1.0; i <= RADIUS; ++i) { + weight = get_weight(i); + csum += (textureLod(PASS0_raw, PASS0_pos + PASS0_pt * vec2(0.0, -i), 0.0) + textureLod(PASS0_raw, PASS0_pos + PASS0_pt * vec2(0.0, i), 0.0)) * PASS0_mul * weight; + wsum += 2.0 * weight; + } + return csum / wsum; +} + +//!HOOK MAIN +//!BIND PASS1 +//!DESC gaussian blur pass2 + +//////////////////////////////////////////////////////////////////////// +// USER CONFIGURABLE, PASS 2 (blur in x axis) +// +// CAUTION! probably should use the same settings for "USER CONFIGURABLE, PASS 1" above +// +#define SIGMA 1.0 //blur spread or amount, (0.0, 10+] +#define RADIUS 3.0 //kernel radius (integer as float, e.g. 3.0), (0.0, 10+]; probably should set it to ceil(3 * SIGMA) +// +//////////////////////////////////////////////////////////////////////// + +#define get_weight(x) (exp(-x * x / (2.0 * SIGMA * SIGMA))) + +vec4 hook() { + float weight; + vec4 csum = textureLod(PASS1_raw, PASS1_pos, 0.0) * PASS1_mul; + float wsum = 1.0; + for(float i = 1.0; i <= RADIUS; ++i) { + weight = get_weight(i); + csum += (textureLod(PASS1_raw, PASS1_pos + PASS1_pt * vec2(-i, 0.0), 0.0) + textureLod(PASS1_raw, PASS1_pos + PASS1_pt * vec2(i, 0.0), 0.0)) * PASS1_mul * weight; + wsum += 2.0 * weight; + } + return delinearize(csum / wsum); +} diff --git a/portable_config/shaders/guided.glsl b/portable_config/shaders/guided.glsl index 7d290255..3c804da3 100644 --- a/portable_config/shaders/guided.glsl +++ b/portable_config/shaders/guided.glsl @@ -77,8 +77,8 @@ vec4 hook() //!HOOK RGB //!DESC Guided filter (MEANI) //!BIND I -//!WIDTH I.w 2.0 / -//!HEIGHT I.h 2.0 / +//!WIDTH I.w 1.5 / +//!HEIGHT I.h 1.5 / //!SAVE MEANI vec4 hook() @@ -169,7 +169,7 @@ vec4 hook() //!HEIGHT I.h //!SAVE A -#define E 0.001 +#define E 0.0013 vec4 hook() { diff --git a/portable_config/shaders/guided_lgc.glsl b/portable_config/shaders/guided_lgc.glsl index 9207f4c4..5278740d 100644 --- a/portable_config/shaders/guided_lgc.glsl +++ b/portable_config/shaders/guided_lgc.glsl @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -//desc: Luma-guided-chroma guided filter +//desc: Luma-guided-chroma denoising. /* The radius can be adjusted with the MEANI stage's downscaling factor. * Higher numbers give a bigger radius. diff --git a/portable_config/shaders/guided_s.glsl b/portable_config/shaders/guided_s.glsl index 0bf3ad84..cc8f4467 100644 --- a/portable_config/shaders/guided_s.glsl +++ b/portable_config/shaders/guided_s.glsl @@ -46,8 +46,8 @@ vec4 hook() //!HOOK RGB //!DESC Guided filter (MEANIP) //!BIND IP -//!WIDTH IP.w 2.0 / -//!HEIGHT IP.h 2.0 / +//!WIDTH IP.w 1.5 / +//!HEIGHT IP.h 1.5 / //!SAVE MEANIP vec4 hook() @@ -93,7 +93,7 @@ vec4 hook() //!HEIGHT IP.h //!SAVE A -#define E 0.001 +#define E 0.002 vec4 hook() { diff --git a/portable_config/shaders/nlmeans.glsl b/portable_config/shaders/nlmeans.glsl index d2e119d0..cecbc09a 100644 --- a/portable_config/shaders/nlmeans.glsl +++ b/portable_config/shaders/nlmeans.glsl @@ -82,11 +82,10 @@ * - Reflections may have a significant speed impact * * Options which always disable textureGather: - * - RF * - PD */ -// The following is shader code injected from guided_s.glsl +// The following is shader code injected from guided.glsl /* vi: ft=c * * Copyright (c) 2022 an3223 @@ -105,25 +104,53 @@ * along with this program. If not, see . */ -//desc: "Self-guided" guided filter +//desc: Guided filter guided by the downscaled image -/* The radius can be adjusted with the MEANIP stage's downscaling factor. +/* The radius can be adjusted with the MEANI stage's downscaling factor. * Higher numbers give a bigger radius. * * The E variable can be found in the A stage. * - * The subsampling (fast guided filter) can be adjusted with the IP stage's + * The subsampling (fast guided filter) can be adjusted with the I stage's * downscaling factor. Higher numbers are faster. + * + * The guide's subsampling can be adjusted with the PREI stage's downscaling + * factor. Higher numbers downscale more. */ //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (IP) +//!DESC Guided filter (PREI) //!BIND HOOKED +//!WIDTH HOOKED.w 1.25 / +//!HEIGHT HOOKED.h 1.25 / +//!SAVE _INJ_PREI + +vec4 hook() +{ + return HOOKED_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (I) +//!BIND _INJ_PREI //!WIDTH HOOKED.w 1.0 / //!HEIGHT HOOKED.h 1.0 / -//!SAVE _INJ_IP +//!SAVE _INJ_I + +vec4 hook() +{ +return _INJ_PREI_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (P) +//!BIND HOOKED +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_P vec4 hook() { @@ -132,87 +159,124 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (MEANIP) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w 2.0 / -//!HEIGHT _INJ_IP.h 2.0 / -//!SAVE _INJ_MEANIP +//!DESC Guided filter (MEANI) +//!BIND _INJ_I +//!WIDTH _INJ_I.w 1.5 / +//!HEIGHT _INJ_I.h 1.5 / +//!SAVE _INJ_MEANI vec4 hook() { -return _INJ_IP_texOff(0); +return _INJ_I_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (_INJ_IP_SQ) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h -//!SAVE _INJ_IP_SQ +//!DESC Guided filter (MEANP) +//!BIND _INJ_P +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_MEANP vec4 hook() { -return _INJ_IP_texOff(0) * _INJ_IP_texOff(0); +return _INJ_P_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (CORRIP) -//!BIND _INJ_IP_SQ -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h -//!SAVE _INJ_CORRIP +//!DESC Guided filter (_INJ_I_SQ) +//!BIND _INJ_I +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_I_SQ vec4 hook() { -return _INJ_IP_SQ_texOff(0); +return _INJ_I_texOff(0) * _INJ_I_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (_INJ_IXP) +//!BIND _INJ_I +//!BIND _INJ_P +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_IXP + +vec4 hook() +{ +return _INJ_I_texOff(0) * _INJ_P_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (CORRI) +//!BIND _INJ_I_SQ +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_CORRI + +vec4 hook() +{ +return _INJ_I_SQ_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (CORRP) +//!BIND _INJ_IXP +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_CORRP + +vec4 hook() +{ +return _INJ_IXP_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (A) -//!BIND _INJ_MEANIP -//!BIND _INJ_CORRIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h +//!BIND _INJ_MEANI +//!BIND _INJ_MEANP +//!BIND _INJ_CORRI +//!BIND _INJ_CORRP +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h //!SAVE _INJ_A -#define E 0.001 +#define E 0.0013 vec4 hook() { -vec4 var = _INJ_CORRIP_texOff(0) - _INJ_MEANIP_texOff(0) * _INJ_MEANIP_texOff(0); - vec4 cov = var; +vec4 var = _INJ_CORRI_texOff(0) - _INJ_MEANI_texOff(0) * _INJ_MEANI_texOff(0); +vec4 cov = _INJ_CORRP_texOff(0) - _INJ_MEANI_texOff(0) * _INJ_MEANP_texOff(0); return cov / (var + E); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (B) //!BIND _INJ_A -//!BIND _INJ_MEANIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h +//!BIND _INJ_MEANI +//!BIND _INJ_MEANP +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h //!SAVE _INJ_B vec4 hook() { -return _INJ_MEANIP_texOff(0) - _INJ_A_texOff(0) * _INJ_MEANIP_texOff(0); +return _INJ_MEANP_texOff(0) - _INJ_A_texOff(0) * _INJ_MEANI_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (MEANA) //!BIND _INJ_A -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h //!SAVE _INJ_MEANA vec4 hook() @@ -222,11 +286,10 @@ return _INJ_A_texOff(0); //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (MEANB) //!BIND _INJ_B -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h //!SAVE _INJ_MEANB vec4 hook() @@ -236,7 +299,6 @@ return _INJ_B_texOff(0); //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter //!BIND HOOKED //!BIND _INJ_MEANA @@ -248,10 +310,9 @@ vec4 hook() return _INJ_MEANA_texOff(0) * HOOKED_texOff(0) + _INJ_MEANB_texOff(0); } -// End of source code injected from guided_s.glsl +// End of source code injected from guided.glsl //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Non-local means (downscale) //!WIDTH LUMA.w 3 / //!HEIGHT LUMA.h 3 / @@ -265,7 +326,6 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Non-local means (share) //!BIND RF_LUMA //!SAVE RF @@ -277,7 +337,6 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!BIND HOOKED //!BIND RF_LUMA //!BIND EP @@ -304,11 +363,10 @@ vec4 hook() * * Even-numbered patch/research sizes will sample between pixels unless PS=6. * It's not known whether this is ever useful behavior or not. This is - * incompatible with textureGather optimizations, so enable RF when using even - * patch/research sizes. + * incompatible with textureGather optimizations, so NG=1 to disable them. */ #ifdef LUMA_raw -#define S 2.25 +#define S 20.0 #define P 3 #define R 5 #else @@ -319,24 +377,37 @@ vec4 hook() /* Adaptive sharpening * - * Uses the blur incurred by denoising plus the weight map to perform an - * unsharp mask that gets applied most strongly to edges. + * Uses the blur incurred by denoising to perform an unsharp mask, and uses the + * weight map to restrict the sharpening to edges. * - * Sharpening will amplify noise, so the denoising factor (S) should usually be - * increased to compensate. + * Use M=4 to get a good look at which areas are/aren't sharpened. * * AS: 2 for sharpening, 1 for sharpening+denoising, 0 to disable * ASF: Sharpening factor, higher numbers make a sharper underlying image * ASP: Weight power, higher numbers use more of the sharp image + * ASW: + * - 0 to use pre-WD weights + * - 1 to use post-WD weights (ASP should be ~2x to compensate) + * ASK: Weight kernel: + * - 0 for power. This is the old method. + * - 1 for sigmoid. This is generally recommended. + * - 2 for constant (non-adaptive, w/ ASP=0 this sharpens the entire image) + * ASC (only for ASK=1, range 0-1): Reduces the contrast of the edge map */ #ifdef LUMA_raw #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #else #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #endif /* Starting weight @@ -345,8 +416,8 @@ vec4 hook() * handle higher noise levels, ringing, and may be useful for other things too? * * EPSILON should be used instead of zero to avoid divide-by-zero errors. The - * avg_weight variable may be used to make SW adapt to the local noise level, - * e.g., SW=max(avg_weight, EPSILON) + * avg_weight/old_avg_weight variables may be used to make SW adapt to the + * local noise level, e.g., SW=max(avg_weight, EPSILON) */ #ifdef LUMA_raw #define SW 1.0 @@ -366,7 +437,7 @@ vec4 hook() * - 0: Disable * * WDT: Threshold coefficient, higher numbers discard more - * WDP (WD=1): Higher numbers reduce the threshold more for small sample sizes + * WDP (only for WD=1): Increasing reduces the threshold for small sample sizes */ #ifdef LUMA_raw #define WD 2 @@ -378,6 +449,49 @@ vec4 hook() #define WDP 6.0 #endif +/* Extremes preserve + * + * Reduces denoising around very bright/dark areas. The downscaling factor of + * EP (located near the top of this shader) controls the area sampled for + * luminance (higher numbers consider more area). + * + * This is incompatible with RGB. If you have RGB hooks enabled then you will + * have to delete the EP shader stage or specify EP=0 through nlmeans_cfg. + * + * EP: 1 to enable, 0 to disable + * DP: EP strength on dark patches, 0 to fully denoise + * BP: EP strength on bright patches, 0 to fully denoise + */ +#ifdef LUMA_raw +#define EP 1 +#define BP 0.75 +#define DP 0.25 +#else +#define EP 0 +#define BP 0.0 +#define DP 0.0 +#endif + +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ + +/* Robust filtering + * + * This setting is dependent on code generation from nlmeans_cfg, so this + * setting can only be enabled via nlmeans_cfg. + * + * Compares the pixel-of-interest against a guide, which could be a downscaled + * image or the output of another shader such as guided.glsl + */ +#ifdef LUMA_raw +#define RF 1 +#else +#define RF 1 +#endif + /* Search shape * * Determines the shape of patches and research zones. Different shapes have @@ -386,6 +500,8 @@ vec4 hook() * * PS applies applies to patches, RS applies to research zones. * + * Be wary of gather optimizations (see the Regarding Speed comment at the top) + * * 0: square (symmetrical) * 1: horizontal line (asymmetric) * 2: vertical line (asymmetric) @@ -432,8 +548,9 @@ vec4 hook() * - Buggy * * Gather samples across multiple frames. May cause motion blur and may - * struggle more with noise that persists across multiple frames (compression - * noise, repeating frames), but can work very well on high quality video. + * struggle more with noise that persists across multiple frames (e.g., from + * compression or duplicate frames), but can work very well on high quality + * video. * * Motion estimation (ME) should improve quality without impacting speed. * @@ -468,60 +585,51 @@ vec4 hook() */ #ifdef LUMA_raw #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #else #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #endif -/* Extremes preserve - * - * Reduces denoising around very bright/dark areas. The downscaling factor of - * EP (located near the top of this shader) controls the area sampled for - * luminance (higher numbers consider more area). +// Scaling factor (should match WIDTH/HEIGHT) +#ifdef LUMA_raw +#define SF 1 +#else +#define SF 1 +#endif + +/* Estimator * - * EP: 1 to enable, 0 to disable - * DP: EP strength on dark patches, 0 to fully denoise - * BP: EP strength on bright patches, 0 to fully denoise + * 0: means + * 1: Euclidean medians (extremely slow, may be good for heavy noise) + * 2: weight map (not a denoiser, maybe useful for generating image masks) + * 3: weighted median intensity (slow, may be good for heavy noise) + * 4: edge map (based on the relevant AS settings) */ #ifdef LUMA_raw -#define EP 1 -#define BP 0.75 -#define DP 0.25 +#define M 0 #else -#define EP 0 -#define BP 0.0 -#define DP 0.0 +#define M 0 #endif -/* Robust filtering - * - * This setting is dependent on code generation from nlmeans_cfg, so this - * setting can only be enabled via nlmeans_cfg. +/* Difference visualization * - * Compares the pixel-of-interest against downscaled pixels. + * Visualizes the difference between input/output image * - * This will virtually always improve quality, but will always disable - * textureGather optimizations. - * - * The downscale factor can be modified in the WIDTH/HEIGHT directives for the - * RF texture (for CHROMA, RGB) and RF_LUMA (LUMA only) textures near the top - * of this shader, higher numbers increase blur. - * - * Any notation of RF as a positive number should be assumed to be referring to - * the downscaling factor, e.g., RF=3 means RF is set to 1 and the downscaling - * factor is set to 3. + * 0: off + * 1: absolute difference scaled by S + * 2: difference centered on 0.5 */ #ifdef LUMA_raw -#define RF 1 +#define DV 0 #else -#define RF 1 +#define DV 0 #endif /* Blur factor @@ -535,30 +643,11 @@ vec4 hook() #define BF 1.0 #endif -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ - -// Scaling factor (should match WIDTH/HEIGHT) -#ifdef LUMA_raw -#define SF 1 -#else -#define SF 1 -#endif - -/* Estimator - * - * 0: means - * 1: Euclidean medians (extremely slow, may be good for heavy noise) - * 2: weight map (not a denoiser, maybe useful for generating image masks) - * 3: weighted median intensity (slow, may be good for heavy noise) - */ +// Force disable textureGather #ifdef LUMA_raw -#define M 0 +#define NG 0 #else -#define M 0 +#define NG 0 #endif // Patch donut (probably useless) @@ -568,7 +657,7 @@ vec4 hook() #define PD 0 #endif -// Duplicate 1st weight +// Duplicate 1st weight (for LGC) #ifdef LUMA_raw #define D1W 0 #else @@ -578,6 +667,7 @@ vec4 hook() /* Shader code */ #define EPSILON 0.00000000001 +#define M_PI 3.14159265358979323846 #if PS == 6 const int hp = P/2; @@ -627,6 +717,7 @@ const float hr = int(R/2) - 0.5*(1-(R%2)); // sample between pixels for even res #define R_AREA(a) (a * T1 + RF-1) // research shapes +// XXX would be nice to have the option of temporally-varying research sizes #if R == 0 || R == 1 #define FOR_RESEARCH(r) S_1X1(r) const int r_area = R_AREA(1); @@ -798,11 +889,13 @@ vec4 patch_comparison(vec3 r, vec3 r2) return min_rot * p_scale; } -#define NO_GATHER (PD == 0) // never textureGather if any of these conditions are false +#define NO_GATHER (PD == 0 && NG == 0) // never textureGather if any of these conditions are false #define REGULAR_ROTATIONS (RI == 0 || RI == 1 || RI == 3) #if (defined(LUMA_gather) || D1W) && ((PS == 3 || PS == 7) && P == 3) && PST == 0 && M != 1 && REGULAR_ROTATIONS && NO_GATHER // 3x3 diamond/plus patch_comparison_gather +// XXX extend to support arbitrary sizes (probably requires code generation) +// XXX extend to support 3x3 square const ivec2 offsets[4] = { ivec2(0,-1), ivec2(-1,0), ivec2(0,1), ivec2(1,0) }; const ivec2 offsets_sf[4] = { ivec2(0,-1) * SF, ivec2(-1,0) * SF, ivec2(0,1) * SF, ivec2(1,0) * SF }; vec4 poi_patch = gather_offs(0, offsets); @@ -832,6 +925,7 @@ vec4 patch_comparison_gather(vec3 r, vec3 r2) } #elif (defined(LUMA_gather) || D1W) && PS == 6 && REGULAR_ROTATIONS && NO_GATHER // tiled even square patch_comparison_gather +// XXX extend to support odd square? vec4 patch_comparison_gather(vec3 r, vec3 r2) { vec2 tile; @@ -912,6 +1006,7 @@ vec4 hook() #endif FOR_FRAME(r) { + // XXX ME is always a frame behind, should have to option to re-research after applying ME (could do it an arbitrary number of times per frame if desired) #if T && ME == 1 // temporal & motion estimation max weight if (r.z > 0) { me += me_tmp; @@ -982,6 +1077,7 @@ vec4 hook() } // FOR_RESEARCH } // FOR_FRAME + // XXX optionally put the denoised pixel into the frame buffer? #if T // temporal #endif @@ -1033,12 +1129,29 @@ vec4 hook() result = sum / total_weight; #endif +#if ASW == 0 // pre-WD weights +#define AS_weight old_avg_weight +#elif ASW == 1 // post-WD weights +#define AS_weight avg_weight +#endif + +#if ASK == 0 + vec4 sharpening_strength = pow(AS_weight, vec4(ASP)); +#elif ASK == 1 +#define sigmoid(x) (tanh(x * 2*M_PI - M_PI)*0.5+0.5) + vec4 sharpening_strength = mix(pow(sigmoid(AS_weight), vec4(ASP)), + AS_weight, ASC); + // just in case ASC < 0 (will sharpen but it's janky XXX) + sharpening_strength = clamp(sharpening_strength, 0.0, 1.0); +#elif ASK == 2 + vec4 sharpening_strength = vec4(ASP); +#endif + + // XXX maybe allow for alternative blurs? e.g., replace result w/ load2? #if AS == 1 // sharpen+denoise vec4 sharpened = result + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #elif AS == 2 // sharpen only vec4 sharpened = poi + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #endif #if EP // extremes preserve @@ -1049,9 +1162,23 @@ vec4 hook() #endif #if AS == 1 // sharpen+denoise - result = mix(sharpened, result, sharpening_power); + result = mix(sharpened, result, sharpening_strength); #elif AS == 2 // sharpen only - result = mix(sharpened, poi, sharpening_power); + result = mix(sharpened, poi, sharpening_strength); +#endif + +#if M == 4 // edge map + result = sharpening_strength; +#endif + +#if (M == 2 || M == 4) && defined(CHROMA_raw) // drop chroma for weight maps + result = vec4(0.5); +#endif + +#if DV == 1 + result = clamp(abs(poi - result) * S, 0.0, 1.0); +#elif DV == 2 + result = (poi - result) * 0.5 + 0.5; #endif return mix(poi, result, BF); diff --git a/portable_config/shaders/nlmeans_anime.glsl b/portable_config/shaders/nlmeans_anime.glsl deleted file mode 100644 index 51d1327c..00000000 --- a/portable_config/shaders/nlmeans_anime.glsl +++ /dev/null @@ -1,1044 +0,0 @@ -/* vi: ft=c - * - * Based on vf_nlmeans.c from FFmpeg. - * - * Copyright (c) 2022 an3223 - * Copyright (c) 2016 Clément Bœsch - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -// Profile description: Tuned for anime/cartoons, may be useful for other content. - -/* The recommended usage of this shader and its variant profiles is to add them - * to input.conf and then dispatch the appropriate shader via a keybind during - * media playback. Here is an example input.conf entry: - * - * F4 no-osd change-list glsl-shaders toggle "~~/shaders/nlmeans_luma.glsl"; show-text "Non-local means (LUMA only)" - * - * These shaders can also be enabled by default in mpv.conf, for example: - * - * glsl-shaders='~~/shaders/nlmeans.glsl' - * - * Both of the examples above assume the shaders are located in a subdirectory - * named "shaders" within mpv's config directory. Refer to the mpv - * documentation for more details. - * - * This shader is highly configurable via user variables below. Although the - * default settings should offer good quality at a reasonable speed, you are - * encouraged to tweak them to your preferences. Be mindful that certain - * settings may greatly affect speed. - * - * Denoising is most useful for noisy content. If there is no perceptible - * noise, you probably won't see a positive difference. - * - * The default settings are generally tuned for low noise and high detail - * preservation. The "medium" and "heavy" profiles are tuned for higher levels - * of noise. - * - * The denoiser will not work properly if the content has been upscaled - * beforehand, whether it was done by you or someone down the line. Consider - * issuing a command to downscale in the mpv console, like so: - * - * vf toggle scale=-2:720 - * - * ...replacing 720 with whatever resolution seems appropriate. Rerun the - * command to undo the downscale. It may take some trial-and-error to find the - * proper resolution. - */ - -/* Regarding speed - * - * Speed may vary wildly for different vo and gpu-api settings. Generally - * vo=gpu-next and gpu-api=vulkan are recommended for the best speed, but this - * may be different for your system. - * - * If your GPU doesn't support textureGather, or if you are on a version of mpv - * prior to 0.35.0, then consider setting RI/RFI to 0, or try the LQ and VLQ - * profiles. - * - * textureGather is LUMA only and limited to the following configurations: - * - * - PS={3,7}:P=3:PST=0:RI={0,1,3}:RFI={0,1,2}:M!=1 - * - Default, very fast, rotations and reflections should be free - * - If this is unusually slow then try changing gpu-api and vo - * - If it's still slow, try setting RI/RFI to 0. - * - * - PS=6:RI={0,1,3}:RFI={0,1,2} - * - Currently the only scalable variant - * - Patch shape is asymmetric on two axis - * - Rotations should have very little speed impact - * - Reflections may have a significant speed impact - * - * Options which always disable textureGather: - * - RF - * - PD - */ - -// The following is shader code injected from guided_s.glsl -/* vi: ft=c - * - * Copyright (c) 2022 an3223 - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -//desc: "Self-guided" guided filter - -/* The radius can be adjusted with the MEANIP stage's downscaling factor. - * Higher numbers give a bigger radius. - * - * The E variable can be found in the A stage. - * - * The subsampling (fast guided filter) can be adjusted with the IP stage's - * downscaling factor. Higher numbers are faster. - */ - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (IP) -//!BIND HOOKED -//!WIDTH HOOKED.w 1.0 / -//!HEIGHT HOOKED.h 1.0 / -//!SAVE _INJ_IP - -vec4 hook() -{ - return HOOKED_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (MEANIP) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w 2.0 / -//!HEIGHT _INJ_IP.h 2.0 / -//!SAVE _INJ_MEANIP - -vec4 hook() -{ -return _INJ_IP_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (_INJ_IP_SQ) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h -//!SAVE _INJ_IP_SQ - -vec4 hook() -{ -return _INJ_IP_texOff(0) * _INJ_IP_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (CORRIP) -//!BIND _INJ_IP_SQ -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h -//!SAVE _INJ_CORRIP - -vec4 hook() -{ -return _INJ_IP_SQ_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (A) -//!BIND _INJ_MEANIP -//!BIND _INJ_CORRIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h -//!SAVE _INJ_A - -#define E 0.001 - -vec4 hook() -{ -vec4 var = _INJ_CORRIP_texOff(0) - _INJ_MEANIP_texOff(0) * _INJ_MEANIP_texOff(0); - vec4 cov = var; - return cov / (var + E); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (B) -//!BIND _INJ_A -//!BIND _INJ_MEANIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h -//!SAVE _INJ_B - -vec4 hook() -{ -return _INJ_MEANIP_texOff(0) - _INJ_A_texOff(0) * _INJ_MEANIP_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (MEANA) -//!BIND _INJ_A -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h -//!SAVE _INJ_MEANA - -vec4 hook() -{ -return _INJ_A_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (MEANB) -//!BIND _INJ_B -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h -//!SAVE _INJ_MEANB - -vec4 hook() -{ -return _INJ_B_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter -//!BIND HOOKED -//!BIND _INJ_MEANA -//!BIND _INJ_MEANB -//!SAVE RF_LUMA - -vec4 hook() -{ -return _INJ_MEANA_texOff(0) * HOOKED_texOff(0) + _INJ_MEANB_texOff(0); -} - -// End of source code injected from guided_s.glsl -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Non-local means (share) -//!BIND RF_LUMA -//!SAVE RF - -vec4 hook() -{ - return RF_LUMA_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!BIND HOOKED -//!BIND RF_LUMA -//!BIND RF -//!DESC Non-local means (nlmeans_anime.glsl) - -/* User variables - * - * It is usually preferable to denoise chroma and luma differently, so the user - * variables for luma and chroma are split. - */ - -/* S = denoising factor - * P = patch size - * R = research size - * - * The denoising factor controls the level of blur, higher is blurrier. - * - * Patch size should usually be an odd number greater than or equal to 3. - * Higher values are slower and not always better. - * - * Research size usually be an odd number greater than or equal to 3. Higher - * values are usually better, but slower and offer diminishing returns. - * - * Even-numbered patch/research sizes will sample between pixels unless PS=6. - * It's not known whether this is ever useful behavior or not. This is - * incompatible with textureGather optimizations, so enable RF when using even - * patch/research sizes. - */ -#ifdef LUMA_raw -#define S 3 -#define P 3 -#define R 5 -#else -#define S 3 -#define P 3 -#define R 5 -#endif - -/* Adaptive sharpening - * - * Uses the blur incurred by denoising plus the weight map to perform an - * unsharp mask that gets applied most strongly to edges. - * - * Sharpening will amplify noise, so the denoising factor (S) should usually be - * increased to compensate. - * - * AS: 2 for sharpening, 1 for sharpening+denoising, 0 to disable - * ASF: Sharpening factor, higher numbers make a sharper underlying image - * ASP: Weight power, higher numbers use more of the sharp image - */ -#ifdef LUMA_raw -#define AS 0 -#define ASF 1.0 -#define ASP 2.0 -#else -#define AS 0 -#define ASF 1.0 -#define ASP 2.0 -#endif - -/* Starting weight - * - * Lower numbers give less weight to the pixel-of-interest, which may help - * handle higher noise levels, ringing, and may be useful for other things too? - * - * EPSILON should be used instead of zero to avoid divide-by-zero errors. The - * avg_weight variable may be used to make SW adapt to the local noise level, - * e.g., SW=max(avg_weight, EPSILON) - */ -#ifdef LUMA_raw -#define SW 1.0 -#else -#define SW 1.0 -#endif - -/* Weight discard - * - * Discard weights that fall below a fraction of the average weight. This culls - * the most dissimilar samples from the blur, yielding a much more pleasant - * result, especially around edges. - * - * WD: - * - 2: True average. Very good quality, but slower and uses more memory. - * - 1: Moving cumulative average. Inaccurate, tends to blur directionally. - * - 0: Disable - * - * WDT: Threshold coefficient, higher numbers discard more - * WDP (WD=1): Higher numbers reduce the threshold more for small sample sizes - */ -#ifdef LUMA_raw -#define WD 2 -#define WDT 1.0 -#define WDP 6.0 -#else -#define WD 2 -#define WDT 1.0 -#define WDP 6.0 -#endif - -/* Search shape - * - * Determines the shape of patches and research zones. Different shapes have - * different speed and quality characteristics. Every shape (besides square) is - * smaller than square. - * - * PS applies applies to patches, RS applies to research zones. - * - * 0: square (symmetrical) - * 1: horizontal line (asymmetric) - * 2: vertical line (asymmetric) - * 3: diamond (symmetrical) - * 4: triangle (asymmetric, pointing upward) - * 5: truncated triangle (asymmetric on two axis, last row halved) - * 6: even sized square (asymmetric on two axis) - * 7: plus (symmetrical) - */ -#ifdef LUMA_raw -#define RS 3 -#define PS 3 -#else -#define RS 3 -#define PS 3 -#endif - -/* Rotational/reflectional invariance - * - * Number of rotations/reflections to try for each patch comparison. Slow, but - * improves feature preservation, although adding more rotations/reflections - * gives diminishing returns. The most similar rotation/reflection will be used. - * - * The angle in degrees of each rotation is 360/(RI+1), so RI=1 will do a - * single 180 degree rotation, RI=3 will do three 90 degree rotations, etc. - * - * RI: Rotational invariance - * RFI (0 to 2): Reflectional invariance - */ -#ifdef LUMA_raw -#define RI 3 -#define RFI 2 -#else -#define RI 0 -#define RFI 0 -#endif - -/* Temporal denoising - * - * Caveats: - * - Slower, each frame needs to be researched - * - Requires vo=gpu-next and nlmeans_temporal.glsl - * - Luma-only (this is a bug) - * - Buggy - * - * Gather samples across multiple frames. May cause motion blur and may - * struggle more with noise that persists across multiple frames (compression - * noise, repeating frames), but can work very well on high quality video. - * - * Motion estimation (ME) should improve quality without impacting speed. - * - * T: number of frames used - * ME: motion estimation, 0 for none, 1 for max weight, 2 for weighted avg - */ -#ifdef LUMA_raw -#define T 0 -#define ME 1 -#else -#define T 0 -#define ME 0 -#endif - -/* Spatial kernel - * - * Increasing the spatial denoising factor (SS) reduces the weight of further - * pixels. - * - * Spatial distortion instructs the spatial kernel to view that axis as - * closer/further, for instance SD=(1,1,0.5) would make the temporal axis - * appear closer and increase blur between frames. - * - * The intra-patch variants do not yet have well-understood effects. They are - * intended to make large patch sizes more useful. Likely slower. - * - * SS: spatial denoising factor - * SD: spatial distortion (X, Y, time) - * PSS: intra-patch spatial denoising factor - * PST: enables intra-patch spatial kernel if P>=PST, 0 fully disables - * PSD: intra-patch spatial distortion (X, Y) - */ -#ifdef LUMA_raw -#define SS 0.0 -#define SD vec3(1,1,1) -#define PST 0 -#define PSS 0.0 -#define PSD vec2(1,1) -#else -#define SS 0.0 -#define SD vec3(1,1,1) -#define PST 0 -#define PSS 0.0 -#define PSD vec2(1,1) -#endif - -/* Extremes preserve - * - * Reduces denoising around very bright/dark areas. The downscaling factor of - * EP (located near the top of this shader) controls the area sampled for - * luminance (higher numbers consider more area). - * - * EP: 1 to enable, 0 to disable - * DP: EP strength on dark patches, 0 to fully denoise - * BP: EP strength on bright patches, 0 to fully denoise - */ -#ifdef LUMA_raw -#define EP 0 -#define BP 0.75 -#define DP 0.25 -#else -#define EP 0 -#define BP 0.0 -#define DP 0.0 -#endif - -/* Robust filtering - * - * This setting is dependent on code generation from nlmeans_cfg, so this - * setting can only be enabled via nlmeans_cfg. - * - * Compares the pixel-of-interest against downscaled pixels. - * - * This will virtually always improve quality, but will always disable - * textureGather optimizations. - * - * The downscale factor can be modified in the WIDTH/HEIGHT directives for the - * RF texture (for CHROMA, RGB) and RF_LUMA (LUMA only) textures near the top - * of this shader, higher numbers increase blur. - * - * Any notation of RF as a positive number should be assumed to be referring to - * the downscaling factor, e.g., RF=3 means RF is set to 1 and the downscaling - * factor is set to 3. - */ -#ifdef LUMA_raw -#define RF 1 -#else -#define RF 1 -#endif - -/* Blur factor - * - * 0 to 1, only useful for alternative estimators. You're probably looking for - * "S" (denoising factor), go back to the top of the shader! - */ -#ifdef LUMA_raw -#define BF 1.0 -#else -#define BF 1.0 -#endif - -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ - -// Scaling factor (should match WIDTH/HEIGHT) -#ifdef LUMA_raw -#define SF 1 -#else -#define SF 1 -#endif - -/* Estimator - * - * 0: means - * 1: Euclidean medians (extremely slow, may be good for heavy noise) - * 2: weight map (not a denoiser, maybe useful for generating image masks) - * 3: weighted median intensity (slow, may be good for heavy noise) - */ -#ifdef LUMA_raw -#define M 0 -#else -#define M 0 -#endif - -// Patch donut (probably useless) -#ifdef LUMA_raw -#define PD 0 -#else -#define PD 0 -#endif - -// Duplicate 1st weight -#ifdef LUMA_raw -#define D1W 0 -#else -#define D1W 0 -#endif - -/* Shader code */ - -#define EPSILON 0.00000000001 - -#if PS == 6 -const int hp = P/2; -#else -const float hp = int(P/2) - 0.5*(1-(P%2)); // sample between pixels for even patch sizes -#endif - -#if RS == 6 -const int hr = R/2; -#else -const float hr = int(R/2) - 0.5*(1-(R%2)); // sample between pixels for even research sizes -#endif - -// donut increment, increments without landing on (0,0,0) -// much faster than a "continue" statement -#define DINCR(z,c) (z.c++,(z.c += int(z == vec3(0)))) - -// search shapes and their corresponding areas -#define S_1X1(z) for (z = vec3(0); z.x <= 0; z.x++) - -#define S_TRIANGLE(z,hz,incr) for (z.y = -hz; z.y <= 0; z.y++) for (z.x = -abs(abs(z.y) - hz); z.x <= abs(abs(z.y) - hz); incr) -#define S_TRUNC_TRIANGLE(z,hz,incr) for (z.y = -hz; z.y <= 0; z.y++) for (z.x = -abs(abs(z.y) - hz); z.x <= abs(abs(z.y) - hz)*int(z.y!=0); incr) -#define S_TRIANGLE_A(hz,Z) int(pow(hz, 2)+Z) - -#define S_DIAMOND(z,hz,incr) for (z.x = -hz; z.x <= hz; z.x++) for (z.y = -abs(abs(z.x) - hz); z.y <= abs(abs(z.x) - hz); incr) -#define S_DIAMOND_A(hz,Z) int(pow(hz, 2)*2+Z) - -#define S_VERTICAL(z,hz,incr) for (z.x = 0; z.x <= 0; z.x++) for (z.y = -hz; z.y <= hz; incr) -#define S_HORIZONTAL(z,hz,incr) for (z.x = -hz; z.x <= hz; incr) for (z.y = 0; z.y <= 0; z.y++) - -#define S_PLUS(z,hz,incr) for (z.x = -hz; z.x <= hz; z.x++) for (z.y = -hz * int(z.x == 0); z.y <= hz * int(z.x == 0); incr) -#define S_PLUS_A(hz,Z) (Z*2 - 1) - -#define S_SQUARE(z,hz,incr) for (z.x = -hz; z.x <= hz; z.x++) for (z.y = -hz; z.y <= hz; incr) -#define S_SQUARE_EVEN(z,hz,incr) for (z.x = -hz; z.x < hz; z.x++) for (z.y = -hz; z.y < hz; incr) - -#define T1 (T+1) -#define FOR_FRAME(r) for (r.z = 0; r.z < T1; r.z++) - -// Skip comparing the pixel-of-interest against itself, unless RF is enabled -#if RF -#define RINCR(z,c) (z.c++) -#else -#define RINCR DINCR -#endif - -#define R_AREA(a) (a * T1 + RF-1) - -// research shapes -#if R == 0 || R == 1 -#define FOR_RESEARCH(r) S_1X1(r) -const int r_area = R_AREA(1); -#elif RS == 7 -#define FOR_RESEARCH(r) S_PLUS(r,hr,RINCR(r,y)) -const int r_area = R_AREA(S_PLUS_A(hr,R)); -#elif RS == 6 -#define FOR_RESEARCH(r) S_SQUARE_EVEN(r,hr,RINCR(r,y)) -const int r_area = R_AREA(R*R); -#elif RS == 5 -#define FOR_RESEARCH(r) S_TRUNC_TRIANGLE(r,hr,RINCR(r,x)) -const int r_area = R_AREA(S_TRIANGLE_A(hr,hr)); -#elif RS == 4 -#define FOR_RESEARCH(r) S_TRIANGLE(r,hr,RINCR(r,x)) -const int r_area = R_AREA(S_TRIANGLE_A(hr,R)); -#elif RS == 3 -#define FOR_RESEARCH(r) S_DIAMOND(r,hr,RINCR(r,y)) -const int r_area = R_AREA(S_DIAMOND_A(hr,R)); -#elif RS == 2 -#define FOR_RESEARCH(r) S_VERTICAL(r,hr,RINCR(r,y)) -const int r_area = R_AREA(R); -#elif RS == 1 -#define FOR_RESEARCH(r) S_HORIZONTAL(r,hr,RINCR(r,x)) -const int r_area = R_AREA(R); -#elif RS == 0 -#define FOR_RESEARCH(r) S_SQUARE(r,hr,RINCR(r,y)) -const int r_area = R_AREA(R*R); -#endif - -#define RI1 (RI+1) -#define RFI1 (RFI+1) - -#if RI -#define FOR_ROTATION for (float ri = 0; ri < 360; ri+=360.0/RI1) -#else -#define FOR_ROTATION -#endif - -#if RFI -#define FOR_REFLECTION for (int rfi = 0; rfi < RFI1; rfi++) -#else -#define FOR_REFLECTION -#endif - -#if PD -#define PINCR DINCR -#else -#define PINCR(z,c) (z.c++) -#endif - -#define P_AREA(a) (a - PD) - -// patch shapes -#if P == 0 || P == 1 -#define FOR_PATCH(p) S_1X1(p) -const int p_area = P_AREA(1); -#elif PS == 7 -#define FOR_PATCH(p) S_PLUS(p,hp,PINCR(p,y)) -const int p_area = P_AREA(S_PLUS_A(hp,P)); -#elif PS == 6 -#define FOR_PATCH(p) S_SQUARE_EVEN(p,hp,PINCR(p,y)) -const int p_area = P_AREA(P*P); -#elif PS == 5 -#define FOR_PATCH(p) S_TRUNC_TRIANGLE(p,hp,PINCR(p,x)) -const int p_area = P_AREA(S_TRIANGLE_A(hp,hp)); -#elif PS == 4 -#define FOR_PATCH(p) S_TRIANGLE(p,hp,PINCR(p,x)) -const int p_area = P_AREA(S_TRIANGLE_A(hp,P)); -#elif PS == 3 -#define FOR_PATCH(p) S_DIAMOND(p,hp,PINCR(p,y)) -const int p_area = P_AREA(S_DIAMOND_A(hp,P)); -#elif PS == 2 -#define FOR_PATCH(p) S_VERTICAL(p,hp,PINCR(p,y)) -const int p_area = P_AREA(P); -#elif PS == 1 -#define FOR_PATCH(p) S_HORIZONTAL(p,hp,PINCR(p,x)) -const int p_area = P_AREA(P); -#elif PS == 0 -#define FOR_PATCH(p) S_SQUARE(p,hp,PINCR(p,y)) -const int p_area = P_AREA(P*P); -#endif - -const float r_scale = 1.0/r_area; -const float p_scale = 1.0/p_area; - -#define load_(off) HOOKED_tex(HOOKED_pos + HOOKED_pt * vec2(off)) - -#if RF && defined(LUMA_raw) -#define load2_(off) RF_LUMA_tex(RF_LUMA_pos + RF_LUMA_pt * vec2(off)) -#define gather_offs(off, off_arr) (RF_LUMA_mul * vec4(textureGatherOffsets(RF_LUMA_raw, RF_LUMA_pos + vec2(off) * RF_LUMA_pt, off_arr))) -#define gather(off) RF_LUMA_gather(RF_LUMA_pos + (off) * RF_LUMA_pt, 0) -#elif RF && D1W -#define load2_(off) RF_tex(RF_pos + RF_pt * vec2(off)) -#define gather_offs(off, off_arr) (RF_mul * vec4(textureGatherOffsets(RF_raw, RF_pos + vec2(off) * RF_pt, off_arr))) -#define gather(off) RF_gather(RF_pos + (off) * RF_pt, 0) -#elif RF -#define load2_(off) RF_tex(RF_pos + RF_pt * vec2(off)) -#else -#define load2_(off) HOOKED_tex(HOOKED_pos + HOOKED_pt * vec2(off)) -#define gather_offs(off, off_arr) (HOOKED_mul * vec4(textureGatherOffsets(HOOKED_raw, HOOKED_pos + vec2(off) * HOOKED_pt, off_arr))) -#define gather(off) HOOKED_gather(HOOKED_pos + (off)*HOOKED_pt, 0) -#endif - -#if T -vec4 load(vec3 off) -{ - switch (int(off.z)) { - case 0: return load_(off); - } -} -vec4 load2(vec3 off) -{ - switch (int(off.z)) { - case 0: return load2_(off); - } -} -#else -#define load(off) load_(off) -#define load2(off) load2_(off) -#endif - -vec4 poi = load(vec3(0)); // pixel-of-interest -vec4 poi2 = load2(vec3(0)); // guide pixel-of-interest - -#if RI // rotation -vec2 rot(vec2 p, float d) -{ - return vec2( - p.x * cos(radians(d)) - p.y * sin(radians(d)), - p.y * sin(radians(d)) + p.x * cos(radians(d)) - ); -} -#else -#define rot(p, d) (p) -#endif - -#if RFI // reflection -vec2 ref(vec2 p, int d) -{ - switch (d) { - case 0: return p; - case 1: return p * vec2(1, -1); - case 2: return p * vec2(-1, 1); - } -} -#else -#define ref(p, d) (p) -#endif - -vec4 patch_comparison(vec3 r, vec3 r2) -{ - vec3 p; - vec4 min_rot = vec4(p_area); - - FOR_ROTATION FOR_REFLECTION { - vec4 pdiff_sq = vec4(0); - FOR_PATCH(p) { - vec3 transformed_p = vec3(ref(rot(p.xy, ri), rfi), p.z); - vec4 diff_sq = pow(load2(p + r2) - load2((transformed_p + r) * SF), vec4(2)); -#if PST && P >= PST - float pdist = exp(-pow(length(p.xy*PSD)*PSS, 2)); - diff_sq = pow(max(diff_sq, EPSILON), vec4(pdist)); -#endif - pdiff_sq += diff_sq; - } - min_rot = min(min_rot, pdiff_sq); - } - - return min_rot * p_scale; -} - -#define NO_GATHER (PD == 0) // never textureGather if any of these conditions are false -#define REGULAR_ROTATIONS (RI == 0 || RI == 1 || RI == 3) - -#if (defined(LUMA_gather) || D1W) && ((PS == 3 || PS == 7) && P == 3) && PST == 0 && M != 1 && REGULAR_ROTATIONS && NO_GATHER -// 3x3 diamond/plus patch_comparison_gather -const ivec2 offsets[4] = { ivec2(0,-1), ivec2(-1,0), ivec2(0,1), ivec2(1,0) }; -const ivec2 offsets_sf[4] = { ivec2(0,-1) * SF, ivec2(-1,0) * SF, ivec2(0,1) * SF, ivec2(1,0) * SF }; -vec4 poi_patch = gather_offs(0, offsets); -vec4 patch_comparison_gather(vec3 r, vec3 r2) -{ - float min_rot = p_area - 1; - vec4 transformer = gather_offs(r, offsets_sf); - FOR_ROTATION { - FOR_REFLECTION { - float diff_sq = dot(pow(poi_patch - transformer, vec4(2)), vec4(1)); - min_rot = min(diff_sq, min_rot); -#if RFI - switch(rfi) { - case 0: transformer = transformer.zyxw; break; - case 1: transformer = transformer.zwxy; break; // undoes last mirror, performs another mirror - case 2: transformer = transformer.zyxw; break; // undoes last mirror - } -#endif - } -#if RI == 3 - transformer = transformer.wxyz; -#elif RI == 1 - transformer = transformer.zwxy; -#endif - } - return vec4(min_rot + pow(poi2.x - load2(r).x, 2), 0, 0, 0) * p_scale; -} -#elif (defined(LUMA_gather) || D1W) && PS == 6 && REGULAR_ROTATIONS && NO_GATHER -// tiled even square patch_comparison_gather -vec4 patch_comparison_gather(vec3 r, vec3 r2) -{ - vec2 tile; - float min_rot = p_area; - - /* gather order: - * w z - * x y - */ - FOR_ROTATION FOR_REFLECTION { - float pdiff_sq = 0; - for (tile.x = -hp; tile.x < hp; tile.x+=2) for (tile.y = -hp; tile.y < hp; tile.y+=2) { - vec4 poi_patch = gather(tile + r2.xy); - vec4 transformer = gather(ref(rot(tile + 0.5, ri), rfi) - 0.5 + r.xy); - -#if RI - for (float i = 0; i < ri; i+=90) - transformer = transformer.wxyz; // rotate 90 degrees -#endif -#if RFI // XXX output is a little off - switch(rfi) { - case 1: transformer = transformer.zyxw; break; - case 2: transformer = transformer.xwzy; break; - } -#endif - - vec4 diff_sq = pow(poi_patch - transformer, vec4(2)); -#if PST && P >= PST - vec4 pdist = vec4( - exp(-pow(length((tile+vec2(0,1))*PSD)*PSS, 2)), - exp(-pow(length((tile+vec2(1,1))*PSD)*PSS, 2)), - exp(-pow(length((tile+vec2(1,0))*PSD)*PSS, 2)), - exp(-pow(length((tile+vec2(0,0))*PSD)*PSS, 2)) - ); - diff_sq = pow(max(diff_sq, EPSILON), pdist); -#endif - pdiff_sq += dot(diff_sq, vec4(1)); - } - min_rot = min(min_rot, pdiff_sq); - } - - return vec4(min_rot, 0, 0, 0) * p_scale; -} -#else -#define patch_comparison_gather patch_comparison -#endif - -vec4 hook() -{ - vec4 total_weight = vec4(0); - vec4 sum = vec4(0); - vec4 result = vec4(0); - - vec3 r = vec3(0); - vec3 p = vec3(0); - vec3 me = vec3(0); - -#if T && ME == 1 // temporal & motion estimation - vec3 me_tmp = vec3(0); - float maxweight = 0; -#elif T && ME == 2 // temporal & motion estimation - vec3 me_sum = vec3(0); - float me_weight = 0; -#endif - -#if WD == 2 || M == 3 // weight discard, weighted median intensities - int r_index = 0; - vec4 all_weights[r_area]; - vec4 all_pixels[r_area]; -#elif WD == 1 // weight discard - vec4 no_weights = vec4(0); - vec4 discard_total_weight = vec4(0); - vec4 discard_sum = vec4(0); -#endif - -#if M == 1 // Euclidean medians - vec4 minsum = vec4(0); -#endif - - FOR_FRAME(r) { -#if T && ME == 1 // temporal & motion estimation max weight - if (r.z > 0) { - me += me_tmp; - me_tmp = vec3(0); - maxweight = 0; - } -#elif T && ME == 2 // temporal & motion estimation weighted average - if (r.z > 0) { - me += round(me_sum / me_weight); - me_sum = vec3(0); - me_weight = 0; - } -#endif - FOR_RESEARCH(r) { - // main NLM logic - const float h = S*0.013; - const float pdiff_scale = 1.0/(h*h); - vec4 pdiff_sq = (r.z == 0) ? patch_comparison_gather(r+me, vec3(0)) : patch_comparison(r+me, vec3(0)); - vec4 weight = exp(-pdiff_sq * pdiff_scale); - -#if T && ME == 1 // temporal & motion estimation max weight - me_tmp = vec3(r.xy,0) * step(maxweight, weight.x) + me_tmp * (1 - step(maxweight, weight.x)); - maxweight = max(maxweight, weight.x); -#elif T && ME == 2 // temporal & motion estimation weighted average - me_sum += vec3(r.xy,0) * weight.x; - me_weight += weight.x; -#endif - -#if D1W - weight = vec4(weight.x); -#endif - - weight *= exp(-pow(length(r*SD)*SS, 2)); // spatial kernel - -#if WD == 2 || M == 3 // weight discard, weighted median intensity - all_weights[r_index] = weight; - all_pixels[r_index] = load(r+me); - r_index++; -#elif WD == 1 // weight discard - vec4 wd_scale = 1.0/max(no_weights, 1); - vec4 keeps = step(total_weight*wd_scale * WDT*exp(-wd_scale*WDP), weight); - discard_sum += load(r+me) * weight * (1 - keeps); - discard_total_weight += weight * (1 - keeps); - no_weights += keeps; -#endif - - sum += load(r+me) * weight; - total_weight += weight; - -#if M == 1 // Euclidean median - // Based on: https://arxiv.org/abs/1207.3056 - // XXX might not work with ME - vec3 r2; - vec4 wpdist_sum = vec4(0); - FOR_FRAME(r2) FOR_RESEARCH(r2) { - vec4 pdist = (r.z + r2.z) == 0 ? patch_comparison_gather(r+me, r2+me) : patch_comparison(r+me, r2+me); - wpdist_sum += sqrt(pdist) * (1-weight); - } - - vec4 newmin = step(wpdist_sum, minsum); // wpdist_sum <= minsum - newmin *= 1 - step(wpdist_sum, vec4(0)); // && wpdist_sum > 0 - newmin += step(minsum, vec4(0)); // || minsum <= 0 - newmin = min(newmin, 1); - - minsum = (newmin * wpdist_sum) + ((1-newmin) * minsum); - result = (newmin * load(r+me)) + ((1-newmin) * result); -#endif - } // FOR_RESEARCH - } // FOR_FRAME - -#if T // temporal -#endif - - vec4 avg_weight = total_weight * r_scale; - vec4 old_avg_weight = avg_weight; - -#if WD == 2 // true average - total_weight = vec4(0); - sum = vec4(0); - vec4 no_weights = vec4(0); - - for (int i = 0; i < r_area; i++) { - vec4 keeps = step(avg_weight*WDT, all_weights[i]); - all_weights[i] *= keeps; - sum += all_pixels[i] * all_weights[i]; - total_weight += all_weights[i]; - no_weights += keeps; - } -#elif WD == 1 // moving cumulative average - total_weight -= discard_total_weight; - sum -= discard_sum; -#endif -#if WD // weight discard - avg_weight = total_weight / no_weights; -#endif - - total_weight += SW; - sum += poi * SW; - -#if M == 3 // weighted median intensity - const float hr_area = r_area/2.0; - vec4 is_median, gt, lt, gte, lte, neq; - - for (int i = 0; i < r_area; i++) { - gt = lt = vec4(0); - for (int j = 0; j < r_area; j++) { - gte = step(all_pixels[i]*all_weights[i], all_pixels[j]*all_weights[j]); - lte = step(all_pixels[j]*all_weights[j], all_pixels[i]*all_weights[i]); - neq = 1 - gte * lte; - gt += gte * neq; - lt += lte * neq; - } - is_median = step(gt, vec4(hr_area)) * step(lt, vec4(hr_area)); - result += step(result, vec4(0)) * is_median * all_pixels[i]; - } -#elif M == 2 // weight map - result = avg_weight; -#elif M == 0 // mean - result = sum / total_weight; -#endif - -#if AS == 1 // sharpen+denoise - vec4 sharpened = result + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); -#elif AS == 2 // sharpen only - vec4 sharpened = poi + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); -#endif - -#if EP // extremes preserve - float luminance = EP_texOff(0).x; - // EPSILON is needed since pow(0,0) is undefined - float ep_weight = pow(max(min(1-luminance, luminance)*2, EPSILON), (luminance < 0.5 ? DP : BP)); - result = mix(poi, result, ep_weight); -#endif - -#if AS == 1 // sharpen+denoise - result = mix(sharpened, result, sharpening_power); -#elif AS == 2 // sharpen only - result = mix(sharpened, poi, sharpening_power); -#endif - - return mix(poi, result, BF); -} - diff --git a/portable_config/shaders/nlmeans_heavy.glsl b/portable_config/shaders/nlmeans_heavy.glsl deleted file mode 100644 index aba1573c..00000000 --- a/portable_config/shaders/nlmeans_heavy.glsl +++ /dev/null @@ -1,1044 +0,0 @@ -/* vi: ft=c - * - * Based on vf_nlmeans.c from FFmpeg. - * - * Copyright (c) 2022 an3223 - * Copyright (c) 2016 Clément Bœsch - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -// Profile description: Tuned for heavy noise. - -/* The recommended usage of this shader and its variant profiles is to add them - * to input.conf and then dispatch the appropriate shader via a keybind during - * media playback. Here is an example input.conf entry: - * - * F4 no-osd change-list glsl-shaders toggle "~~/shaders/nlmeans_luma.glsl"; show-text "Non-local means (LUMA only)" - * - * These shaders can also be enabled by default in mpv.conf, for example: - * - * glsl-shaders='~~/shaders/nlmeans.glsl' - * - * Both of the examples above assume the shaders are located in a subdirectory - * named "shaders" within mpv's config directory. Refer to the mpv - * documentation for more details. - * - * This shader is highly configurable via user variables below. Although the - * default settings should offer good quality at a reasonable speed, you are - * encouraged to tweak them to your preferences. Be mindful that certain - * settings may greatly affect speed. - * - * Denoising is most useful for noisy content. If there is no perceptible - * noise, you probably won't see a positive difference. - * - * The default settings are generally tuned for low noise and high detail - * preservation. The "medium" and "heavy" profiles are tuned for higher levels - * of noise. - * - * The denoiser will not work properly if the content has been upscaled - * beforehand, whether it was done by you or someone down the line. Consider - * issuing a command to downscale in the mpv console, like so: - * - * vf toggle scale=-2:720 - * - * ...replacing 720 with whatever resolution seems appropriate. Rerun the - * command to undo the downscale. It may take some trial-and-error to find the - * proper resolution. - */ - -/* Regarding speed - * - * Speed may vary wildly for different vo and gpu-api settings. Generally - * vo=gpu-next and gpu-api=vulkan are recommended for the best speed, but this - * may be different for your system. - * - * If your GPU doesn't support textureGather, or if you are on a version of mpv - * prior to 0.35.0, then consider setting RI/RFI to 0, or try the LQ and VLQ - * profiles. - * - * textureGather is LUMA only and limited to the following configurations: - * - * - PS={3,7}:P=3:PST=0:RI={0,1,3}:RFI={0,1,2}:M!=1 - * - Default, very fast, rotations and reflections should be free - * - If this is unusually slow then try changing gpu-api and vo - * - If it's still slow, try setting RI/RFI to 0. - * - * - PS=6:RI={0,1,3}:RFI={0,1,2} - * - Currently the only scalable variant - * - Patch shape is asymmetric on two axis - * - Rotations should have very little speed impact - * - Reflections may have a significant speed impact - * - * Options which always disable textureGather: - * - RF - * - PD - */ - -// The following is shader code injected from guided_s.glsl -/* vi: ft=c - * - * Copyright (c) 2022 an3223 - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 2.1 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -//desc: "Self-guided" guided filter - -/* The radius can be adjusted with the MEANIP stage's downscaling factor. - * Higher numbers give a bigger radius. - * - * The E variable can be found in the A stage. - * - * The subsampling (fast guided filter) can be adjusted with the IP stage's - * downscaling factor. Higher numbers are faster. - */ - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (IP) -//!BIND HOOKED -//!WIDTH HOOKED.w 1.0 / -//!HEIGHT HOOKED.h 1.0 / -//!SAVE _INJ_IP - -vec4 hook() -{ - return HOOKED_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (MEANIP) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w 2.0 / -//!HEIGHT _INJ_IP.h 2.0 / -//!SAVE _INJ_MEANIP - -vec4 hook() -{ -return _INJ_IP_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (_INJ_IP_SQ) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h -//!SAVE _INJ_IP_SQ - -vec4 hook() -{ -return _INJ_IP_texOff(0) * _INJ_IP_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (CORRIP) -//!BIND _INJ_IP_SQ -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h -//!SAVE _INJ_CORRIP - -vec4 hook() -{ -return _INJ_IP_SQ_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (A) -//!BIND _INJ_MEANIP -//!BIND _INJ_CORRIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h -//!SAVE _INJ_A - -#define E 0.001 - -vec4 hook() -{ -vec4 var = _INJ_CORRIP_texOff(0) - _INJ_MEANIP_texOff(0) * _INJ_MEANIP_texOff(0); - vec4 cov = var; - return cov / (var + E); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (B) -//!BIND _INJ_A -//!BIND _INJ_MEANIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h -//!SAVE _INJ_B - -vec4 hook() -{ -return _INJ_MEANIP_texOff(0) - _INJ_A_texOff(0) * _INJ_MEANIP_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (MEANA) -//!BIND _INJ_A -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h -//!SAVE _INJ_MEANA - -vec4 hook() -{ -return _INJ_A_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (MEANB) -//!BIND _INJ_B -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h -//!SAVE _INJ_MEANB - -vec4 hook() -{ -return _INJ_B_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter -//!BIND HOOKED -//!BIND _INJ_MEANA -//!BIND _INJ_MEANB -//!SAVE RF_LUMA - -vec4 hook() -{ -return _INJ_MEANA_texOff(0) * HOOKED_texOff(0) + _INJ_MEANB_texOff(0); -} - -// End of source code injected from guided_s.glsl -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!DESC Non-local means (share) -//!BIND RF_LUMA -//!SAVE RF - -vec4 hook() -{ - return RF_LUMA_texOff(0); -} - -//!HOOK LUMA -//!HOOK CHROMA -//!HOOK RGB -//!BIND HOOKED -//!BIND RF_LUMA -//!BIND RF -//!DESC Non-local means (nlmeans_heavy.glsl) - -/* User variables - * - * It is usually preferable to denoise chroma and luma differently, so the user - * variables for luma and chroma are split. - */ - -/* S = denoising factor - * P = patch size - * R = research size - * - * The denoising factor controls the level of blur, higher is blurrier. - * - * Patch size should usually be an odd number greater than or equal to 3. - * Higher values are slower and not always better. - * - * Research size usually be an odd number greater than or equal to 3. Higher - * values are usually better, but slower and offer diminishing returns. - * - * Even-numbered patch/research sizes will sample between pixels unless PS=6. - * It's not known whether this is ever useful behavior or not. This is - * incompatible with textureGather optimizations, so enable RF when using even - * patch/research sizes. - */ -#ifdef LUMA_raw -#define S 2.25 -#define P 3 -#define R 5 -#else -#define S 1.50 -#define P 3 -#define R 5 -#endif - -/* Adaptive sharpening - * - * Uses the blur incurred by denoising plus the weight map to perform an - * unsharp mask that gets applied most strongly to edges. - * - * Sharpening will amplify noise, so the denoising factor (S) should usually be - * increased to compensate. - * - * AS: 2 for sharpening, 1 for sharpening+denoising, 0 to disable - * ASF: Sharpening factor, higher numbers make a sharper underlying image - * ASP: Weight power, higher numbers use more of the sharp image - */ -#ifdef LUMA_raw -#define AS 0 -#define ASF 1.0 -#define ASP 2.0 -#else -#define AS 0 -#define ASF 1.0 -#define ASP 2.0 -#endif - -/* Starting weight - * - * Lower numbers give less weight to the pixel-of-interest, which may help - * handle higher noise levels, ringing, and may be useful for other things too? - * - * EPSILON should be used instead of zero to avoid divide-by-zero errors. The - * avg_weight variable may be used to make SW adapt to the local noise level, - * e.g., SW=max(avg_weight, EPSILON) - */ -#ifdef LUMA_raw -#define SW EPSILON -#else -#define SW EPSILON -#endif - -/* Weight discard - * - * Discard weights that fall below a fraction of the average weight. This culls - * the most dissimilar samples from the blur, yielding a much more pleasant - * result, especially around edges. - * - * WD: - * - 2: True average. Very good quality, but slower and uses more memory. - * - 1: Moving cumulative average. Inaccurate, tends to blur directionally. - * - 0: Disable - * - * WDT: Threshold coefficient, higher numbers discard more - * WDP (WD=1): Higher numbers reduce the threshold more for small sample sizes - */ -#ifdef LUMA_raw -#define WD 2 -#define WDT 1.0 -#define WDP 6.0 -#else -#define WD 2 -#define WDT 1.0 -#define WDP 6.0 -#endif - -/* Search shape - * - * Determines the shape of patches and research zones. Different shapes have - * different speed and quality characteristics. Every shape (besides square) is - * smaller than square. - * - * PS applies applies to patches, RS applies to research zones. - * - * 0: square (symmetrical) - * 1: horizontal line (asymmetric) - * 2: vertical line (asymmetric) - * 3: diamond (symmetrical) - * 4: triangle (asymmetric, pointing upward) - * 5: truncated triangle (asymmetric on two axis, last row halved) - * 6: even sized square (asymmetric on two axis) - * 7: plus (symmetrical) - */ -#ifdef LUMA_raw -#define RS 3 -#define PS 3 -#else -#define RS 3 -#define PS 3 -#endif - -/* Rotational/reflectional invariance - * - * Number of rotations/reflections to try for each patch comparison. Slow, but - * improves feature preservation, although adding more rotations/reflections - * gives diminishing returns. The most similar rotation/reflection will be used. - * - * The angle in degrees of each rotation is 360/(RI+1), so RI=1 will do a - * single 180 degree rotation, RI=3 will do three 90 degree rotations, etc. - * - * RI: Rotational invariance - * RFI (0 to 2): Reflectional invariance - */ -#ifdef LUMA_raw -#define RI 3 -#define RFI 2 -#else -#define RI 0 -#define RFI 0 -#endif - -/* Temporal denoising - * - * Caveats: - * - Slower, each frame needs to be researched - * - Requires vo=gpu-next and nlmeans_temporal.glsl - * - Luma-only (this is a bug) - * - Buggy - * - * Gather samples across multiple frames. May cause motion blur and may - * struggle more with noise that persists across multiple frames (compression - * noise, repeating frames), but can work very well on high quality video. - * - * Motion estimation (ME) should improve quality without impacting speed. - * - * T: number of frames used - * ME: motion estimation, 0 for none, 1 for max weight, 2 for weighted avg - */ -#ifdef LUMA_raw -#define T 0 -#define ME 1 -#else -#define T 0 -#define ME 0 -#endif - -/* Spatial kernel - * - * Increasing the spatial denoising factor (SS) reduces the weight of further - * pixels. - * - * Spatial distortion instructs the spatial kernel to view that axis as - * closer/further, for instance SD=(1,1,0.5) would make the temporal axis - * appear closer and increase blur between frames. - * - * The intra-patch variants do not yet have well-understood effects. They are - * intended to make large patch sizes more useful. Likely slower. - * - * SS: spatial denoising factor - * SD: spatial distortion (X, Y, time) - * PSS: intra-patch spatial denoising factor - * PST: enables intra-patch spatial kernel if P>=PST, 0 fully disables - * PSD: intra-patch spatial distortion (X, Y) - */ -#ifdef LUMA_raw -#define SS 0.25 -#define SD vec3(1,1,1) -#define PST 0 -#define PSS 0.0 -#define PSD vec2(1,1) -#else -#define SS 0.25 -#define SD vec3(1,1,1) -#define PST 0 -#define PSS 0.0 -#define PSD vec2(1,1) -#endif - -/* Extremes preserve - * - * Reduces denoising around very bright/dark areas. The downscaling factor of - * EP (located near the top of this shader) controls the area sampled for - * luminance (higher numbers consider more area). - * - * EP: 1 to enable, 0 to disable - * DP: EP strength on dark patches, 0 to fully denoise - * BP: EP strength on bright patches, 0 to fully denoise - */ -#ifdef LUMA_raw -#define EP 0 -#define BP 0.75 -#define DP 0.25 -#else -#define EP 0 -#define BP 0.0 -#define DP 0.0 -#endif - -/* Robust filtering - * - * This setting is dependent on code generation from nlmeans_cfg, so this - * setting can only be enabled via nlmeans_cfg. - * - * Compares the pixel-of-interest against downscaled pixels. - * - * This will virtually always improve quality, but will always disable - * textureGather optimizations. - * - * The downscale factor can be modified in the WIDTH/HEIGHT directives for the - * RF texture (for CHROMA, RGB) and RF_LUMA (LUMA only) textures near the top - * of this shader, higher numbers increase blur. - * - * Any notation of RF as a positive number should be assumed to be referring to - * the downscaling factor, e.g., RF=3 means RF is set to 1 and the downscaling - * factor is set to 3. - */ -#ifdef LUMA_raw -#define RF 1 -#else -#define RF 1 -#endif - -/* Blur factor - * - * 0 to 1, only useful for alternative estimators. You're probably looking for - * "S" (denoising factor), go back to the top of the shader! - */ -#ifdef LUMA_raw -#define BF 1.0 -#else -#define BF 1.0 -#endif - -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ - -// Scaling factor (should match WIDTH/HEIGHT) -#ifdef LUMA_raw -#define SF 1 -#else -#define SF 1 -#endif - -/* Estimator - * - * 0: means - * 1: Euclidean medians (extremely slow, may be good for heavy noise) - * 2: weight map (not a denoiser, maybe useful for generating image masks) - * 3: weighted median intensity (slow, may be good for heavy noise) - */ -#ifdef LUMA_raw -#define M 0 -#else -#define M 0 -#endif - -// Patch donut (probably useless) -#ifdef LUMA_raw -#define PD 0 -#else -#define PD 0 -#endif - -// Duplicate 1st weight -#ifdef LUMA_raw -#define D1W 0 -#else -#define D1W 0 -#endif - -/* Shader code */ - -#define EPSILON 0.00000000001 - -#if PS == 6 -const int hp = P/2; -#else -const float hp = int(P/2) - 0.5*(1-(P%2)); // sample between pixels for even patch sizes -#endif - -#if RS == 6 -const int hr = R/2; -#else -const float hr = int(R/2) - 0.5*(1-(R%2)); // sample between pixels for even research sizes -#endif - -// donut increment, increments without landing on (0,0,0) -// much faster than a "continue" statement -#define DINCR(z,c) (z.c++,(z.c += int(z == vec3(0)))) - -// search shapes and their corresponding areas -#define S_1X1(z) for (z = vec3(0); z.x <= 0; z.x++) - -#define S_TRIANGLE(z,hz,incr) for (z.y = -hz; z.y <= 0; z.y++) for (z.x = -abs(abs(z.y) - hz); z.x <= abs(abs(z.y) - hz); incr) -#define S_TRUNC_TRIANGLE(z,hz,incr) for (z.y = -hz; z.y <= 0; z.y++) for (z.x = -abs(abs(z.y) - hz); z.x <= abs(abs(z.y) - hz)*int(z.y!=0); incr) -#define S_TRIANGLE_A(hz,Z) int(pow(hz, 2)+Z) - -#define S_DIAMOND(z,hz,incr) for (z.x = -hz; z.x <= hz; z.x++) for (z.y = -abs(abs(z.x) - hz); z.y <= abs(abs(z.x) - hz); incr) -#define S_DIAMOND_A(hz,Z) int(pow(hz, 2)*2+Z) - -#define S_VERTICAL(z,hz,incr) for (z.x = 0; z.x <= 0; z.x++) for (z.y = -hz; z.y <= hz; incr) -#define S_HORIZONTAL(z,hz,incr) for (z.x = -hz; z.x <= hz; incr) for (z.y = 0; z.y <= 0; z.y++) - -#define S_PLUS(z,hz,incr) for (z.x = -hz; z.x <= hz; z.x++) for (z.y = -hz * int(z.x == 0); z.y <= hz * int(z.x == 0); incr) -#define S_PLUS_A(hz,Z) (Z*2 - 1) - -#define S_SQUARE(z,hz,incr) for (z.x = -hz; z.x <= hz; z.x++) for (z.y = -hz; z.y <= hz; incr) -#define S_SQUARE_EVEN(z,hz,incr) for (z.x = -hz; z.x < hz; z.x++) for (z.y = -hz; z.y < hz; incr) - -#define T1 (T+1) -#define FOR_FRAME(r) for (r.z = 0; r.z < T1; r.z++) - -// Skip comparing the pixel-of-interest against itself, unless RF is enabled -#if RF -#define RINCR(z,c) (z.c++) -#else -#define RINCR DINCR -#endif - -#define R_AREA(a) (a * T1 + RF-1) - -// research shapes -#if R == 0 || R == 1 -#define FOR_RESEARCH(r) S_1X1(r) -const int r_area = R_AREA(1); -#elif RS == 7 -#define FOR_RESEARCH(r) S_PLUS(r,hr,RINCR(r,y)) -const int r_area = R_AREA(S_PLUS_A(hr,R)); -#elif RS == 6 -#define FOR_RESEARCH(r) S_SQUARE_EVEN(r,hr,RINCR(r,y)) -const int r_area = R_AREA(R*R); -#elif RS == 5 -#define FOR_RESEARCH(r) S_TRUNC_TRIANGLE(r,hr,RINCR(r,x)) -const int r_area = R_AREA(S_TRIANGLE_A(hr,hr)); -#elif RS == 4 -#define FOR_RESEARCH(r) S_TRIANGLE(r,hr,RINCR(r,x)) -const int r_area = R_AREA(S_TRIANGLE_A(hr,R)); -#elif RS == 3 -#define FOR_RESEARCH(r) S_DIAMOND(r,hr,RINCR(r,y)) -const int r_area = R_AREA(S_DIAMOND_A(hr,R)); -#elif RS == 2 -#define FOR_RESEARCH(r) S_VERTICAL(r,hr,RINCR(r,y)) -const int r_area = R_AREA(R); -#elif RS == 1 -#define FOR_RESEARCH(r) S_HORIZONTAL(r,hr,RINCR(r,x)) -const int r_area = R_AREA(R); -#elif RS == 0 -#define FOR_RESEARCH(r) S_SQUARE(r,hr,RINCR(r,y)) -const int r_area = R_AREA(R*R); -#endif - -#define RI1 (RI+1) -#define RFI1 (RFI+1) - -#if RI -#define FOR_ROTATION for (float ri = 0; ri < 360; ri+=360.0/RI1) -#else -#define FOR_ROTATION -#endif - -#if RFI -#define FOR_REFLECTION for (int rfi = 0; rfi < RFI1; rfi++) -#else -#define FOR_REFLECTION -#endif - -#if PD -#define PINCR DINCR -#else -#define PINCR(z,c) (z.c++) -#endif - -#define P_AREA(a) (a - PD) - -// patch shapes -#if P == 0 || P == 1 -#define FOR_PATCH(p) S_1X1(p) -const int p_area = P_AREA(1); -#elif PS == 7 -#define FOR_PATCH(p) S_PLUS(p,hp,PINCR(p,y)) -const int p_area = P_AREA(S_PLUS_A(hp,P)); -#elif PS == 6 -#define FOR_PATCH(p) S_SQUARE_EVEN(p,hp,PINCR(p,y)) -const int p_area = P_AREA(P*P); -#elif PS == 5 -#define FOR_PATCH(p) S_TRUNC_TRIANGLE(p,hp,PINCR(p,x)) -const int p_area = P_AREA(S_TRIANGLE_A(hp,hp)); -#elif PS == 4 -#define FOR_PATCH(p) S_TRIANGLE(p,hp,PINCR(p,x)) -const int p_area = P_AREA(S_TRIANGLE_A(hp,P)); -#elif PS == 3 -#define FOR_PATCH(p) S_DIAMOND(p,hp,PINCR(p,y)) -const int p_area = P_AREA(S_DIAMOND_A(hp,P)); -#elif PS == 2 -#define FOR_PATCH(p) S_VERTICAL(p,hp,PINCR(p,y)) -const int p_area = P_AREA(P); -#elif PS == 1 -#define FOR_PATCH(p) S_HORIZONTAL(p,hp,PINCR(p,x)) -const int p_area = P_AREA(P); -#elif PS == 0 -#define FOR_PATCH(p) S_SQUARE(p,hp,PINCR(p,y)) -const int p_area = P_AREA(P*P); -#endif - -const float r_scale = 1.0/r_area; -const float p_scale = 1.0/p_area; - -#define load_(off) HOOKED_tex(HOOKED_pos + HOOKED_pt * vec2(off)) - -#if RF && defined(LUMA_raw) -#define load2_(off) RF_LUMA_tex(RF_LUMA_pos + RF_LUMA_pt * vec2(off)) -#define gather_offs(off, off_arr) (RF_LUMA_mul * vec4(textureGatherOffsets(RF_LUMA_raw, RF_LUMA_pos + vec2(off) * RF_LUMA_pt, off_arr))) -#define gather(off) RF_LUMA_gather(RF_LUMA_pos + (off) * RF_LUMA_pt, 0) -#elif RF && D1W -#define load2_(off) RF_tex(RF_pos + RF_pt * vec2(off)) -#define gather_offs(off, off_arr) (RF_mul * vec4(textureGatherOffsets(RF_raw, RF_pos + vec2(off) * RF_pt, off_arr))) -#define gather(off) RF_gather(RF_pos + (off) * RF_pt, 0) -#elif RF -#define load2_(off) RF_tex(RF_pos + RF_pt * vec2(off)) -#else -#define load2_(off) HOOKED_tex(HOOKED_pos + HOOKED_pt * vec2(off)) -#define gather_offs(off, off_arr) (HOOKED_mul * vec4(textureGatherOffsets(HOOKED_raw, HOOKED_pos + vec2(off) * HOOKED_pt, off_arr))) -#define gather(off) HOOKED_gather(HOOKED_pos + (off)*HOOKED_pt, 0) -#endif - -#if T -vec4 load(vec3 off) -{ - switch (int(off.z)) { - case 0: return load_(off); - } -} -vec4 load2(vec3 off) -{ - switch (int(off.z)) { - case 0: return load2_(off); - } -} -#else -#define load(off) load_(off) -#define load2(off) load2_(off) -#endif - -vec4 poi = load(vec3(0)); // pixel-of-interest -vec4 poi2 = load2(vec3(0)); // guide pixel-of-interest - -#if RI // rotation -vec2 rot(vec2 p, float d) -{ - return vec2( - p.x * cos(radians(d)) - p.y * sin(radians(d)), - p.y * sin(radians(d)) + p.x * cos(radians(d)) - ); -} -#else -#define rot(p, d) (p) -#endif - -#if RFI // reflection -vec2 ref(vec2 p, int d) -{ - switch (d) { - case 0: return p; - case 1: return p * vec2(1, -1); - case 2: return p * vec2(-1, 1); - } -} -#else -#define ref(p, d) (p) -#endif - -vec4 patch_comparison(vec3 r, vec3 r2) -{ - vec3 p; - vec4 min_rot = vec4(p_area); - - FOR_ROTATION FOR_REFLECTION { - vec4 pdiff_sq = vec4(0); - FOR_PATCH(p) { - vec3 transformed_p = vec3(ref(rot(p.xy, ri), rfi), p.z); - vec4 diff_sq = pow(load2(p + r2) - load2((transformed_p + r) * SF), vec4(2)); -#if PST && P >= PST - float pdist = exp(-pow(length(p.xy*PSD)*PSS, 2)); - diff_sq = pow(max(diff_sq, EPSILON), vec4(pdist)); -#endif - pdiff_sq += diff_sq; - } - min_rot = min(min_rot, pdiff_sq); - } - - return min_rot * p_scale; -} - -#define NO_GATHER (PD == 0) // never textureGather if any of these conditions are false -#define REGULAR_ROTATIONS (RI == 0 || RI == 1 || RI == 3) - -#if (defined(LUMA_gather) || D1W) && ((PS == 3 || PS == 7) && P == 3) && PST == 0 && M != 1 && REGULAR_ROTATIONS && NO_GATHER -// 3x3 diamond/plus patch_comparison_gather -const ivec2 offsets[4] = { ivec2(0,-1), ivec2(-1,0), ivec2(0,1), ivec2(1,0) }; -const ivec2 offsets_sf[4] = { ivec2(0,-1) * SF, ivec2(-1,0) * SF, ivec2(0,1) * SF, ivec2(1,0) * SF }; -vec4 poi_patch = gather_offs(0, offsets); -vec4 patch_comparison_gather(vec3 r, vec3 r2) -{ - float min_rot = p_area - 1; - vec4 transformer = gather_offs(r, offsets_sf); - FOR_ROTATION { - FOR_REFLECTION { - float diff_sq = dot(pow(poi_patch - transformer, vec4(2)), vec4(1)); - min_rot = min(diff_sq, min_rot); -#if RFI - switch(rfi) { - case 0: transformer = transformer.zyxw; break; - case 1: transformer = transformer.zwxy; break; // undoes last mirror, performs another mirror - case 2: transformer = transformer.zyxw; break; // undoes last mirror - } -#endif - } -#if RI == 3 - transformer = transformer.wxyz; -#elif RI == 1 - transformer = transformer.zwxy; -#endif - } - return vec4(min_rot + pow(poi2.x - load2(r).x, 2), 0, 0, 0) * p_scale; -} -#elif (defined(LUMA_gather) || D1W) && PS == 6 && REGULAR_ROTATIONS && NO_GATHER -// tiled even square patch_comparison_gather -vec4 patch_comparison_gather(vec3 r, vec3 r2) -{ - vec2 tile; - float min_rot = p_area; - - /* gather order: - * w z - * x y - */ - FOR_ROTATION FOR_REFLECTION { - float pdiff_sq = 0; - for (tile.x = -hp; tile.x < hp; tile.x+=2) for (tile.y = -hp; tile.y < hp; tile.y+=2) { - vec4 poi_patch = gather(tile + r2.xy); - vec4 transformer = gather(ref(rot(tile + 0.5, ri), rfi) - 0.5 + r.xy); - -#if RI - for (float i = 0; i < ri; i+=90) - transformer = transformer.wxyz; // rotate 90 degrees -#endif -#if RFI // XXX output is a little off - switch(rfi) { - case 1: transformer = transformer.zyxw; break; - case 2: transformer = transformer.xwzy; break; - } -#endif - - vec4 diff_sq = pow(poi_patch - transformer, vec4(2)); -#if PST && P >= PST - vec4 pdist = vec4( - exp(-pow(length((tile+vec2(0,1))*PSD)*PSS, 2)), - exp(-pow(length((tile+vec2(1,1))*PSD)*PSS, 2)), - exp(-pow(length((tile+vec2(1,0))*PSD)*PSS, 2)), - exp(-pow(length((tile+vec2(0,0))*PSD)*PSS, 2)) - ); - diff_sq = pow(max(diff_sq, EPSILON), pdist); -#endif - pdiff_sq += dot(diff_sq, vec4(1)); - } - min_rot = min(min_rot, pdiff_sq); - } - - return vec4(min_rot, 0, 0, 0) * p_scale; -} -#else -#define patch_comparison_gather patch_comparison -#endif - -vec4 hook() -{ - vec4 total_weight = vec4(0); - vec4 sum = vec4(0); - vec4 result = vec4(0); - - vec3 r = vec3(0); - vec3 p = vec3(0); - vec3 me = vec3(0); - -#if T && ME == 1 // temporal & motion estimation - vec3 me_tmp = vec3(0); - float maxweight = 0; -#elif T && ME == 2 // temporal & motion estimation - vec3 me_sum = vec3(0); - float me_weight = 0; -#endif - -#if WD == 2 || M == 3 // weight discard, weighted median intensities - int r_index = 0; - vec4 all_weights[r_area]; - vec4 all_pixels[r_area]; -#elif WD == 1 // weight discard - vec4 no_weights = vec4(0); - vec4 discard_total_weight = vec4(0); - vec4 discard_sum = vec4(0); -#endif - -#if M == 1 // Euclidean medians - vec4 minsum = vec4(0); -#endif - - FOR_FRAME(r) { -#if T && ME == 1 // temporal & motion estimation max weight - if (r.z > 0) { - me += me_tmp; - me_tmp = vec3(0); - maxweight = 0; - } -#elif T && ME == 2 // temporal & motion estimation weighted average - if (r.z > 0) { - me += round(me_sum / me_weight); - me_sum = vec3(0); - me_weight = 0; - } -#endif - FOR_RESEARCH(r) { - // main NLM logic - const float h = S*0.013; - const float pdiff_scale = 1.0/(h*h); - vec4 pdiff_sq = (r.z == 0) ? patch_comparison_gather(r+me, vec3(0)) : patch_comparison(r+me, vec3(0)); - vec4 weight = exp(-pdiff_sq * pdiff_scale); - -#if T && ME == 1 // temporal & motion estimation max weight - me_tmp = vec3(r.xy,0) * step(maxweight, weight.x) + me_tmp * (1 - step(maxweight, weight.x)); - maxweight = max(maxweight, weight.x); -#elif T && ME == 2 // temporal & motion estimation weighted average - me_sum += vec3(r.xy,0) * weight.x; - me_weight += weight.x; -#endif - -#if D1W - weight = vec4(weight.x); -#endif - - weight *= exp(-pow(length(r*SD)*SS, 2)); // spatial kernel - -#if WD == 2 || M == 3 // weight discard, weighted median intensity - all_weights[r_index] = weight; - all_pixels[r_index] = load(r+me); - r_index++; -#elif WD == 1 // weight discard - vec4 wd_scale = 1.0/max(no_weights, 1); - vec4 keeps = step(total_weight*wd_scale * WDT*exp(-wd_scale*WDP), weight); - discard_sum += load(r+me) * weight * (1 - keeps); - discard_total_weight += weight * (1 - keeps); - no_weights += keeps; -#endif - - sum += load(r+me) * weight; - total_weight += weight; - -#if M == 1 // Euclidean median - // Based on: https://arxiv.org/abs/1207.3056 - // XXX might not work with ME - vec3 r2; - vec4 wpdist_sum = vec4(0); - FOR_FRAME(r2) FOR_RESEARCH(r2) { - vec4 pdist = (r.z + r2.z) == 0 ? patch_comparison_gather(r+me, r2+me) : patch_comparison(r+me, r2+me); - wpdist_sum += sqrt(pdist) * (1-weight); - } - - vec4 newmin = step(wpdist_sum, minsum); // wpdist_sum <= minsum - newmin *= 1 - step(wpdist_sum, vec4(0)); // && wpdist_sum > 0 - newmin += step(minsum, vec4(0)); // || minsum <= 0 - newmin = min(newmin, 1); - - minsum = (newmin * wpdist_sum) + ((1-newmin) * minsum); - result = (newmin * load(r+me)) + ((1-newmin) * result); -#endif - } // FOR_RESEARCH - } // FOR_FRAME - -#if T // temporal -#endif - - vec4 avg_weight = total_weight * r_scale; - vec4 old_avg_weight = avg_weight; - -#if WD == 2 // true average - total_weight = vec4(0); - sum = vec4(0); - vec4 no_weights = vec4(0); - - for (int i = 0; i < r_area; i++) { - vec4 keeps = step(avg_weight*WDT, all_weights[i]); - all_weights[i] *= keeps; - sum += all_pixels[i] * all_weights[i]; - total_weight += all_weights[i]; - no_weights += keeps; - } -#elif WD == 1 // moving cumulative average - total_weight -= discard_total_weight; - sum -= discard_sum; -#endif -#if WD // weight discard - avg_weight = total_weight / no_weights; -#endif - - total_weight += SW; - sum += poi * SW; - -#if M == 3 // weighted median intensity - const float hr_area = r_area/2.0; - vec4 is_median, gt, lt, gte, lte, neq; - - for (int i = 0; i < r_area; i++) { - gt = lt = vec4(0); - for (int j = 0; j < r_area; j++) { - gte = step(all_pixels[i]*all_weights[i], all_pixels[j]*all_weights[j]); - lte = step(all_pixels[j]*all_weights[j], all_pixels[i]*all_weights[i]); - neq = 1 - gte * lte; - gt += gte * neq; - lt += lte * neq; - } - is_median = step(gt, vec4(hr_area)) * step(lt, vec4(hr_area)); - result += step(result, vec4(0)) * is_median * all_pixels[i]; - } -#elif M == 2 // weight map - result = avg_weight; -#elif M == 0 // mean - result = sum / total_weight; -#endif - -#if AS == 1 // sharpen+denoise - vec4 sharpened = result + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); -#elif AS == 2 // sharpen only - vec4 sharpened = poi + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); -#endif - -#if EP // extremes preserve - float luminance = EP_texOff(0).x; - // EPSILON is needed since pow(0,0) is undefined - float ep_weight = pow(max(min(1-luminance, luminance)*2, EPSILON), (luminance < 0.5 ? DP : BP)); - result = mix(poi, result, ep_weight); -#endif - -#if AS == 1 // sharpen+denoise - result = mix(sharpened, result, sharpening_power); -#elif AS == 2 // sharpen only - result = mix(sharpened, poi, sharpening_power); -#endif - - return mix(poi, result, BF); -} - diff --git a/portable_config/shaders/nlmeans_hq.glsl b/portable_config/shaders/nlmeans_hq.glsl index 16f13d51..f1d6b4da 100644 --- a/portable_config/shaders/nlmeans_hq.glsl +++ b/portable_config/shaders/nlmeans_hq.glsl @@ -82,11 +82,10 @@ * - Reflections may have a significant speed impact * * Options which always disable textureGather: - * - RF * - PD */ -// The following is shader code injected from guided_s.glsl +// The following is shader code injected from guided.glsl /* vi: ft=c * * Copyright (c) 2022 an3223 @@ -105,25 +104,53 @@ * along with this program. If not, see . */ -//desc: "Self-guided" guided filter +//desc: Guided filter guided by the downscaled image -/* The radius can be adjusted with the MEANIP stage's downscaling factor. +/* The radius can be adjusted with the MEANI stage's downscaling factor. * Higher numbers give a bigger radius. * * The E variable can be found in the A stage. * - * The subsampling (fast guided filter) can be adjusted with the IP stage's + * The subsampling (fast guided filter) can be adjusted with the I stage's * downscaling factor. Higher numbers are faster. + * + * The guide's subsampling can be adjusted with the PREI stage's downscaling + * factor. Higher numbers downscale more. */ //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (IP) +//!DESC Guided filter (PREI) //!BIND HOOKED +//!WIDTH HOOKED.w 1.25 / +//!HEIGHT HOOKED.h 1.25 / +//!SAVE _INJ_PREI + +vec4 hook() +{ + return HOOKED_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (I) +//!BIND _INJ_PREI //!WIDTH HOOKED.w 1.0 / //!HEIGHT HOOKED.h 1.0 / -//!SAVE _INJ_IP +//!SAVE _INJ_I + +vec4 hook() +{ +return _INJ_PREI_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (P) +//!BIND HOOKED +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_P vec4 hook() { @@ -132,87 +159,124 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (MEANIP) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w 2.0 / -//!HEIGHT _INJ_IP.h 2.0 / -//!SAVE _INJ_MEANIP +//!DESC Guided filter (MEANI) +//!BIND _INJ_I +//!WIDTH _INJ_I.w 1.5 / +//!HEIGHT _INJ_I.h 1.5 / +//!SAVE _INJ_MEANI vec4 hook() { -return _INJ_IP_texOff(0); +return _INJ_I_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (_INJ_IP_SQ) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h -//!SAVE _INJ_IP_SQ +//!DESC Guided filter (MEANP) +//!BIND _INJ_P +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_MEANP vec4 hook() { -return _INJ_IP_texOff(0) * _INJ_IP_texOff(0); +return _INJ_P_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (CORRIP) -//!BIND _INJ_IP_SQ -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h -//!SAVE _INJ_CORRIP +//!DESC Guided filter (_INJ_I_SQ) +//!BIND _INJ_I +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_I_SQ vec4 hook() { -return _INJ_IP_SQ_texOff(0); +return _INJ_I_texOff(0) * _INJ_I_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (_INJ_IXP) +//!BIND _INJ_I +//!BIND _INJ_P +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_IXP + +vec4 hook() +{ +return _INJ_I_texOff(0) * _INJ_P_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (CORRI) +//!BIND _INJ_I_SQ +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_CORRI + +vec4 hook() +{ +return _INJ_I_SQ_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (CORRP) +//!BIND _INJ_IXP +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_CORRP + +vec4 hook() +{ +return _INJ_IXP_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (A) -//!BIND _INJ_MEANIP -//!BIND _INJ_CORRIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h +//!BIND _INJ_MEANI +//!BIND _INJ_MEANP +//!BIND _INJ_CORRI +//!BIND _INJ_CORRP +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h //!SAVE _INJ_A -#define E 0.001 +#define E 0.0013 vec4 hook() { -vec4 var = _INJ_CORRIP_texOff(0) - _INJ_MEANIP_texOff(0) * _INJ_MEANIP_texOff(0); - vec4 cov = var; +vec4 var = _INJ_CORRI_texOff(0) - _INJ_MEANI_texOff(0) * _INJ_MEANI_texOff(0); +vec4 cov = _INJ_CORRP_texOff(0) - _INJ_MEANI_texOff(0) * _INJ_MEANP_texOff(0); return cov / (var + E); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (B) //!BIND _INJ_A -//!BIND _INJ_MEANIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h +//!BIND _INJ_MEANI +//!BIND _INJ_MEANP +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h //!SAVE _INJ_B vec4 hook() { -return _INJ_MEANIP_texOff(0) - _INJ_A_texOff(0) * _INJ_MEANIP_texOff(0); +return _INJ_MEANP_texOff(0) - _INJ_A_texOff(0) * _INJ_MEANI_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (MEANA) //!BIND _INJ_A -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h //!SAVE _INJ_MEANA vec4 hook() @@ -222,11 +286,10 @@ return _INJ_A_texOff(0); //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (MEANB) //!BIND _INJ_B -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h //!SAVE _INJ_MEANB vec4 hook() @@ -236,7 +299,6 @@ return _INJ_B_texOff(0); //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter //!BIND HOOKED //!BIND _INJ_MEANA @@ -248,10 +310,9 @@ vec4 hook() return _INJ_MEANA_texOff(0) * HOOKED_texOff(0) + _INJ_MEANB_texOff(0); } -// End of source code injected from guided_s.glsl +// End of source code injected from guided.glsl //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Non-local means (share) //!BIND RF_LUMA //!SAVE RF @@ -263,7 +324,6 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!BIND HOOKED //!BIND RF_LUMA //!BIND RF @@ -289,8 +349,7 @@ vec4 hook() * * Even-numbered patch/research sizes will sample between pixels unless PS=6. * It's not known whether this is ever useful behavior or not. This is - * incompatible with textureGather optimizations, so enable RF when using even - * patch/research sizes. + * incompatible with textureGather optimizations, so NG=1 to disable them. */ #ifdef LUMA_raw #define S 3 @@ -304,24 +363,37 @@ vec4 hook() /* Adaptive sharpening * - * Uses the blur incurred by denoising plus the weight map to perform an - * unsharp mask that gets applied most strongly to edges. + * Uses the blur incurred by denoising to perform an unsharp mask, and uses the + * weight map to restrict the sharpening to edges. * - * Sharpening will amplify noise, so the denoising factor (S) should usually be - * increased to compensate. + * Use M=4 to get a good look at which areas are/aren't sharpened. * * AS: 2 for sharpening, 1 for sharpening+denoising, 0 to disable * ASF: Sharpening factor, higher numbers make a sharper underlying image * ASP: Weight power, higher numbers use more of the sharp image + * ASW: + * - 0 to use pre-WD weights + * - 1 to use post-WD weights (ASP should be ~2x to compensate) + * ASK: Weight kernel: + * - 0 for power. This is the old method. + * - 1 for sigmoid. This is generally recommended. + * - 2 for constant (non-adaptive, w/ ASP=0 this sharpens the entire image) + * ASC (only for ASK=1, range 0-1): Reduces the contrast of the edge map */ #ifdef LUMA_raw #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #else #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #endif /* Starting weight @@ -330,8 +402,8 @@ vec4 hook() * handle higher noise levels, ringing, and may be useful for other things too? * * EPSILON should be used instead of zero to avoid divide-by-zero errors. The - * avg_weight variable may be used to make SW adapt to the local noise level, - * e.g., SW=max(avg_weight, EPSILON) + * avg_weight/old_avg_weight variables may be used to make SW adapt to the + * local noise level, e.g., SW=max(avg_weight, EPSILON) */ #ifdef LUMA_raw #define SW 1.0 @@ -351,7 +423,7 @@ vec4 hook() * - 0: Disable * * WDT: Threshold coefficient, higher numbers discard more - * WDP (WD=1): Higher numbers reduce the threshold more for small sample sizes + * WDP (only for WD=1): Increasing reduces the threshold for small sample sizes */ #ifdef LUMA_raw #define WD 2 @@ -363,6 +435,49 @@ vec4 hook() #define WDP 6.0 #endif +/* Extremes preserve + * + * Reduces denoising around very bright/dark areas. The downscaling factor of + * EP (located near the top of this shader) controls the area sampled for + * luminance (higher numbers consider more area). + * + * This is incompatible with RGB. If you have RGB hooks enabled then you will + * have to delete the EP shader stage or specify EP=0 through nlmeans_cfg. + * + * EP: 1 to enable, 0 to disable + * DP: EP strength on dark patches, 0 to fully denoise + * BP: EP strength on bright patches, 0 to fully denoise + */ +#ifdef LUMA_raw +#define EP 0 +#define BP 0.75 +#define DP 0.25 +#else +#define EP 0 +#define BP 0.0 +#define DP 0.0 +#endif + +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ + +/* Robust filtering + * + * This setting is dependent on code generation from nlmeans_cfg, so this + * setting can only be enabled via nlmeans_cfg. + * + * Compares the pixel-of-interest against a guide, which could be a downscaled + * image or the output of another shader such as guided.glsl + */ +#ifdef LUMA_raw +#define RF 1 +#else +#define RF 1 +#endif + /* Search shape * * Determines the shape of patches and research zones. Different shapes have @@ -371,6 +486,8 @@ vec4 hook() * * PS applies applies to patches, RS applies to research zones. * + * Be wary of gather optimizations (see the Regarding Speed comment at the top) + * * 0: square (symmetrical) * 1: horizontal line (asymmetric) * 2: vertical line (asymmetric) @@ -417,8 +534,9 @@ vec4 hook() * - Buggy * * Gather samples across multiple frames. May cause motion blur and may - * struggle more with noise that persists across multiple frames (compression - * noise, repeating frames), but can work very well on high quality video. + * struggle more with noise that persists across multiple frames (e.g., from + * compression or duplicate frames), but can work very well on high quality + * video. * * Motion estimation (ME) should improve quality without impacting speed. * @@ -453,60 +571,51 @@ vec4 hook() */ #ifdef LUMA_raw #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #else #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #endif -/* Extremes preserve - * - * Reduces denoising around very bright/dark areas. The downscaling factor of - * EP (located near the top of this shader) controls the area sampled for - * luminance (higher numbers consider more area). +// Scaling factor (should match WIDTH/HEIGHT) +#ifdef LUMA_raw +#define SF 1 +#else +#define SF 1 +#endif + +/* Estimator * - * EP: 1 to enable, 0 to disable - * DP: EP strength on dark patches, 0 to fully denoise - * BP: EP strength on bright patches, 0 to fully denoise + * 0: means + * 1: Euclidean medians (extremely slow, may be good for heavy noise) + * 2: weight map (not a denoiser, maybe useful for generating image masks) + * 3: weighted median intensity (slow, may be good for heavy noise) + * 4: edge map (based on the relevant AS settings) */ #ifdef LUMA_raw -#define EP 0 -#define BP 0.75 -#define DP 0.25 +#define M 0 #else -#define EP 0 -#define BP 0.0 -#define DP 0.0 +#define M 0 #endif -/* Robust filtering - * - * This setting is dependent on code generation from nlmeans_cfg, so this - * setting can only be enabled via nlmeans_cfg. +/* Difference visualization * - * Compares the pixel-of-interest against downscaled pixels. + * Visualizes the difference between input/output image * - * This will virtually always improve quality, but will always disable - * textureGather optimizations. - * - * The downscale factor can be modified in the WIDTH/HEIGHT directives for the - * RF texture (for CHROMA, RGB) and RF_LUMA (LUMA only) textures near the top - * of this shader, higher numbers increase blur. - * - * Any notation of RF as a positive number should be assumed to be referring to - * the downscaling factor, e.g., RF=3 means RF is set to 1 and the downscaling - * factor is set to 3. + * 0: off + * 1: absolute difference scaled by S + * 2: difference centered on 0.5 */ #ifdef LUMA_raw -#define RF 1 +#define DV 0 #else -#define RF 1 +#define DV 0 #endif /* Blur factor @@ -520,30 +629,11 @@ vec4 hook() #define BF 1.0 #endif -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ - -// Scaling factor (should match WIDTH/HEIGHT) -#ifdef LUMA_raw -#define SF 1 -#else -#define SF 1 -#endif - -/* Estimator - * - * 0: means - * 1: Euclidean medians (extremely slow, may be good for heavy noise) - * 2: weight map (not a denoiser, maybe useful for generating image masks) - * 3: weighted median intensity (slow, may be good for heavy noise) - */ +// Force disable textureGather #ifdef LUMA_raw -#define M 0 +#define NG 0 #else -#define M 0 +#define NG 0 #endif // Patch donut (probably useless) @@ -553,7 +643,7 @@ vec4 hook() #define PD 0 #endif -// Duplicate 1st weight +// Duplicate 1st weight (for LGC) #ifdef LUMA_raw #define D1W 0 #else @@ -563,6 +653,7 @@ vec4 hook() /* Shader code */ #define EPSILON 0.00000000001 +#define M_PI 3.14159265358979323846 #if PS == 6 const int hp = P/2; @@ -612,6 +703,7 @@ const float hr = int(R/2) - 0.5*(1-(R%2)); // sample between pixels for even res #define R_AREA(a) (a * T1 + RF-1) // research shapes +// XXX would be nice to have the option of temporally-varying research sizes #if R == 0 || R == 1 #define FOR_RESEARCH(r) S_1X1(r) const int r_area = R_AREA(1); @@ -783,11 +875,13 @@ vec4 patch_comparison(vec3 r, vec3 r2) return min_rot * p_scale; } -#define NO_GATHER (PD == 0) // never textureGather if any of these conditions are false +#define NO_GATHER (PD == 0 && NG == 0) // never textureGather if any of these conditions are false #define REGULAR_ROTATIONS (RI == 0 || RI == 1 || RI == 3) #if (defined(LUMA_gather) || D1W) && ((PS == 3 || PS == 7) && P == 3) && PST == 0 && M != 1 && REGULAR_ROTATIONS && NO_GATHER // 3x3 diamond/plus patch_comparison_gather +// XXX extend to support arbitrary sizes (probably requires code generation) +// XXX extend to support 3x3 square const ivec2 offsets[4] = { ivec2(0,-1), ivec2(-1,0), ivec2(0,1), ivec2(1,0) }; const ivec2 offsets_sf[4] = { ivec2(0,-1) * SF, ivec2(-1,0) * SF, ivec2(0,1) * SF, ivec2(1,0) * SF }; vec4 poi_patch = gather_offs(0, offsets); @@ -817,6 +911,7 @@ vec4 patch_comparison_gather(vec3 r, vec3 r2) } #elif (defined(LUMA_gather) || D1W) && PS == 6 && REGULAR_ROTATIONS && NO_GATHER // tiled even square patch_comparison_gather +// XXX extend to support odd square? vec4 patch_comparison_gather(vec3 r, vec3 r2) { vec2 tile; @@ -897,6 +992,7 @@ vec4 hook() #endif FOR_FRAME(r) { + // XXX ME is always a frame behind, should have to option to re-research after applying ME (could do it an arbitrary number of times per frame if desired) #if T && ME == 1 // temporal & motion estimation max weight if (r.z > 0) { me += me_tmp; @@ -967,6 +1063,7 @@ vec4 hook() } // FOR_RESEARCH } // FOR_FRAME + // XXX optionally put the denoised pixel into the frame buffer? #if T // temporal #endif @@ -1018,12 +1115,29 @@ vec4 hook() result = sum / total_weight; #endif +#if ASW == 0 // pre-WD weights +#define AS_weight old_avg_weight +#elif ASW == 1 // post-WD weights +#define AS_weight avg_weight +#endif + +#if ASK == 0 + vec4 sharpening_strength = pow(AS_weight, vec4(ASP)); +#elif ASK == 1 +#define sigmoid(x) (tanh(x * 2*M_PI - M_PI)*0.5+0.5) + vec4 sharpening_strength = mix(pow(sigmoid(AS_weight), vec4(ASP)), + AS_weight, ASC); + // just in case ASC < 0 (will sharpen but it's janky XXX) + sharpening_strength = clamp(sharpening_strength, 0.0, 1.0); +#elif ASK == 2 + vec4 sharpening_strength = vec4(ASP); +#endif + + // XXX maybe allow for alternative blurs? e.g., replace result w/ load2? #if AS == 1 // sharpen+denoise vec4 sharpened = result + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #elif AS == 2 // sharpen only vec4 sharpened = poi + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #endif #if EP // extremes preserve @@ -1034,9 +1148,23 @@ vec4 hook() #endif #if AS == 1 // sharpen+denoise - result = mix(sharpened, result, sharpening_power); + result = mix(sharpened, result, sharpening_strength); #elif AS == 2 // sharpen only - result = mix(sharpened, poi, sharpening_power); + result = mix(sharpened, poi, sharpening_strength); +#endif + +#if M == 4 // edge map + result = sharpening_strength; +#endif + +#if (M == 2 || M == 4) && defined(CHROMA_raw) // drop chroma for weight maps + result = vec4(0.5); +#endif + +#if DV == 1 + result = clamp(abs(poi - result) * S, 0.0, 1.0); +#elif DV == 2 + result = (poi - result) * 0.5 + 0.5; #endif return mix(poi, result, BF); diff --git a/portable_config/shaders/nlmeans_lgc.glsl b/portable_config/shaders/nlmeans_lgc.glsl index 91da530b..7d18a350 100644 --- a/portable_config/shaders/nlmeans_lgc.glsl +++ b/portable_config/shaders/nlmeans_lgc.glsl @@ -82,7 +82,6 @@ * - Reflections may have a significant speed impact * * Options which always disable textureGather: - * - RF * - PD */ @@ -136,11 +135,10 @@ vec4 hook() * * Even-numbered patch/research sizes will sample between pixels unless PS=6. * It's not known whether this is ever useful behavior or not. This is - * incompatible with textureGather optimizations, so enable RF when using even - * patch/research sizes. + * incompatible with textureGather optimizations, so NG=1 to disable them. */ #ifdef LUMA_raw -#define S 2.25 +#define S 20.0 #define P 3 #define R 5 #else @@ -151,24 +149,37 @@ vec4 hook() /* Adaptive sharpening * - * Uses the blur incurred by denoising plus the weight map to perform an - * unsharp mask that gets applied most strongly to edges. + * Uses the blur incurred by denoising to perform an unsharp mask, and uses the + * weight map to restrict the sharpening to edges. * - * Sharpening will amplify noise, so the denoising factor (S) should usually be - * increased to compensate. + * Use M=4 to get a good look at which areas are/aren't sharpened. * * AS: 2 for sharpening, 1 for sharpening+denoising, 0 to disable * ASF: Sharpening factor, higher numbers make a sharper underlying image * ASP: Weight power, higher numbers use more of the sharp image + * ASW: + * - 0 to use pre-WD weights + * - 1 to use post-WD weights (ASP should be ~2x to compensate) + * ASK: Weight kernel: + * - 0 for power. This is the old method. + * - 1 for sigmoid. This is generally recommended. + * - 2 for constant (non-adaptive, w/ ASP=0 this sharpens the entire image) + * ASC (only for ASK=1, range 0-1): Reduces the contrast of the edge map */ #ifdef LUMA_raw #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #else #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #endif /* Starting weight @@ -177,8 +188,8 @@ vec4 hook() * handle higher noise levels, ringing, and may be useful for other things too? * * EPSILON should be used instead of zero to avoid divide-by-zero errors. The - * avg_weight variable may be used to make SW adapt to the local noise level, - * e.g., SW=max(avg_weight, EPSILON) + * avg_weight/old_avg_weight variables may be used to make SW adapt to the + * local noise level, e.g., SW=max(avg_weight, EPSILON) */ #ifdef LUMA_raw #define SW 1.0 @@ -198,7 +209,7 @@ vec4 hook() * - 0: Disable * * WDT: Threshold coefficient, higher numbers discard more - * WDP (WD=1): Higher numbers reduce the threshold more for small sample sizes + * WDP (only for WD=1): Increasing reduces the threshold for small sample sizes */ #ifdef LUMA_raw #define WD 2 @@ -210,6 +221,49 @@ vec4 hook() #define WDP 6.0 #endif +/* Extremes preserve + * + * Reduces denoising around very bright/dark areas. The downscaling factor of + * EP (located near the top of this shader) controls the area sampled for + * luminance (higher numbers consider more area). + * + * This is incompatible with RGB. If you have RGB hooks enabled then you will + * have to delete the EP shader stage or specify EP=0 through nlmeans_cfg. + * + * EP: 1 to enable, 0 to disable + * DP: EP strength on dark patches, 0 to fully denoise + * BP: EP strength on bright patches, 0 to fully denoise + */ +#ifdef LUMA_raw +#define EP 1 +#define BP 0.75 +#define DP 0.25 +#else +#define EP 0 +#define BP 0.0 +#define DP 0.0 +#endif + +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ + +/* Robust filtering + * + * This setting is dependent on code generation from nlmeans_cfg, so this + * setting can only be enabled via nlmeans_cfg. + * + * Compares the pixel-of-interest against a guide, which could be a downscaled + * image or the output of another shader such as guided.glsl + */ +#ifdef LUMA_raw +#define RF 0 +#else +#define RF 1 +#endif + /* Search shape * * Determines the shape of patches and research zones. Different shapes have @@ -218,6 +272,8 @@ vec4 hook() * * PS applies applies to patches, RS applies to research zones. * + * Be wary of gather optimizations (see the Regarding Speed comment at the top) + * * 0: square (symmetrical) * 1: horizontal line (asymmetric) * 2: vertical line (asymmetric) @@ -264,8 +320,9 @@ vec4 hook() * - Buggy * * Gather samples across multiple frames. May cause motion blur and may - * struggle more with noise that persists across multiple frames (compression - * noise, repeating frames), but can work very well on high quality video. + * struggle more with noise that persists across multiple frames (e.g., from + * compression or duplicate frames), but can work very well on high quality + * video. * * Motion estimation (ME) should improve quality without impacting speed. * @@ -300,60 +357,51 @@ vec4 hook() */ #ifdef LUMA_raw #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #else #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #endif -/* Extremes preserve - * - * Reduces denoising around very bright/dark areas. The downscaling factor of - * EP (located near the top of this shader) controls the area sampled for - * luminance (higher numbers consider more area). +// Scaling factor (should match WIDTH/HEIGHT) +#ifdef LUMA_raw +#define SF 1 +#else +#define SF 1 +#endif + +/* Estimator * - * EP: 1 to enable, 0 to disable - * DP: EP strength on dark patches, 0 to fully denoise - * BP: EP strength on bright patches, 0 to fully denoise + * 0: means + * 1: Euclidean medians (extremely slow, may be good for heavy noise) + * 2: weight map (not a denoiser, maybe useful for generating image masks) + * 3: weighted median intensity (slow, may be good for heavy noise) + * 4: edge map (based on the relevant AS settings) */ #ifdef LUMA_raw -#define EP 1 -#define BP 0.75 -#define DP 0.25 +#define M 0 #else -#define EP 0 -#define BP 0.0 -#define DP 0.0 +#define M 0 #endif -/* Robust filtering - * - * This setting is dependent on code generation from nlmeans_cfg, so this - * setting can only be enabled via nlmeans_cfg. +/* Difference visualization * - * Compares the pixel-of-interest against downscaled pixels. + * Visualizes the difference between input/output image * - * This will virtually always improve quality, but will always disable - * textureGather optimizations. - * - * The downscale factor can be modified in the WIDTH/HEIGHT directives for the - * RF texture (for CHROMA, RGB) and RF_LUMA (LUMA only) textures near the top - * of this shader, higher numbers increase blur. - * - * Any notation of RF as a positive number should be assumed to be referring to - * the downscaling factor, e.g., RF=3 means RF is set to 1 and the downscaling - * factor is set to 3. + * 0: off + * 1: absolute difference scaled by S + * 2: difference centered on 0.5 */ #ifdef LUMA_raw -#define RF 0 +#define DV 0 #else -#define RF 1 +#define DV 0 #endif /* Blur factor @@ -367,30 +415,11 @@ vec4 hook() #define BF 1.0 #endif -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ - -// Scaling factor (should match WIDTH/HEIGHT) -#ifdef LUMA_raw -#define SF 1 -#else -#define SF 1 -#endif - -/* Estimator - * - * 0: means - * 1: Euclidean medians (extremely slow, may be good for heavy noise) - * 2: weight map (not a denoiser, maybe useful for generating image masks) - * 3: weighted median intensity (slow, may be good for heavy noise) - */ +// Force disable textureGather #ifdef LUMA_raw -#define M 0 +#define NG 0 #else -#define M 0 +#define NG 0 #endif // Patch donut (probably useless) @@ -400,7 +429,7 @@ vec4 hook() #define PD 0 #endif -// Duplicate 1st weight +// Duplicate 1st weight (for LGC) #ifdef LUMA_raw #define D1W 0 #else @@ -410,6 +439,7 @@ vec4 hook() /* Shader code */ #define EPSILON 0.00000000001 +#define M_PI 3.14159265358979323846 #if PS == 6 const int hp = P/2; @@ -459,6 +489,7 @@ const float hr = int(R/2) - 0.5*(1-(R%2)); // sample between pixels for even res #define R_AREA(a) (a * T1 + RF-1) // research shapes +// XXX would be nice to have the option of temporally-varying research sizes #if R == 0 || R == 1 #define FOR_RESEARCH(r) S_1X1(r) const int r_area = R_AREA(1); @@ -630,11 +661,13 @@ vec4 patch_comparison(vec3 r, vec3 r2) return min_rot * p_scale; } -#define NO_GATHER (PD == 0) // never textureGather if any of these conditions are false +#define NO_GATHER (PD == 0 && NG == 0) // never textureGather if any of these conditions are false #define REGULAR_ROTATIONS (RI == 0 || RI == 1 || RI == 3) #if (defined(LUMA_gather) || D1W) && ((PS == 3 || PS == 7) && P == 3) && PST == 0 && M != 1 && REGULAR_ROTATIONS && NO_GATHER // 3x3 diamond/plus patch_comparison_gather +// XXX extend to support arbitrary sizes (probably requires code generation) +// XXX extend to support 3x3 square const ivec2 offsets[4] = { ivec2(0,-1), ivec2(-1,0), ivec2(0,1), ivec2(1,0) }; const ivec2 offsets_sf[4] = { ivec2(0,-1) * SF, ivec2(-1,0) * SF, ivec2(0,1) * SF, ivec2(1,0) * SF }; vec4 poi_patch = gather_offs(0, offsets); @@ -664,6 +697,7 @@ vec4 patch_comparison_gather(vec3 r, vec3 r2) } #elif (defined(LUMA_gather) || D1W) && PS == 6 && REGULAR_ROTATIONS && NO_GATHER // tiled even square patch_comparison_gather +// XXX extend to support odd square? vec4 patch_comparison_gather(vec3 r, vec3 r2) { vec2 tile; @@ -744,6 +778,7 @@ vec4 hook() #endif FOR_FRAME(r) { + // XXX ME is always a frame behind, should have to option to re-research after applying ME (could do it an arbitrary number of times per frame if desired) #if T && ME == 1 // temporal & motion estimation max weight if (r.z > 0) { me += me_tmp; @@ -814,6 +849,7 @@ vec4 hook() } // FOR_RESEARCH } // FOR_FRAME + // XXX optionally put the denoised pixel into the frame buffer? #if T // temporal #endif @@ -865,12 +901,29 @@ vec4 hook() result = sum / total_weight; #endif +#if ASW == 0 // pre-WD weights +#define AS_weight old_avg_weight +#elif ASW == 1 // post-WD weights +#define AS_weight avg_weight +#endif + +#if ASK == 0 + vec4 sharpening_strength = pow(AS_weight, vec4(ASP)); +#elif ASK == 1 +#define sigmoid(x) (tanh(x * 2*M_PI - M_PI)*0.5+0.5) + vec4 sharpening_strength = mix(pow(sigmoid(AS_weight), vec4(ASP)), + AS_weight, ASC); + // just in case ASC < 0 (will sharpen but it's janky XXX) + sharpening_strength = clamp(sharpening_strength, 0.0, 1.0); +#elif ASK == 2 + vec4 sharpening_strength = vec4(ASP); +#endif + + // XXX maybe allow for alternative blurs? e.g., replace result w/ load2? #if AS == 1 // sharpen+denoise vec4 sharpened = result + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #elif AS == 2 // sharpen only vec4 sharpened = poi + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #endif #if EP // extremes preserve @@ -881,9 +934,23 @@ vec4 hook() #endif #if AS == 1 // sharpen+denoise - result = mix(sharpened, result, sharpening_power); + result = mix(sharpened, result, sharpening_strength); #elif AS == 2 // sharpen only - result = mix(sharpened, poi, sharpening_power); + result = mix(sharpened, poi, sharpening_strength); +#endif + +#if M == 4 // edge map + result = sharpening_strength; +#endif + +#if (M == 2 || M == 4) && defined(CHROMA_raw) // drop chroma for weight maps + result = vec4(0.5); +#endif + +#if DV == 1 + result = clamp(abs(poi - result) * S, 0.0, 1.0); +#elif DV == 2 + result = (poi - result) * 0.5 + 0.5; #endif return mix(poi, result, BF); diff --git a/portable_config/shaders/nlmeans_lq.glsl b/portable_config/shaders/nlmeans_lq.glsl index 39ba8e84..23da8884 100644 --- a/portable_config/shaders/nlmeans_lq.glsl +++ b/portable_config/shaders/nlmeans_lq.glsl @@ -82,13 +82,11 @@ * - Reflections may have a significant speed impact * * Options which always disable textureGather: - * - RF * - PD */ //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Non-local means (downscale) //!BIND HOOKED //!SAVE PRERF_LUMA @@ -102,7 +100,6 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Non-local means (unscale) //!BIND PRERF_LUMA //!SAVE RF_LUMA @@ -116,7 +113,6 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Non-local means (downscale) //!WIDTH LUMA.w 3 / //!HEIGHT LUMA.h 3 / @@ -130,7 +126,6 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Non-local means (share) //!BIND RF_LUMA //!SAVE RF @@ -142,7 +137,6 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!BIND HOOKED //!BIND RF_LUMA //!BIND EP @@ -169,8 +163,7 @@ vec4 hook() * * Even-numbered patch/research sizes will sample between pixels unless PS=6. * It's not known whether this is ever useful behavior or not. This is - * incompatible with textureGather optimizations, so enable RF when using even - * patch/research sizes. + * incompatible with textureGather optimizations, so NG=1 to disable them. */ #ifdef LUMA_raw #define S 1.25 @@ -184,24 +177,37 @@ vec4 hook() /* Adaptive sharpening * - * Uses the blur incurred by denoising plus the weight map to perform an - * unsharp mask that gets applied most strongly to edges. + * Uses the blur incurred by denoising to perform an unsharp mask, and uses the + * weight map to restrict the sharpening to edges. * - * Sharpening will amplify noise, so the denoising factor (S) should usually be - * increased to compensate. + * Use M=4 to get a good look at which areas are/aren't sharpened. * * AS: 2 for sharpening, 1 for sharpening+denoising, 0 to disable * ASF: Sharpening factor, higher numbers make a sharper underlying image * ASP: Weight power, higher numbers use more of the sharp image + * ASW: + * - 0 to use pre-WD weights + * - 1 to use post-WD weights (ASP should be ~2x to compensate) + * ASK: Weight kernel: + * - 0 for power. This is the old method. + * - 1 for sigmoid. This is generally recommended. + * - 2 for constant (non-adaptive, w/ ASP=0 this sharpens the entire image) + * ASC (only for ASK=1, range 0-1): Reduces the contrast of the edge map */ #ifdef LUMA_raw #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #else #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #endif /* Starting weight @@ -210,8 +216,8 @@ vec4 hook() * handle higher noise levels, ringing, and may be useful for other things too? * * EPSILON should be used instead of zero to avoid divide-by-zero errors. The - * avg_weight variable may be used to make SW adapt to the local noise level, - * e.g., SW=max(avg_weight, EPSILON) + * avg_weight/old_avg_weight variables may be used to make SW adapt to the + * local noise level, e.g., SW=max(avg_weight, EPSILON) */ #ifdef LUMA_raw #define SW 1.0 @@ -231,7 +237,7 @@ vec4 hook() * - 0: Disable * * WDT: Threshold coefficient, higher numbers discard more - * WDP (WD=1): Higher numbers reduce the threshold more for small sample sizes + * WDP (only for WD=1): Increasing reduces the threshold for small sample sizes */ #ifdef LUMA_raw #define WD 1 @@ -243,6 +249,49 @@ vec4 hook() #define WDP 6.0 #endif +/* Extremes preserve + * + * Reduces denoising around very bright/dark areas. The downscaling factor of + * EP (located near the top of this shader) controls the area sampled for + * luminance (higher numbers consider more area). + * + * This is incompatible with RGB. If you have RGB hooks enabled then you will + * have to delete the EP shader stage or specify EP=0 through nlmeans_cfg. + * + * EP: 1 to enable, 0 to disable + * DP: EP strength on dark patches, 0 to fully denoise + * BP: EP strength on bright patches, 0 to fully denoise + */ +#ifdef LUMA_raw +#define EP 1 +#define BP 0.75 +#define DP 0.25 +#else +#define EP 0 +#define BP 0.0 +#define DP 0.0 +#endif + +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ + +/* Robust filtering + * + * This setting is dependent on code generation from nlmeans_cfg, so this + * setting can only be enabled via nlmeans_cfg. + * + * Compares the pixel-of-interest against a guide, which could be a downscaled + * image or the output of another shader such as guided.glsl + */ +#ifdef LUMA_raw +#define RF 1 +#else +#define RF 1 +#endif + /* Search shape * * Determines the shape of patches and research zones. Different shapes have @@ -251,6 +300,8 @@ vec4 hook() * * PS applies applies to patches, RS applies to research zones. * + * Be wary of gather optimizations (see the Regarding Speed comment at the top) + * * 0: square (symmetrical) * 1: horizontal line (asymmetric) * 2: vertical line (asymmetric) @@ -297,8 +348,9 @@ vec4 hook() * - Buggy * * Gather samples across multiple frames. May cause motion blur and may - * struggle more with noise that persists across multiple frames (compression - * noise, repeating frames), but can work very well on high quality video. + * struggle more with noise that persists across multiple frames (e.g., from + * compression or duplicate frames), but can work very well on high quality + * video. * * Motion estimation (ME) should improve quality without impacting speed. * @@ -333,60 +385,51 @@ vec4 hook() */ #ifdef LUMA_raw #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #else #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #endif -/* Extremes preserve - * - * Reduces denoising around very bright/dark areas. The downscaling factor of - * EP (located near the top of this shader) controls the area sampled for - * luminance (higher numbers consider more area). +// Scaling factor (should match WIDTH/HEIGHT) +#ifdef LUMA_raw +#define SF 1 +#else +#define SF 1 +#endif + +/* Estimator * - * EP: 1 to enable, 0 to disable - * DP: EP strength on dark patches, 0 to fully denoise - * BP: EP strength on bright patches, 0 to fully denoise + * 0: means + * 1: Euclidean medians (extremely slow, may be good for heavy noise) + * 2: weight map (not a denoiser, maybe useful for generating image masks) + * 3: weighted median intensity (slow, may be good for heavy noise) + * 4: edge map (based on the relevant AS settings) */ #ifdef LUMA_raw -#define EP 1 -#define BP 0.75 -#define DP 0.25 +#define M 0 #else -#define EP 0 -#define BP 0.0 -#define DP 0.0 +#define M 0 #endif -/* Robust filtering - * - * This setting is dependent on code generation from nlmeans_cfg, so this - * setting can only be enabled via nlmeans_cfg. +/* Difference visualization * - * Compares the pixel-of-interest against downscaled pixels. + * Visualizes the difference between input/output image * - * This will virtually always improve quality, but will always disable - * textureGather optimizations. - * - * The downscale factor can be modified in the WIDTH/HEIGHT directives for the - * RF texture (for CHROMA, RGB) and RF_LUMA (LUMA only) textures near the top - * of this shader, higher numbers increase blur. - * - * Any notation of RF as a positive number should be assumed to be referring to - * the downscaling factor, e.g., RF=3 means RF is set to 1 and the downscaling - * factor is set to 3. + * 0: off + * 1: absolute difference scaled by S + * 2: difference centered on 0.5 */ #ifdef LUMA_raw -#define RF 1 +#define DV 0 #else -#define RF 1 +#define DV 0 #endif /* Blur factor @@ -400,30 +443,11 @@ vec4 hook() #define BF 1.0 #endif -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ - -// Scaling factor (should match WIDTH/HEIGHT) -#ifdef LUMA_raw -#define SF 1 -#else -#define SF 1 -#endif - -/* Estimator - * - * 0: means - * 1: Euclidean medians (extremely slow, may be good for heavy noise) - * 2: weight map (not a denoiser, maybe useful for generating image masks) - * 3: weighted median intensity (slow, may be good for heavy noise) - */ +// Force disable textureGather #ifdef LUMA_raw -#define M 0 +#define NG 0 #else -#define M 0 +#define NG 0 #endif // Patch donut (probably useless) @@ -433,7 +457,7 @@ vec4 hook() #define PD 0 #endif -// Duplicate 1st weight +// Duplicate 1st weight (for LGC) #ifdef LUMA_raw #define D1W 0 #else @@ -443,6 +467,7 @@ vec4 hook() /* Shader code */ #define EPSILON 0.00000000001 +#define M_PI 3.14159265358979323846 #if PS == 6 const int hp = P/2; @@ -492,6 +517,7 @@ const float hr = int(R/2) - 0.5*(1-(R%2)); // sample between pixels for even res #define R_AREA(a) (a * T1 + RF-1) // research shapes +// XXX would be nice to have the option of temporally-varying research sizes #if R == 0 || R == 1 #define FOR_RESEARCH(r) S_1X1(r) const int r_area = R_AREA(1); @@ -663,11 +689,13 @@ vec4 patch_comparison(vec3 r, vec3 r2) return min_rot * p_scale; } -#define NO_GATHER (PD == 0) // never textureGather if any of these conditions are false +#define NO_GATHER (PD == 0 && NG == 0) // never textureGather if any of these conditions are false #define REGULAR_ROTATIONS (RI == 0 || RI == 1 || RI == 3) #if (defined(LUMA_gather) || D1W) && ((PS == 3 || PS == 7) && P == 3) && PST == 0 && M != 1 && REGULAR_ROTATIONS && NO_GATHER // 3x3 diamond/plus patch_comparison_gather +// XXX extend to support arbitrary sizes (probably requires code generation) +// XXX extend to support 3x3 square const ivec2 offsets[4] = { ivec2(0,-1), ivec2(-1,0), ivec2(0,1), ivec2(1,0) }; const ivec2 offsets_sf[4] = { ivec2(0,-1) * SF, ivec2(-1,0) * SF, ivec2(0,1) * SF, ivec2(1,0) * SF }; vec4 poi_patch = gather_offs(0, offsets); @@ -697,6 +725,7 @@ vec4 patch_comparison_gather(vec3 r, vec3 r2) } #elif (defined(LUMA_gather) || D1W) && PS == 6 && REGULAR_ROTATIONS && NO_GATHER // tiled even square patch_comparison_gather +// XXX extend to support odd square? vec4 patch_comparison_gather(vec3 r, vec3 r2) { vec2 tile; @@ -777,6 +806,7 @@ vec4 hook() #endif FOR_FRAME(r) { + // XXX ME is always a frame behind, should have to option to re-research after applying ME (could do it an arbitrary number of times per frame if desired) #if T && ME == 1 // temporal & motion estimation max weight if (r.z > 0) { me += me_tmp; @@ -847,6 +877,7 @@ vec4 hook() } // FOR_RESEARCH } // FOR_FRAME + // XXX optionally put the denoised pixel into the frame buffer? #if T // temporal #endif @@ -898,12 +929,29 @@ vec4 hook() result = sum / total_weight; #endif +#if ASW == 0 // pre-WD weights +#define AS_weight old_avg_weight +#elif ASW == 1 // post-WD weights +#define AS_weight avg_weight +#endif + +#if ASK == 0 + vec4 sharpening_strength = pow(AS_weight, vec4(ASP)); +#elif ASK == 1 +#define sigmoid(x) (tanh(x * 2*M_PI - M_PI)*0.5+0.5) + vec4 sharpening_strength = mix(pow(sigmoid(AS_weight), vec4(ASP)), + AS_weight, ASC); + // just in case ASC < 0 (will sharpen but it's janky XXX) + sharpening_strength = clamp(sharpening_strength, 0.0, 1.0); +#elif ASK == 2 + vec4 sharpening_strength = vec4(ASP); +#endif + + // XXX maybe allow for alternative blurs? e.g., replace result w/ load2? #if AS == 1 // sharpen+denoise vec4 sharpened = result + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #elif AS == 2 // sharpen only vec4 sharpened = poi + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #endif #if EP // extremes preserve @@ -914,9 +962,23 @@ vec4 hook() #endif #if AS == 1 // sharpen+denoise - result = mix(sharpened, result, sharpening_power); + result = mix(sharpened, result, sharpening_strength); #elif AS == 2 // sharpen only - result = mix(sharpened, poi, sharpening_power); + result = mix(sharpened, poi, sharpening_strength); +#endif + +#if M == 4 // edge map + result = sharpening_strength; +#endif + +#if (M == 2 || M == 4) && defined(CHROMA_raw) // drop chroma for weight maps + result = vec4(0.5); +#endif + +#if DV == 1 + result = clamp(abs(poi - result) * S, 0.0, 1.0); +#elif DV == 2 + result = (poi - result) * 0.5 + 0.5; #endif return mix(poi, result, BF); diff --git a/portable_config/shaders/nlmeans_luma.glsl b/portable_config/shaders/nlmeans_luma.glsl index 3e8a733c..6944449c 100644 --- a/portable_config/shaders/nlmeans_luma.glsl +++ b/portable_config/shaders/nlmeans_luma.glsl @@ -82,11 +82,10 @@ * - Reflections may have a significant speed impact * * Options which always disable textureGather: - * - RF * - PD */ -// The following is shader code injected from guided_s.glsl +// The following is shader code injected from guided.glsl /* vi: ft=c * * Copyright (c) 2022 an3223 @@ -105,23 +104,50 @@ * along with this program. If not, see . */ -//desc: "Self-guided" guided filter +//desc: Guided filter guided by the downscaled image -/* The radius can be adjusted with the MEANIP stage's downscaling factor. +/* The radius can be adjusted with the MEANI stage's downscaling factor. * Higher numbers give a bigger radius. * * The E variable can be found in the A stage. * - * The subsampling (fast guided filter) can be adjusted with the IP stage's + * The subsampling (fast guided filter) can be adjusted with the I stage's * downscaling factor. Higher numbers are faster. + * + * The guide's subsampling can be adjusted with the PREI stage's downscaling + * factor. Higher numbers downscale more. */ //!HOOK LUMA -//!DESC Guided filter (IP) +//!DESC Guided filter (PREI) //!BIND HOOKED +//!WIDTH HOOKED.w 1.25 / +//!HEIGHT HOOKED.h 1.25 / +//!SAVE _INJ_PREI + +vec4 hook() +{ + return HOOKED_texOff(0); +} + +//!HOOK LUMA +//!DESC Guided filter (I) +//!BIND _INJ_PREI //!WIDTH HOOKED.w 1.0 / //!HEIGHT HOOKED.h 1.0 / -//!SAVE _INJ_IP +//!SAVE _INJ_I + +vec4 hook() +{ +return _INJ_PREI_texOff(0); +} + +//!HOOK LUMA +//!DESC Guided filter (P) +//!BIND HOOKED +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_P vec4 hook() { @@ -129,76 +155,116 @@ vec4 hook() } //!HOOK LUMA -//!DESC Guided filter (MEANIP) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w 2.0 / -//!HEIGHT _INJ_IP.h 2.0 / -//!SAVE _INJ_MEANIP +//!DESC Guided filter (MEANI) +//!BIND _INJ_I +//!WIDTH _INJ_I.w 1.5 / +//!HEIGHT _INJ_I.h 1.5 / +//!SAVE _INJ_MEANI + +vec4 hook() +{ +return _INJ_I_texOff(0); +} + +//!HOOK LUMA +//!DESC Guided filter (MEANP) +//!BIND _INJ_P +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_MEANP + +vec4 hook() +{ +return _INJ_P_texOff(0); +} + +//!HOOK LUMA +//!DESC Guided filter (_INJ_I_SQ) +//!BIND _INJ_I +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_I_SQ vec4 hook() { -return _INJ_IP_texOff(0); +return _INJ_I_texOff(0) * _INJ_I_texOff(0); } //!HOOK LUMA -//!DESC Guided filter (_INJ_IP_SQ) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h -//!SAVE _INJ_IP_SQ +//!DESC Guided filter (_INJ_IXP) +//!BIND _INJ_I +//!BIND _INJ_P +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_IXP vec4 hook() { -return _INJ_IP_texOff(0) * _INJ_IP_texOff(0); +return _INJ_I_texOff(0) * _INJ_P_texOff(0); } //!HOOK LUMA -//!DESC Guided filter (CORRIP) -//!BIND _INJ_IP_SQ -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h -//!SAVE _INJ_CORRIP +//!DESC Guided filter (CORRI) +//!BIND _INJ_I_SQ +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_CORRI vec4 hook() { -return _INJ_IP_SQ_texOff(0); +return _INJ_I_SQ_texOff(0); +} + +//!HOOK LUMA +//!DESC Guided filter (CORRP) +//!BIND _INJ_IXP +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_CORRP + +vec4 hook() +{ +return _INJ_IXP_texOff(0); } //!HOOK LUMA //!DESC Guided filter (A) -//!BIND _INJ_MEANIP -//!BIND _INJ_CORRIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h +//!BIND _INJ_MEANI +//!BIND _INJ_MEANP +//!BIND _INJ_CORRI +//!BIND _INJ_CORRP +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h //!SAVE _INJ_A -#define E 0.001 +#define E 0.0013 vec4 hook() { -vec4 var = _INJ_CORRIP_texOff(0) - _INJ_MEANIP_texOff(0) * _INJ_MEANIP_texOff(0); - vec4 cov = var; +vec4 var = _INJ_CORRI_texOff(0) - _INJ_MEANI_texOff(0) * _INJ_MEANI_texOff(0); +vec4 cov = _INJ_CORRP_texOff(0) - _INJ_MEANI_texOff(0) * _INJ_MEANP_texOff(0); return cov / (var + E); } //!HOOK LUMA //!DESC Guided filter (B) //!BIND _INJ_A -//!BIND _INJ_MEANIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h +//!BIND _INJ_MEANI +//!BIND _INJ_MEANP +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h //!SAVE _INJ_B vec4 hook() { -return _INJ_MEANIP_texOff(0) - _INJ_A_texOff(0) * _INJ_MEANIP_texOff(0); +return _INJ_MEANP_texOff(0) - _INJ_A_texOff(0) * _INJ_MEANI_texOff(0); } //!HOOK LUMA //!DESC Guided filter (MEANA) //!BIND _INJ_A -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h //!SAVE _INJ_MEANA vec4 hook() @@ -209,8 +275,8 @@ return _INJ_A_texOff(0); //!HOOK LUMA //!DESC Guided filter (MEANB) //!BIND _INJ_B -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h //!SAVE _INJ_MEANB vec4 hook() @@ -230,7 +296,7 @@ vec4 hook() return _INJ_MEANA_texOff(0) * HOOKED_texOff(0) + _INJ_MEANB_texOff(0); } -// End of source code injected from guided_s.glsl +// End of source code injected from guided.glsl //!HOOK LUMA //!DESC Non-local means (downscale) //!WIDTH LUMA.w 3 / @@ -280,11 +346,10 @@ vec4 hook() * * Even-numbered patch/research sizes will sample between pixels unless PS=6. * It's not known whether this is ever useful behavior or not. This is - * incompatible with textureGather optimizations, so enable RF when using even - * patch/research sizes. + * incompatible with textureGather optimizations, so NG=1 to disable them. */ #ifdef LUMA_raw -#define S 2.25 +#define S 20.0 #define P 3 #define R 5 #else @@ -295,24 +360,37 @@ vec4 hook() /* Adaptive sharpening * - * Uses the blur incurred by denoising plus the weight map to perform an - * unsharp mask that gets applied most strongly to edges. + * Uses the blur incurred by denoising to perform an unsharp mask, and uses the + * weight map to restrict the sharpening to edges. * - * Sharpening will amplify noise, so the denoising factor (S) should usually be - * increased to compensate. + * Use M=4 to get a good look at which areas are/aren't sharpened. * * AS: 2 for sharpening, 1 for sharpening+denoising, 0 to disable * ASF: Sharpening factor, higher numbers make a sharper underlying image * ASP: Weight power, higher numbers use more of the sharp image + * ASW: + * - 0 to use pre-WD weights + * - 1 to use post-WD weights (ASP should be ~2x to compensate) + * ASK: Weight kernel: + * - 0 for power. This is the old method. + * - 1 for sigmoid. This is generally recommended. + * - 2 for constant (non-adaptive, w/ ASP=0 this sharpens the entire image) + * ASC (only for ASK=1, range 0-1): Reduces the contrast of the edge map */ #ifdef LUMA_raw #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #else #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #endif /* Starting weight @@ -321,8 +399,8 @@ vec4 hook() * handle higher noise levels, ringing, and may be useful for other things too? * * EPSILON should be used instead of zero to avoid divide-by-zero errors. The - * avg_weight variable may be used to make SW adapt to the local noise level, - * e.g., SW=max(avg_weight, EPSILON) + * avg_weight/old_avg_weight variables may be used to make SW adapt to the + * local noise level, e.g., SW=max(avg_weight, EPSILON) */ #ifdef LUMA_raw #define SW 1.0 @@ -342,7 +420,7 @@ vec4 hook() * - 0: Disable * * WDT: Threshold coefficient, higher numbers discard more - * WDP (WD=1): Higher numbers reduce the threshold more for small sample sizes + * WDP (only for WD=1): Increasing reduces the threshold for small sample sizes */ #ifdef LUMA_raw #define WD 2 @@ -354,6 +432,49 @@ vec4 hook() #define WDP 6.0 #endif +/* Extremes preserve + * + * Reduces denoising around very bright/dark areas. The downscaling factor of + * EP (located near the top of this shader) controls the area sampled for + * luminance (higher numbers consider more area). + * + * This is incompatible with RGB. If you have RGB hooks enabled then you will + * have to delete the EP shader stage or specify EP=0 through nlmeans_cfg. + * + * EP: 1 to enable, 0 to disable + * DP: EP strength on dark patches, 0 to fully denoise + * BP: EP strength on bright patches, 0 to fully denoise + */ +#ifdef LUMA_raw +#define EP 1 +#define BP 0.75 +#define DP 0.25 +#else +#define EP 0 +#define BP 0.0 +#define DP 0.0 +#endif + +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ + +/* Robust filtering + * + * This setting is dependent on code generation from nlmeans_cfg, so this + * setting can only be enabled via nlmeans_cfg. + * + * Compares the pixel-of-interest against a guide, which could be a downscaled + * image or the output of another shader such as guided.glsl + */ +#ifdef LUMA_raw +#define RF 1 +#else +#define RF 1 +#endif + /* Search shape * * Determines the shape of patches and research zones. Different shapes have @@ -362,6 +483,8 @@ vec4 hook() * * PS applies applies to patches, RS applies to research zones. * + * Be wary of gather optimizations (see the Regarding Speed comment at the top) + * * 0: square (symmetrical) * 1: horizontal line (asymmetric) * 2: vertical line (asymmetric) @@ -408,8 +531,9 @@ vec4 hook() * - Buggy * * Gather samples across multiple frames. May cause motion blur and may - * struggle more with noise that persists across multiple frames (compression - * noise, repeating frames), but can work very well on high quality video. + * struggle more with noise that persists across multiple frames (e.g., from + * compression or duplicate frames), but can work very well on high quality + * video. * * Motion estimation (ME) should improve quality without impacting speed. * @@ -444,60 +568,51 @@ vec4 hook() */ #ifdef LUMA_raw #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #else #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #endif -/* Extremes preserve - * - * Reduces denoising around very bright/dark areas. The downscaling factor of - * EP (located near the top of this shader) controls the area sampled for - * luminance (higher numbers consider more area). +// Scaling factor (should match WIDTH/HEIGHT) +#ifdef LUMA_raw +#define SF 1 +#else +#define SF 1 +#endif + +/* Estimator * - * EP: 1 to enable, 0 to disable - * DP: EP strength on dark patches, 0 to fully denoise - * BP: EP strength on bright patches, 0 to fully denoise + * 0: means + * 1: Euclidean medians (extremely slow, may be good for heavy noise) + * 2: weight map (not a denoiser, maybe useful for generating image masks) + * 3: weighted median intensity (slow, may be good for heavy noise) + * 4: edge map (based on the relevant AS settings) */ #ifdef LUMA_raw -#define EP 1 -#define BP 0.75 -#define DP 0.25 +#define M 0 #else -#define EP 0 -#define BP 0.0 -#define DP 0.0 +#define M 0 #endif -/* Robust filtering - * - * This setting is dependent on code generation from nlmeans_cfg, so this - * setting can only be enabled via nlmeans_cfg. - * - * Compares the pixel-of-interest against downscaled pixels. +/* Difference visualization * - * This will virtually always improve quality, but will always disable - * textureGather optimizations. + * Visualizes the difference between input/output image * - * The downscale factor can be modified in the WIDTH/HEIGHT directives for the - * RF texture (for CHROMA, RGB) and RF_LUMA (LUMA only) textures near the top - * of this shader, higher numbers increase blur. - * - * Any notation of RF as a positive number should be assumed to be referring to - * the downscaling factor, e.g., RF=3 means RF is set to 1 and the downscaling - * factor is set to 3. + * 0: off + * 1: absolute difference scaled by S + * 2: difference centered on 0.5 */ #ifdef LUMA_raw -#define RF 1 +#define DV 0 #else -#define RF 1 +#define DV 0 #endif /* Blur factor @@ -511,30 +626,11 @@ vec4 hook() #define BF 1.0 #endif -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ - -// Scaling factor (should match WIDTH/HEIGHT) +// Force disable textureGather #ifdef LUMA_raw -#define SF 1 +#define NG 0 #else -#define SF 1 -#endif - -/* Estimator - * - * 0: means - * 1: Euclidean medians (extremely slow, may be good for heavy noise) - * 2: weight map (not a denoiser, maybe useful for generating image masks) - * 3: weighted median intensity (slow, may be good for heavy noise) - */ -#ifdef LUMA_raw -#define M 0 -#else -#define M 0 +#define NG 0 #endif // Patch donut (probably useless) @@ -544,7 +640,7 @@ vec4 hook() #define PD 0 #endif -// Duplicate 1st weight +// Duplicate 1st weight (for LGC) #ifdef LUMA_raw #define D1W 0 #else @@ -554,6 +650,7 @@ vec4 hook() /* Shader code */ #define EPSILON 0.00000000001 +#define M_PI 3.14159265358979323846 #if PS == 6 const int hp = P/2; @@ -603,6 +700,7 @@ const float hr = int(R/2) - 0.5*(1-(R%2)); // sample between pixels for even res #define R_AREA(a) (a * T1 + RF-1) // research shapes +// XXX would be nice to have the option of temporally-varying research sizes #if R == 0 || R == 1 #define FOR_RESEARCH(r) S_1X1(r) const int r_area = R_AREA(1); @@ -774,11 +872,13 @@ vec4 patch_comparison(vec3 r, vec3 r2) return min_rot * p_scale; } -#define NO_GATHER (PD == 0) // never textureGather if any of these conditions are false +#define NO_GATHER (PD == 0 && NG == 0) // never textureGather if any of these conditions are false #define REGULAR_ROTATIONS (RI == 0 || RI == 1 || RI == 3) #if (defined(LUMA_gather) || D1W) && ((PS == 3 || PS == 7) && P == 3) && PST == 0 && M != 1 && REGULAR_ROTATIONS && NO_GATHER // 3x3 diamond/plus patch_comparison_gather +// XXX extend to support arbitrary sizes (probably requires code generation) +// XXX extend to support 3x3 square const ivec2 offsets[4] = { ivec2(0,-1), ivec2(-1,0), ivec2(0,1), ivec2(1,0) }; const ivec2 offsets_sf[4] = { ivec2(0,-1) * SF, ivec2(-1,0) * SF, ivec2(0,1) * SF, ivec2(1,0) * SF }; vec4 poi_patch = gather_offs(0, offsets); @@ -808,6 +908,7 @@ vec4 patch_comparison_gather(vec3 r, vec3 r2) } #elif (defined(LUMA_gather) || D1W) && PS == 6 && REGULAR_ROTATIONS && NO_GATHER // tiled even square patch_comparison_gather +// XXX extend to support odd square? vec4 patch_comparison_gather(vec3 r, vec3 r2) { vec2 tile; @@ -888,6 +989,7 @@ vec4 hook() #endif FOR_FRAME(r) { + // XXX ME is always a frame behind, should have to option to re-research after applying ME (could do it an arbitrary number of times per frame if desired) #if T && ME == 1 // temporal & motion estimation max weight if (r.z > 0) { me += me_tmp; @@ -958,6 +1060,7 @@ vec4 hook() } // FOR_RESEARCH } // FOR_FRAME + // XXX optionally put the denoised pixel into the frame buffer? #if T // temporal #endif @@ -1009,12 +1112,29 @@ vec4 hook() result = sum / total_weight; #endif +#if ASW == 0 // pre-WD weights +#define AS_weight old_avg_weight +#elif ASW == 1 // post-WD weights +#define AS_weight avg_weight +#endif + +#if ASK == 0 + vec4 sharpening_strength = pow(AS_weight, vec4(ASP)); +#elif ASK == 1 +#define sigmoid(x) (tanh(x * 2*M_PI - M_PI)*0.5+0.5) + vec4 sharpening_strength = mix(pow(sigmoid(AS_weight), vec4(ASP)), + AS_weight, ASC); + // just in case ASC < 0 (will sharpen but it's janky XXX) + sharpening_strength = clamp(sharpening_strength, 0.0, 1.0); +#elif ASK == 2 + vec4 sharpening_strength = vec4(ASP); +#endif + + // XXX maybe allow for alternative blurs? e.g., replace result w/ load2? #if AS == 1 // sharpen+denoise vec4 sharpened = result + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #elif AS == 2 // sharpen only vec4 sharpened = poi + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #endif #if EP // extremes preserve @@ -1025,9 +1145,23 @@ vec4 hook() #endif #if AS == 1 // sharpen+denoise - result = mix(sharpened, result, sharpening_power); + result = mix(sharpened, result, sharpening_strength); #elif AS == 2 // sharpen only - result = mix(sharpened, poi, sharpening_power); + result = mix(sharpened, poi, sharpening_strength); +#endif + +#if M == 4 // edge map + result = sharpening_strength; +#endif + +#if (M == 2 || M == 4) && defined(CHROMA_raw) // drop chroma for weight maps + result = vec4(0.5); +#endif + +#if DV == 1 + result = clamp(abs(poi - result) * S, 0.0, 1.0); +#elif DV == 2 + result = (poi - result) * 0.5 + 0.5; #endif return mix(poi, result, BF); diff --git a/portable_config/shaders/nlmeans_temporal.glsl b/portable_config/shaders/nlmeans_temporal.glsl index 3b24553b..f16ec8a4 100644 --- a/portable_config/shaders/nlmeans_temporal.glsl +++ b/portable_config/shaders/nlmeans_temporal.glsl @@ -82,11 +82,10 @@ * - Reflections may have a significant speed impact * * Options which always disable textureGather: - * - RF * - PD */ -// The following is shader code injected from guided_s.glsl +// The following is shader code injected from guided.glsl /* vi: ft=c * * Copyright (c) 2022 an3223 @@ -105,25 +104,53 @@ * along with this program. If not, see . */ -//desc: "Self-guided" guided filter +//desc: Guided filter guided by the downscaled image -/* The radius can be adjusted with the MEANIP stage's downscaling factor. +/* The radius can be adjusted with the MEANI stage's downscaling factor. * Higher numbers give a bigger radius. * * The E variable can be found in the A stage. * - * The subsampling (fast guided filter) can be adjusted with the IP stage's + * The subsampling (fast guided filter) can be adjusted with the I stage's * downscaling factor. Higher numbers are faster. + * + * The guide's subsampling can be adjusted with the PREI stage's downscaling + * factor. Higher numbers downscale more. */ //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (IP) +//!DESC Guided filter (PREI) //!BIND HOOKED +//!WIDTH HOOKED.w 1.25 / +//!HEIGHT HOOKED.h 1.25 / +//!SAVE _INJ_PREI + +vec4 hook() +{ + return HOOKED_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (I) +//!BIND _INJ_PREI //!WIDTH HOOKED.w 1.0 / //!HEIGHT HOOKED.h 1.0 / -//!SAVE _INJ_IP +//!SAVE _INJ_I + +vec4 hook() +{ +return _INJ_PREI_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (P) +//!BIND HOOKED +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_P vec4 hook() { @@ -132,87 +159,124 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (MEANIP) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w 2.0 / -//!HEIGHT _INJ_IP.h 2.0 / -//!SAVE _INJ_MEANIP +//!DESC Guided filter (MEANI) +//!BIND _INJ_I +//!WIDTH _INJ_I.w 1.5 / +//!HEIGHT _INJ_I.h 1.5 / +//!SAVE _INJ_MEANI vec4 hook() { -return _INJ_IP_texOff(0); +return _INJ_I_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (_INJ_IP_SQ) -//!BIND _INJ_IP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h -//!SAVE _INJ_IP_SQ +//!DESC Guided filter (MEANP) +//!BIND _INJ_P +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_MEANP vec4 hook() { -return _INJ_IP_texOff(0) * _INJ_IP_texOff(0); +return _INJ_P_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB -//!DESC Guided filter (CORRIP) -//!BIND _INJ_IP_SQ -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h -//!SAVE _INJ_CORRIP +//!DESC Guided filter (_INJ_I_SQ) +//!BIND _INJ_I +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_I_SQ vec4 hook() { -return _INJ_IP_SQ_texOff(0); +return _INJ_I_texOff(0) * _INJ_I_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (_INJ_IXP) +//!BIND _INJ_I +//!BIND _INJ_P +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h +//!SAVE _INJ_IXP + +vec4 hook() +{ +return _INJ_I_texOff(0) * _INJ_P_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (CORRI) +//!BIND _INJ_I_SQ +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_CORRI + +vec4 hook() +{ +return _INJ_I_SQ_texOff(0); +} + +//!HOOK LUMA +//!HOOK CHROMA +//!DESC Guided filter (CORRP) +//!BIND _INJ_IXP +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h +//!SAVE _INJ_CORRP + +vec4 hook() +{ +return _INJ_IXP_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (A) -//!BIND _INJ_MEANIP -//!BIND _INJ_CORRIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h +//!BIND _INJ_MEANI +//!BIND _INJ_MEANP +//!BIND _INJ_CORRI +//!BIND _INJ_CORRP +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h //!SAVE _INJ_A -#define E 0.001 +#define E 0.0013 vec4 hook() { -vec4 var = _INJ_CORRIP_texOff(0) - _INJ_MEANIP_texOff(0) * _INJ_MEANIP_texOff(0); - vec4 cov = var; +vec4 var = _INJ_CORRI_texOff(0) - _INJ_MEANI_texOff(0) * _INJ_MEANI_texOff(0); +vec4 cov = _INJ_CORRP_texOff(0) - _INJ_MEANI_texOff(0) * _INJ_MEANP_texOff(0); return cov / (var + E); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (B) //!BIND _INJ_A -//!BIND _INJ_MEANIP -//!WIDTH _INJ_IP.w -//!HEIGHT _INJ_IP.h +//!BIND _INJ_MEANI +//!BIND _INJ_MEANP +//!WIDTH _INJ_I.w +//!HEIGHT _INJ_I.h //!SAVE _INJ_B vec4 hook() { -return _INJ_MEANIP_texOff(0) - _INJ_A_texOff(0) * _INJ_MEANIP_texOff(0); +return _INJ_MEANP_texOff(0) - _INJ_A_texOff(0) * _INJ_MEANI_texOff(0); } //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (MEANA) //!BIND _INJ_A -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h //!SAVE _INJ_MEANA vec4 hook() @@ -222,11 +286,10 @@ return _INJ_A_texOff(0); //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter (MEANB) //!BIND _INJ_B -//!WIDTH _INJ_MEANIP.w -//!HEIGHT _INJ_MEANIP.h +//!WIDTH _INJ_MEANI.w +//!HEIGHT _INJ_MEANI.h //!SAVE _INJ_MEANB vec4 hook() @@ -236,7 +299,6 @@ return _INJ_B_texOff(0); //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Guided filter //!BIND HOOKED //!BIND _INJ_MEANA @@ -248,10 +310,9 @@ vec4 hook() return _INJ_MEANA_texOff(0) * HOOKED_texOff(0) + _INJ_MEANB_texOff(0); } -// End of source code injected from guided_s.glsl +// End of source code injected from guided.glsl //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Non-local means (downscale) //!WIDTH LUMA.w 3 / //!HEIGHT LUMA.h 3 / @@ -265,7 +326,6 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!DESC Non-local means (share) //!BIND RF_LUMA //!SAVE RF @@ -277,7 +337,6 @@ vec4 hook() //!HOOK LUMA //!HOOK CHROMA -//!HOOK RGB //!BIND HOOKED //!BIND RF_LUMA //!BIND EP @@ -307,11 +366,10 @@ vec4 hook() * * Even-numbered patch/research sizes will sample between pixels unless PS=6. * It's not known whether this is ever useful behavior or not. This is - * incompatible with textureGather optimizations, so enable RF when using even - * patch/research sizes. + * incompatible with textureGather optimizations, so NG=1 to disable them. */ #ifdef LUMA_raw -#define S 2.25 +#define S 20.0 #define P 3 #define R 5 #else @@ -322,24 +380,37 @@ vec4 hook() /* Adaptive sharpening * - * Uses the blur incurred by denoising plus the weight map to perform an - * unsharp mask that gets applied most strongly to edges. + * Uses the blur incurred by denoising to perform an unsharp mask, and uses the + * weight map to restrict the sharpening to edges. * - * Sharpening will amplify noise, so the denoising factor (S) should usually be - * increased to compensate. + * Use M=4 to get a good look at which areas are/aren't sharpened. * * AS: 2 for sharpening, 1 for sharpening+denoising, 0 to disable * ASF: Sharpening factor, higher numbers make a sharper underlying image * ASP: Weight power, higher numbers use more of the sharp image + * ASW: + * - 0 to use pre-WD weights + * - 1 to use post-WD weights (ASP should be ~2x to compensate) + * ASK: Weight kernel: + * - 0 for power. This is the old method. + * - 1 for sigmoid. This is generally recommended. + * - 2 for constant (non-adaptive, w/ ASP=0 this sharpens the entire image) + * ASC (only for ASK=1, range 0-1): Reduces the contrast of the edge map */ #ifdef LUMA_raw #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #else #define AS 0 -#define ASF 1.0 -#define ASP 2.0 +#define ASF 2.0 +#define ASP 32.0 +#define ASW 0 +#define ASK 1 +#define ASC 0.0 #endif /* Starting weight @@ -348,8 +419,8 @@ vec4 hook() * handle higher noise levels, ringing, and may be useful for other things too? * * EPSILON should be used instead of zero to avoid divide-by-zero errors. The - * avg_weight variable may be used to make SW adapt to the local noise level, - * e.g., SW=max(avg_weight, EPSILON) + * avg_weight/old_avg_weight variables may be used to make SW adapt to the + * local noise level, e.g., SW=max(avg_weight, EPSILON) */ #ifdef LUMA_raw #define SW 1.0 @@ -369,7 +440,7 @@ vec4 hook() * - 0: Disable * * WDT: Threshold coefficient, higher numbers discard more - * WDP (WD=1): Higher numbers reduce the threshold more for small sample sizes + * WDP (only for WD=1): Increasing reduces the threshold for small sample sizes */ #ifdef LUMA_raw #define WD 1 @@ -381,6 +452,49 @@ vec4 hook() #define WDP 6.0 #endif +/* Extremes preserve + * + * Reduces denoising around very bright/dark areas. The downscaling factor of + * EP (located near the top of this shader) controls the area sampled for + * luminance (higher numbers consider more area). + * + * This is incompatible with RGB. If you have RGB hooks enabled then you will + * have to delete the EP shader stage or specify EP=0 through nlmeans_cfg. + * + * EP: 1 to enable, 0 to disable + * DP: EP strength on dark patches, 0 to fully denoise + * BP: EP strength on bright patches, 0 to fully denoise + */ +#ifdef LUMA_raw +#define EP 1 +#define BP 0.75 +#define DP 0.25 +#else +#define EP 0 +#define BP 0.0 +#define DP 0.0 +#endif + +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ +/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ + +/* Robust filtering + * + * This setting is dependent on code generation from nlmeans_cfg, so this + * setting can only be enabled via nlmeans_cfg. + * + * Compares the pixel-of-interest against a guide, which could be a downscaled + * image or the output of another shader such as guided.glsl + */ +#ifdef LUMA_raw +#define RF 1 +#else +#define RF 1 +#endif + /* Search shape * * Determines the shape of patches and research zones. Different shapes have @@ -389,6 +503,8 @@ vec4 hook() * * PS applies applies to patches, RS applies to research zones. * + * Be wary of gather optimizations (see the Regarding Speed comment at the top) + * * 0: square (symmetrical) * 1: horizontal line (asymmetric) * 2: vertical line (asymmetric) @@ -435,8 +551,9 @@ vec4 hook() * - Buggy * * Gather samples across multiple frames. May cause motion blur and may - * struggle more with noise that persists across multiple frames (compression - * noise, repeating frames), but can work very well on high quality video. + * struggle more with noise that persists across multiple frames (e.g., from + * compression or duplicate frames), but can work very well on high quality + * video. * * Motion estimation (ME) should improve quality without impacting speed. * @@ -471,60 +588,51 @@ vec4 hook() */ #ifdef LUMA_raw #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #else #define SS 0.25 -#define SD vec3(1,1,1) +#define SD vec3(1,1,1.5) #define PST 0 #define PSS 0.0 #define PSD vec2(1,1) #endif -/* Extremes preserve - * - * Reduces denoising around very bright/dark areas. The downscaling factor of - * EP (located near the top of this shader) controls the area sampled for - * luminance (higher numbers consider more area). +// Scaling factor (should match WIDTH/HEIGHT) +#ifdef LUMA_raw +#define SF 1 +#else +#define SF 1 +#endif + +/* Estimator * - * EP: 1 to enable, 0 to disable - * DP: EP strength on dark patches, 0 to fully denoise - * BP: EP strength on bright patches, 0 to fully denoise + * 0: means + * 1: Euclidean medians (extremely slow, may be good for heavy noise) + * 2: weight map (not a denoiser, maybe useful for generating image masks) + * 3: weighted median intensity (slow, may be good for heavy noise) + * 4: edge map (based on the relevant AS settings) */ #ifdef LUMA_raw -#define EP 1 -#define BP 0.75 -#define DP 0.25 +#define M 0 #else -#define EP 0 -#define BP 0.0 -#define DP 0.0 +#define M 0 #endif -/* Robust filtering - * - * This setting is dependent on code generation from nlmeans_cfg, so this - * setting can only be enabled via nlmeans_cfg. +/* Difference visualization * - * Compares the pixel-of-interest against downscaled pixels. + * Visualizes the difference between input/output image * - * This will virtually always improve quality, but will always disable - * textureGather optimizations. - * - * The downscale factor can be modified in the WIDTH/HEIGHT directives for the - * RF texture (for CHROMA, RGB) and RF_LUMA (LUMA only) textures near the top - * of this shader, higher numbers increase blur. - * - * Any notation of RF as a positive number should be assumed to be referring to - * the downscaling factor, e.g., RF=3 means RF is set to 1 and the downscaling - * factor is set to 3. + * 0: off + * 1: absolute difference scaled by S + * 2: difference centered on 0.5 */ #ifdef LUMA_raw -#define RF 1 +#define DV 0 #else -#define RF 1 +#define DV 0 #endif /* Blur factor @@ -538,30 +646,11 @@ vec4 hook() #define BF 1.0 #endif -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ -/* ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS * ADVANCED OPTIONS */ - -// Scaling factor (should match WIDTH/HEIGHT) -#ifdef LUMA_raw -#define SF 1 -#else -#define SF 1 -#endif - -/* Estimator - * - * 0: means - * 1: Euclidean medians (extremely slow, may be good for heavy noise) - * 2: weight map (not a denoiser, maybe useful for generating image masks) - * 3: weighted median intensity (slow, may be good for heavy noise) - */ +// Force disable textureGather #ifdef LUMA_raw -#define M 0 +#define NG 0 #else -#define M 0 +#define NG 0 #endif // Patch donut (probably useless) @@ -571,7 +660,7 @@ vec4 hook() #define PD 0 #endif -// Duplicate 1st weight +// Duplicate 1st weight (for LGC) #ifdef LUMA_raw #define D1W 0 #else @@ -581,6 +670,7 @@ vec4 hook() /* Shader code */ #define EPSILON 0.00000000001 +#define M_PI 3.14159265358979323846 #if PS == 6 const int hp = P/2; @@ -630,6 +720,7 @@ const float hr = int(R/2) - 0.5*(1-(R%2)); // sample between pixels for even res #define R_AREA(a) (a * T1 + RF-1) // research shapes +// XXX would be nice to have the option of temporally-varying research sizes #if R == 0 || R == 1 #define FOR_RESEARCH(r) S_1X1(r) const int r_area = R_AREA(1); @@ -807,11 +898,13 @@ vec4 patch_comparison(vec3 r, vec3 r2) return min_rot * p_scale; } -#define NO_GATHER (PD == 0) // never textureGather if any of these conditions are false +#define NO_GATHER (PD == 0 && NG == 0) // never textureGather if any of these conditions are false #define REGULAR_ROTATIONS (RI == 0 || RI == 1 || RI == 3) #if (defined(LUMA_gather) || D1W) && ((PS == 3 || PS == 7) && P == 3) && PST == 0 && M != 1 && REGULAR_ROTATIONS && NO_GATHER // 3x3 diamond/plus patch_comparison_gather +// XXX extend to support arbitrary sizes (probably requires code generation) +// XXX extend to support 3x3 square const ivec2 offsets[4] = { ivec2(0,-1), ivec2(-1,0), ivec2(0,1), ivec2(1,0) }; const ivec2 offsets_sf[4] = { ivec2(0,-1) * SF, ivec2(-1,0) * SF, ivec2(0,1) * SF, ivec2(1,0) * SF }; vec4 poi_patch = gather_offs(0, offsets); @@ -841,6 +934,7 @@ vec4 patch_comparison_gather(vec3 r, vec3 r2) } #elif (defined(LUMA_gather) || D1W) && PS == 6 && REGULAR_ROTATIONS && NO_GATHER // tiled even square patch_comparison_gather +// XXX extend to support odd square? vec4 patch_comparison_gather(vec3 r, vec3 r2) { vec2 tile; @@ -921,6 +1015,7 @@ vec4 hook() #endif FOR_FRAME(r) { + // XXX ME is always a frame behind, should have to option to re-research after applying ME (could do it an arbitrary number of times per frame if desired) #if T && ME == 1 // temporal & motion estimation max weight if (r.z > 0) { me += me_tmp; @@ -991,6 +1086,7 @@ vec4 hook() } // FOR_RESEARCH } // FOR_FRAME + // XXX optionally put the denoised pixel into the frame buffer? #if T // temporal imageStore(PREV3, ivec2(HOOKED_pos*imageSize(PREV3)), load2(vec3(0,0,2))); imageStore(PREV2, ivec2(HOOKED_pos*imageSize(PREV2)), load2(vec3(0,0,1))); @@ -1045,12 +1141,29 @@ vec4 hook() result = sum / total_weight; #endif +#if ASW == 0 // pre-WD weights +#define AS_weight old_avg_weight +#elif ASW == 1 // post-WD weights +#define AS_weight avg_weight +#endif + +#if ASK == 0 + vec4 sharpening_strength = pow(AS_weight, vec4(ASP)); +#elif ASK == 1 +#define sigmoid(x) (tanh(x * 2*M_PI - M_PI)*0.5+0.5) + vec4 sharpening_strength = mix(pow(sigmoid(AS_weight), vec4(ASP)), + AS_weight, ASC); + // just in case ASC < 0 (will sharpen but it's janky XXX) + sharpening_strength = clamp(sharpening_strength, 0.0, 1.0); +#elif ASK == 2 + vec4 sharpening_strength = vec4(ASP); +#endif + + // XXX maybe allow for alternative blurs? e.g., replace result w/ load2? #if AS == 1 // sharpen+denoise vec4 sharpened = result + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #elif AS == 2 // sharpen only vec4 sharpened = poi + (poi - result) * ASF; - vec4 sharpening_power = pow(avg_weight, vec4(ASP)); #endif #if EP // extremes preserve @@ -1061,9 +1174,23 @@ vec4 hook() #endif #if AS == 1 // sharpen+denoise - result = mix(sharpened, result, sharpening_power); + result = mix(sharpened, result, sharpening_strength); #elif AS == 2 // sharpen only - result = mix(sharpened, poi, sharpening_power); + result = mix(sharpened, poi, sharpening_strength); +#endif + +#if M == 4 // edge map + result = sharpening_strength; +#endif + +#if (M == 2 || M == 4) && defined(CHROMA_raw) // drop chroma for weight maps + result = vec4(0.5); +#endif + +#if DV == 1 + result = clamp(abs(poi - result) * S, 0.0, 1.0); +#elif DV == 2 + result = (poi - result) * 0.5 + 0.5; #endif return mix(poi, result, BF); diff --git a/portable_config/shaders/unsharpMask_next.glsl b/portable_config/shaders/unsharpMask_next.glsl new file mode 100644 index 00000000..3b6b9a2b --- /dev/null +++ b/portable_config/shaders/unsharpMask_next.glsl @@ -0,0 +1,74 @@ +//!HOOK MAIN +//!BIND HOOKED +//!SAVE PASS0 +//!DESC unsharp mask pass0 + +vec4 hook() { + return linearize(textureLod(HOOKED_raw, HOOKED_pos, 0.0) * HOOKED_mul); +} + +//!HOOK MAIN +//!BIND PASS0 +//!SAVE PASS1 +//!DESC unsharp mask pass1 + +//////////////////////////////////////////////////////////////////////// +// USER CONFIGURABLE, PASS 1 (blur in y axis) +// +// CAUTION! probably should use the same settings for "USER CONFIGURABLE, PASS 2" below +// +#define SIGMA 1.0 //blur spread or amount, (0.0, 10+] +#define RADIUS 3.0 //kernel radius (integer as float, e.g. 3.0), (0.0, 10+]; probably should set it to ceil(3 * SIGMA) +// +//////////////////////////////////////////////////////////////////////// + +#define get_weight(x) (exp(-x * x / (2.0 * SIGMA * SIGMA))) + +vec4 hook() { + float weight; + vec4 csum = textureLod(PASS0_raw, PASS0_pos, 0.0) * PASS0_mul; + float wsum = 1.0; + for(float i = 1.0; i <= RADIUS; ++i) { + weight = get_weight(i); + csum += (textureLod(PASS0_raw, PASS0_pos + PASS0_pt * vec2(0.0, -i), 0.0) + textureLod(PASS0_raw, PASS0_pos + PASS0_pt * vec2(0.0, i), 0.0)) * PASS0_mul * weight; + wsum += 2.0 * weight; + } + return csum / wsum; +} + +//!HOOK MAIN +//!BIND PASS0 +//!BIND PASS1 +//!DESC unsharp mask pass2 + +//////////////////////////////////////////////////////////////////////// +// USER CONFIGURABLE, PASS 2 (blur in x axis and aply unsharp mask) +// +// CAUTION! probably should use the same settings for "USER CONFIGURABLE, PASS 1" above +// +#define SIGMA 1.0 //blur spread or amount, (0.0, 10+] +#define RADIUS 3.0 //kernel radius (integer as float, e.g. 3.0), (0.0, 10+]; probably should set it to ceil(3 * SIGMA) +// +//sharpnes +#define AMOUNT 0.5 //amount of sharpening [0.0, 10+] +#define THRESHOLD 0.0 //sets the minimum contrast for sharpening (e.g. 0.1), [0.0, 1.0] +// +//////////////////////////////////////////////////////////////////////// + +#define get_weight(x) (exp(-x * x / (2.0 * SIGMA * SIGMA))) + +vec4 hook() { + float weight; + vec4 csum = textureLod(PASS1_raw, PASS1_pos, 0.0) * PASS1_mul; + float wsum = 1.0; + for(float i = 1.0; i <= RADIUS; ++i) { + weight = get_weight(i); + csum += (textureLod(PASS1_raw, PASS1_pos + PASS1_pt * vec2(-i, 0.0), 0.0) + textureLod(PASS1_raw, PASS1_pos + PASS1_pt * vec2(i, 0.0), 0.0)) * PASS1_mul * weight; + wsum += 2.0 * weight; + } + vec4 original = textureLod(PASS0_raw, PASS0_pos, 0.0) * PASS0_mul; + vec4 mask = original - csum / wsum; + if (abs(mask.r) > THRESHOLD || abs(mask.g) > THRESHOLD || abs(mask.b) > THRESHOLD) + return delinearize(original + mask * AMOUNT); + return delinearize(original); +}