From 054bef361c1b7c8f910b7b15e17b1a13612a6015 Mon Sep 17 00:00:00 2001 From: "evan.liu" <583229071@qq.com> Date: Sat, 3 Oct 2026 10:12:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E9=80=81Ai=E6=BC=8F=E6=8E=89=E7=9A=84?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/HUD_Overlay_BlackKey.lua | 406 ++++++++++++ ...tline.lua => HUD_Overlay_Outline-V1.0.lua} | 111 +++- deploy/HUD_Overlay_Outline-V2.lua | 605 ++++++++++++++++++ 3 files changed, 1093 insertions(+), 29 deletions(-) create mode 100644 deploy/HUD_Overlay_BlackKey.lua rename deploy/{HUD_Overlay_Outline.lua => HUD_Overlay_Outline-V1.0.lua} (74%) create mode 100644 deploy/HUD_Overlay_Outline-V2.lua diff --git a/deploy/HUD_Overlay_BlackKey.lua b/deploy/HUD_Overlay_BlackKey.lua new file mode 100644 index 0000000..bc00694 --- /dev/null +++ b/deploy/HUD_Overlay_BlackKey.lua @@ -0,0 +1,406 @@ +-- HUD Overlay Black-Key Script for OBS (separate Create / Update buttons) +-- Black-to-transparent keying: black pixels become fully transparent, brightness is used as alpha +-- Principle: alpha = max(R, G, B); pure black -> alpha 0 (transparent), the brighter the pixel the more opaque +-- For a "pure green on pure black" HUD this is simpler and more stable than greenness keying: +-- no green judgment at all, so anti-aliased edges and dim segments key out naturally. +-- Requires: obs-shaderfilter plugin (installed) +-- Install: OBS -> Tools -> Scripts -> + -> select this file -> pick a mode +-- "Create Scene" : builds the scene from scratch (deletes and recreates both media sources) +-- "Update Scene" : updates the existing scene in place (video paths, color, keying parameters) + +obs = obslua + +local SETTING_FRONT_PATH = "front_path" +local SETTING_BACK_PATH = "back_path" +local SETTING_COLOR = "hud_color" -- preset color index (1 = keep original green) +local SETTING_BLACK_LOW = "black_low" +local SETTING_GAIN = "alpha_gain" +local SETTING_OPACITY = "opacity" + +local SCENE_NAME = "HUD Overlay Black Key" +local FRONT_NAME = "HUD - Front BlackKey" +local BACK_NAME = "Center IG - Back BlackKey" +local FILTER_NAME = "Black Key" + +-- Preset colors; index 1 keeps the original green from the video +local COLOR_PRESETS = { + { name = "Keep original color (video green)", r = 0.0, g = 1.0, b = 0.0, original = true }, + { name = "Red (recommended, complementary to teal)", r = 1.0, g = 0.0, b = 0.0, original = false }, + { name = "Magenta", r = 1.0, g = 0.0, b = 1.0, original = false }, + { name = "Orange", r = 1.0, g = 0.5, b = 0.0, original = false }, + { name = "Yellow", r = 1.0, g = 1.0, b = 0.0, original = false }, + { name = "White", r = 1.0, g = 1.0, b = 1.0, original = false }, + { name = "Bright green (neon green)", r = 0.25, g = 1.0, b = 0.1, original = false }, +} + +local front_path = "" +local back_path = "" +local color_index = 1 +local black_low = 0.0 +local alpha_gain = 1.0 +local opacity = 1.0 + +local fit_retries = 0 + +-- Black-to-transparent keying shader: +-- k = max(R, G, B); black -> 0, pure green -> 1.0 +-- alpha = rescale k above black_low, then apply gain; soft alpha preserves anti-aliased edges +-- Output is premultiplied: either the original pixel color or the specified HUD color +local SHADER_TEXT = [==[ +uniform float black_low < + string label = "Black threshold"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 0.5; + float step = 0.001; +> = 0.0; + +uniform float alpha_gain < + string label = "Alpha gain"; + string widget_type = "slider"; + float minimum = 0.5; + float maximum = 4.0; + float step = 0.01; +> = 1.0; + +uniform bool use_original < + string label = "Keep original color"; +> = true; + +uniform float hud_r < + string label = "HUD color R"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 0.0; + +uniform float hud_g < + string label = "HUD color G"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 1.0; + +uniform float hud_b < + string label = "HUD color B"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 0.0; + +uniform float opacity < + string label = "Opacity"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 1.0; + +float4 mainImage(VertData v_in) : TARGET +{ + float4 px = image.Sample(textureSampler, v_in.uv); + + // The OBS filter chain input is premultiplied; un-premultiply first (media sources have alpha=1, so usually no effect) + px.rgb *= (px.a > 0.0) ? (1.0 / px.a) : 0.0; + + // Brightness key: max channel is 0 for black, 1.0 for pure green + float k = max(px.r, max(px.g, px.b)); + + // Everything below black_low is fully transparent; rescale the rest back to [0, 1] + float a = clamp((k - black_low) / max(1.0 - black_low, 0.001), 0.0, 1.0); + a = clamp(a * alpha_gain, 0.0, 1.0) * opacity; + + float3 col = use_original ? px.rgb : float3(hud_r, hud_g, hud_b); + + // Premultiplied output: color x alpha + return float4(col * a, a); +} +]==] + +------------------------------------------------------------ +-- Utility functions +------------------------------------------------------------ + +local function remove_source_by_name(name) + local s = obs.obs_get_source_by_name(name) + if s ~= nil then + obs.obs_source_remove(s) + obs.obs_source_release(s) + end +end + +local function create_media_source(name, path) + local s = obs.obs_data_create() + obs.obs_data_set_string(s, "local_file", path) + obs.obs_data_set_bool(s, "is_local_file", true) + obs.obs_data_set_bool(s, "looping", true) + obs.obs_data_set_bool(s, "restart_on_activate", true) + obs.obs_data_set_bool(s, "close_when_inactive", false) + local src = obs.obs_source_create("ffmpeg_source", name, s, nil) + obs.obs_data_release(s) + return src +end + +-- Point an existing media source at a new file without recreating it +local function update_media_source_path(src, path) + local s = obs.obs_source_get_settings(src) + obs.obs_data_set_string(s, "local_file", path) + obs.obs_source_update(src, s) + obs.obs_data_release(s) +end + +local function apply_key_filter(src) + local old = obs.obs_source_get_filter_by_name(src, FILTER_NAME) + if old ~= nil then + obs.obs_source_filter_remove(src, old) + obs.obs_source_release(old) + end + + local preset = COLOR_PRESETS[color_index] or COLOR_PRESETS[1] + + local fs = obs.obs_data_create() + obs.obs_data_set_bool(fs, "from_file", false) + obs.obs_data_set_string(fs, "shader_text", SHADER_TEXT) + obs.obs_data_set_double(fs, "black_low", black_low) + obs.obs_data_set_double(fs, "alpha_gain", alpha_gain) + obs.obs_data_set_bool(fs, "use_original", preset.original) + obs.obs_data_set_double(fs, "hud_r", preset.r) + obs.obs_data_set_double(fs, "hud_g", preset.g) + obs.obs_data_set_double(fs, "hud_b", preset.b) + obs.obs_data_set_double(fs, "opacity", opacity) + + -- First check whether the obs-shaderfilter plugin is loaded + if obs.obs_source_get_display_name("shader_filter") == nil then + obs.obs_data_release(fs) + obs.script_log(obs.LOG_WARNING, + "HUD Overlay Black Key: obs-shaderfilter plugin not detected; keying was not applied. Please fully quit OBS (including the system tray) and restart it.") + return + end + + local f = obs.obs_source_create("shader_filter", FILTER_NAME, fs, nil) + obs.obs_data_release(fs) + + if f ~= nil then + obs.obs_source_filter_add(src, f) + obs.obs_source_release(f) + end +end + +local function fit_items_to_canvas() + local vi = obs.obs_video_info() + if obs.obs_get_video_info(vi) == false then + return false + end + local cw, ch = vi.base_width, vi.base_height + + local scene_src = obs.obs_get_source_by_name(SCENE_NAME) + if scene_src == nil then + return false + end + local scene = obs.obs_scene_from_source(scene_src) + local items = obs.obs_scene_enum_items(scene) + local all_ready = true + + for _, item in ipairs(items) do + local s = obs.obs_sceneitem_get_source(item) + local w = obs.obs_source_get_width(s) + local h = obs.obs_source_get_height(s) + if w > 0 and h > 0 then + local scale = math.min(cw / w, ch / h) + local sc = obs.vec2() + sc.x = scale + sc.y = scale + obs.obs_sceneitem_set_scale(item, sc) + local pos = obs.vec2() + pos.x = (cw - w * scale) / 2 + pos.y = (ch - h * scale) / 2 + obs.obs_sceneitem_set_pos(item, pos) + -- Use Lanczos when upscaling line-art footage; sharper than the default bilinear + if obs.obs_sceneitem_set_scale_filter ~= nil then + obs.obs_sceneitem_set_scale_filter(item, obs.OBS_SCALE_LANCZOS or 4) + end + else + all_ready = false + end + end + + obs.sceneitem_list_release(items) + obs.obs_source_release(scene_src) + return all_ready +end + +local function fit_retry() + fit_retries = fit_retries - 1 + if fit_items_to_canvas() or fit_retries <= 0 then + obs.timer_remove(fit_retry) + end +end + +local function schedule_fit() + fit_retries = 10 + obs.timer_remove(fit_retry) + obs.timer_add(fit_retry, 500) +end + +------------------------------------------------------------ +-- Create scene (from scratch) +------------------------------------------------------------ + +local function create_scene() + if front_path == "" or back_path == "" then + obs.script_log(obs.LOG_WARNING, "HUD Overlay Black Key: please set the foreground and background video paths first") + return + end + + local scene = nil + local scene_src = obs.obs_get_source_by_name(SCENE_NAME) + if scene_src ~= nil then + scene = obs.obs_scene_from_source(scene_src) + else + scene = obs.obs_scene_create(SCENE_NAME) + scene_src = obs.obs_scene_get_source(scene) + end + + remove_source_by_name(FRONT_NAME) + remove_source_by_name(BACK_NAME) + + local back = create_media_source(BACK_NAME, back_path) + obs.obs_scene_add(scene, back) + + local front = create_media_source(FRONT_NAME, front_path) + apply_key_filter(front) + obs.obs_scene_add(scene, front) + + obs.obs_source_release(back) + obs.obs_source_release(front) + + obs.obs_frontend_set_current_scene(scene_src) + obs.obs_source_release(scene_src) + + schedule_fit() + + obs.script_log(obs.LOG_INFO, "HUD Overlay Black Key: scene created, symbol color = " .. + (COLOR_PRESETS[color_index] or COLOR_PRESETS[1]).name) +end + +------------------------------------------------------------ +-- Update scene (in place, without recreating sources) +------------------------------------------------------------ + +local function update_scene() + if front_path == "" or back_path == "" then + obs.script_log(obs.LOG_WARNING, "HUD Overlay Black Key: please set the foreground and background video paths first") + return + end + + local scene_src = obs.obs_get_source_by_name(SCENE_NAME) + if scene_src == nil then + obs.script_log(obs.LOG_WARNING, + "HUD Overlay Black Key: scene \"" .. SCENE_NAME .. "\" does not exist yet; please use \"Create Scene\" first") + return + end + + local back = obs.obs_get_source_by_name(BACK_NAME) + local front = obs.obs_get_source_by_name(FRONT_NAME) + if back == nil or front == nil then + if back ~= nil then obs.obs_source_release(back) end + if front ~= nil then obs.obs_source_release(front) end + obs.obs_source_release(scene_src) + obs.script_log(obs.LOG_WARNING, + "HUD Overlay Black Key: media sources are missing; please use \"Create Scene\" to rebuild the scene") + return + end + + -- Update video files in place + update_media_source_path(back, back_path) + update_media_source_path(front, front_path) + + -- Re-apply the keying filter with the current color / keying parameters + apply_key_filter(front) + + obs.obs_source_release(back) + obs.obs_source_release(front) + + obs.obs_frontend_set_current_scene(scene_src) + obs.obs_source_release(scene_src) + + schedule_fit() + + obs.script_log(obs.LOG_INFO, "HUD Overlay Black Key: scene updated, symbol color = " .. + (COLOR_PRESETS[color_index] or COLOR_PRESETS[1]).name) +end + +local function on_create_clicked(props, p) + create_scene() + return true +end + +local function on_update_clicked(props, p) + update_scene() + return true +end + +------------------------------------------------------------ +-- Script UI and settings +------------------------------------------------------------ + +function script_description() + return "Black-to-transparent keying: black pixels become fully transparent; pixel brightness (max of R/G/B) is used as alpha.\n" .. + "For a 'pure green on pure black' HUD this is simpler and more stable than greenness keying; edges and dim segments key out naturally.\n" .. + "\"Create Scene\": builds the scene from scratch (deletes and recreates both media sources).\n" .. + "\"Update Scene\": updates the existing scene in place (video paths, symbol color, keying parameters).\n" .. + "The filter panel also has Black threshold / Alpha gain / HUD color sliders for fine-tuning." +end + +function script_properties() + local props = obs.obs_properties_create() + + obs.obs_properties_add_path(props, SETTING_FRONT_PATH, + "Foreground video (HUD, green symbols on black)", obs.OBS_PATH_FILE, + "Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "") + + obs.obs_properties_add_path(props, SETTING_BACK_PATH, + "Background video (bottom layer)", obs.OBS_PATH_FILE, + "Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "") + + local clist = obs.obs_properties_add_list(props, SETTING_COLOR, + "Symbol color", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT) + for i, c in ipairs(COLOR_PRESETS) do + obs.obs_property_list_add_int(clist, c.name, i) + end + + obs.obs_properties_add_float_slider(props, SETTING_BLACK_LOW, + "Black threshold (increase if dark noise remains around the symbols)", 0.0, 0.5, 0.001) + obs.obs_properties_add_float_slider(props, SETTING_GAIN, + "Alpha gain (increase if the symbols look too transparent)", 0.5, 4.0, 0.01) + obs.obs_properties_add_float_slider(props, SETTING_OPACITY, + "Overall opacity", 0.0, 1.0, 0.01) + + obs.obs_properties_add_button(props, "create_btn", "Create Scene", on_create_clicked) + obs.obs_properties_add_button(props, "update_btn", "Update Scene", on_update_clicked) + + return props +end + +function script_defaults(settings) + obs.obs_data_set_default_string(settings, SETTING_FRONT_PATH, + "D:\\Test Pictures + Video\\HUD - Front Video.avi") + obs.obs_data_set_default_string(settings, SETTING_BACK_PATH, + "D:\\Test Pictures + Video\\Center IG - Back Videos.avi") + obs.obs_data_set_default_int(settings, SETTING_COLOR, 1) + obs.obs_data_set_default_double(settings, SETTING_BLACK_LOW, 0.0) + obs.obs_data_set_default_double(settings, SETTING_GAIN, 1.0) + obs.obs_data_set_default_double(settings, SETTING_OPACITY, 1.0) +end + +function script_update(settings) + front_path = obs.obs_data_get_string(settings, SETTING_FRONT_PATH) + back_path = obs.obs_data_get_string(settings, SETTING_BACK_PATH) + color_index = obs.obs_data_get_int(settings, SETTING_COLOR) + black_low = obs.obs_data_get_double(settings, SETTING_BLACK_LOW) + alpha_gain = obs.obs_data_get_double(settings, SETTING_GAIN) + opacity = obs.obs_data_get_double(settings, SETTING_OPACITY) +end diff --git a/deploy/HUD_Overlay_Outline.lua b/deploy/HUD_Overlay_Outline-V1.0.lua similarity index 74% rename from deploy/HUD_Overlay_Outline.lua rename to deploy/HUD_Overlay_Outline-V1.0.lua index f3d2d18..487c422 100644 --- a/deploy/HUD_Overlay_Outline.lua +++ b/deploy/HUD_Overlay_Outline-V1.0.lua @@ -1,12 +1,13 @@ -- HUD Overlay Outline Script for OBS (separate Create / Update buttons) --- New approach: detect the green symbols first, then draw them in pure green with a --- dark green outline, so they stay readable over ANY background (bright sky, green terrain, clouds). +-- New approach: detect the green symbols first, then draw them in a configurable +-- color (preset list x brightness), so they stay readable over ANY background +-- (bright sky, green terrain, clouds). -- Principle: -- 1. symbol detection: subtract a black level, alpha = max(R, G, B) x gain, -- multiplied by a greenness factor (G - max(R, B)) to reject gray/white noise --- 2. outline: sample the symbol alpha in a ring around each pixel; pixels that are --- near a symbol but not part of it get a dark outline underneath the green fill --- 3. premultiplied output: pure green x symbol alpha + outline color x outline alpha +-- 2. optional outline: sample the symbol alpha in a ring around each pixel; pixels +-- near a symbol get the same color (x outline brightness) underneath the fill +-- 3. premultiplied output: symbol color x symbol alpha + outline color x outline alpha -- Requires: obs-shaderfilter plugin (installed) -- Install: OBS -> Tools -> Scripts -> + -> select this file -- "Create Scene" : builds the scene from scratch (deletes and recreates both media sources) @@ -16,6 +17,8 @@ obs = obslua local SETTING_FRONT_PATH = "front_path" local SETTING_BACK_PATH = "back_path" +local SETTING_COLOR = "hud_color" -- preset color index +local SETTING_BRIGHTNESS = "symbol_brightness" local SETTING_BLACK_LEVEL = "black_level" local SETTING_GAIN = "alpha_gain" local SETTING_GREEN_THRESH = "green_thresh" @@ -28,12 +31,27 @@ local FRONT_NAME = "HUD - Front Outline" local BACK_NAME = "Center IG - Back Outline" local FILTER_NAME = "HUD Outline" +-- Preset symbol colors at full brightness; the Brightness slider scales the chosen color +local COLOR_PRESETS = { + { name = "Pure green", r = 0.0, g = 1.0, b = 0.0 }, + { name = "HUD green (measured from source video)", r = 0.15, g = 1.0, b = 0.10 }, + { name = "Bright green (neon green)", r = 0.25, g = 1.0, b = 0.1 }, + { name = "Red (complementary to teal sky)", r = 1.0, g = 0.0, b = 0.0 }, + { name = "Magenta", r = 1.0, g = 0.0, b = 1.0 }, + { name = "Orange", r = 1.0, g = 0.5, b = 0.0 }, + { name = "Yellow", r = 1.0, g = 1.0, b = 0.0 }, + { name = "White", r = 1.0, g = 1.0, b = 1.0 }, + { name = "Black", r = 0.0, g = 0.0, b = 0.0 }, +} + local front_path = "" local back_path = "" +local color_index = 1 +local symbol_brightness = 1.0 local black_level = 0.055 -- 14/255, measured noise floor of the HUD capture local alpha_gain = 1.35 local green_thresh = 0.10 -- greenness (G - max(R,B)) needed to count as a green symbol -local outline_width = 2.0 -- outline thickness in source pixels +local outline_width = 0.0 -- stroke width adjustment in source pixels: + thicker, - thinner, 0 = original local outline_alpha = 0.9 local opacity = 1.0 @@ -68,12 +86,12 @@ uniform float green_thresh < > = 0.10; uniform float outline_width < - string label = "Outline width (px)"; + string label = "Symbol width (px, + thicker / - thinner)"; string widget_type = "slider"; - float minimum = 0.0; + float minimum = -4.0; float maximum = 8.0; float step = 0.5; -> = 2.0; +> = 0.0; uniform float outline_alpha < string label = "Outline strength"; @@ -113,7 +131,7 @@ uniform float outline_brightness < float minimum = 0.0; float maximum = 1.0; float step = 0.01; -> = 0.35; +> = 1.0; uniform float opacity < string label = "Opacity"; @@ -146,26 +164,42 @@ float4 mainImage(VertData v_in) : TARGET { float a_sym = sym_alpha(v_in.uv); - // Ring of 8 neighbor taps at outline_width distance -> dilated symbol alpha - float2 d = uv_pixel_interval * outline_width; + // Symbol width: positive = dilate (thicker strokes), negative = erode (thinner) + float2 d = uv_pixel_interval * abs(outline_width); float2 dg = d * 0.7071; - float ring = 0.0; - ring = max(ring, sym_alpha(v_in.uv + float2( d.x, 0.0))); - ring = max(ring, sym_alpha(v_in.uv + float2(-d.x, 0.0))); - ring = max(ring, sym_alpha(v_in.uv + float2( 0.0, d.y))); - ring = max(ring, sym_alpha(v_in.uv + float2( 0.0, -d.y))); - ring = max(ring, sym_alpha(v_in.uv + float2( dg.x, dg.y))); - ring = max(ring, sym_alpha(v_in.uv + float2(-dg.x, dg.y))); - ring = max(ring, sym_alpha(v_in.uv + float2( dg.x, -dg.y))); - ring = max(ring, sym_alpha(v_in.uv + float2(-dg.x, -dg.y))); + float a_out = 0.0; - // Outline only where the symbol itself is not already opaque - float a_out = min(ring * outline_alpha, 1.0 - a_sym); + if (outline_width > 0.0) { + // Ring max -> same-color outline under the fill = thicker strokes + float ring = 0.0; + ring = max(ring, sym_alpha(v_in.uv + float2( d.x, 0.0))); + ring = max(ring, sym_alpha(v_in.uv + float2(-d.x, 0.0))); + ring = max(ring, sym_alpha(v_in.uv + float2( 0.0, d.y))); + ring = max(ring, sym_alpha(v_in.uv + float2( 0.0, -d.y))); + ring = max(ring, sym_alpha(v_in.uv + float2( dg.x, dg.y))); + ring = max(ring, sym_alpha(v_in.uv + float2(-dg.x, dg.y))); + ring = max(ring, sym_alpha(v_in.uv + float2( dg.x, -dg.y))); + ring = max(ring, sym_alpha(v_in.uv + float2(-dg.x, -dg.y))); + // Outline only where the symbol itself is not already opaque + a_out = min(ring * outline_alpha, 1.0 - a_sym); + } else if (outline_width < 0.0) { + // Ring min -> erode the strokes thinner + float core = 1.0; + core = min(core, sym_alpha(v_in.uv + float2( d.x, 0.0))); + core = min(core, sym_alpha(v_in.uv + float2(-d.x, 0.0))); + core = min(core, sym_alpha(v_in.uv + float2( 0.0, d.y))); + core = min(core, sym_alpha(v_in.uv + float2( 0.0, -d.y))); + core = min(core, sym_alpha(v_in.uv + float2( dg.x, dg.y))); + core = min(core, sym_alpha(v_in.uv + float2(-dg.x, dg.y))); + core = min(core, sym_alpha(v_in.uv + float2( dg.x, -dg.y))); + core = min(core, sym_alpha(v_in.uv + float2(-dg.x, -dg.y))); + a_sym = min(a_sym, core); + } float a_sym_o = a_sym * opacity; float a_out_o = a_out * opacity; - // Premultiplied: pure symbol color over a dimmer pure-green outline + // Premultiplied: symbol color over a same-color outline (x outline brightness) float3 rgb = float3(hud_r, hud_g, hud_b) * a_sym_o + float3(hud_r, hud_g, hud_b) * outline_brightness * a_out_o; return float4(rgb, a_sym_o + a_out_o); @@ -211,9 +245,14 @@ local function apply_key_filter(src) obs.obs_source_release(old) end + local preset = COLOR_PRESETS[color_index] or COLOR_PRESETS[1] + local fs = obs.obs_data_create() obs.obs_data_set_bool(fs, "from_file", false) obs.obs_data_set_string(fs, "shader_text", SHADER_TEXT) + obs.obs_data_set_double(fs, "hud_r", preset.r * symbol_brightness) + obs.obs_data_set_double(fs, "hud_g", preset.g * symbol_brightness) + obs.obs_data_set_double(fs, "hud_b", preset.b * symbol_brightness) obs.obs_data_set_double(fs, "black_level", black_level) obs.obs_data_set_double(fs, "alpha_gain", alpha_gain) obs.obs_data_set_double(fs, "green_thresh", green_thresh) @@ -395,12 +434,14 @@ end ------------------------------------------------------------ function script_description() - return "Detects the green HUD symbols, then draws them in pure green with a dark green outline,\n" .. + return "Detects the green HUD symbols and draws them in a configurable color and brightness,\n" .. "so they stay readable over any background (bright sky, green terrain, clouds).\n" .. "Symbol detection: alpha = max(R, G, B) x gain after black-level subtraction, gated by greenness G - max(R, B) to reject noise.\n" .. + "\"Symbol color\" picks the hue, \"Symbol brightness\" scales it (0.35 = dark green, 1.0 = full brightness).\n" .. + "\"Symbol width\" thickens (positive) or thins (negative) the strokes; 0 keeps the original width.\n" .. "\"Create Scene\": builds the scene from scratch (deletes and recreates both media sources).\n" .. - "\"Update Scene\": updates the existing scene in place (video paths, keying parameters).\n" .. - "The filter panel has sliders for symbol color, outline width, strength and brightness." + "\"Update Scene\": updates the existing scene in place (video paths, color, keying parameters).\n" .. + "The filter panel has sliders for fine-tuning (symbol color R/G/B, outline width, strength and brightness)." end function script_properties() @@ -414,6 +455,14 @@ function script_properties() "Background video (bottom layer)", obs.OBS_PATH_FILE, "Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "") + local clist = obs.obs_properties_add_list(props, SETTING_COLOR, + "Symbol color", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT) + for i, c in ipairs(COLOR_PRESETS) do + obs.obs_property_list_add_int(clist, c.name, i) + end + + obs.obs_properties_add_float_slider(props, SETTING_BRIGHTNESS, + "Symbol brightness (scales the chosen color)", 0.05, 1.0, 0.01) obs.obs_properties_add_float_slider(props, SETTING_BLACK_LEVEL, "Black level (increase if a dark halo/noise remains)", 0.0, 0.3, 0.001) obs.obs_properties_add_float_slider(props, SETTING_GAIN, @@ -421,7 +470,7 @@ function script_properties() obs.obs_properties_add_float_slider(props, SETTING_GREEN_THRESH, "Green threshold (increase if non-green noise appears)", 0.01, 0.5, 0.005) obs.obs_properties_add_float_slider(props, SETTING_OUT_WIDTH, - "Outline width (px)", 0.0, 8.0, 0.5) + "Symbol width (px, + thicker / - thinner, 0 = original)", -4.0, 8.0, 0.5) obs.obs_properties_add_float_slider(props, SETTING_OUT_ALPHA, "Outline strength", 0.0, 1.0, 0.01) obs.obs_properties_add_float_slider(props, SETTING_OPACITY, @@ -438,10 +487,12 @@ function script_defaults(settings) "D:\\Test Pictures + Video\\HUD - Front Video.avi") obs.obs_data_set_default_string(settings, SETTING_BACK_PATH, "D:\\Test Pictures + Video\\Center IG - Back Videos.avi") + obs.obs_data_set_default_int(settings, SETTING_COLOR, 1) + obs.obs_data_set_default_double(settings, SETTING_BRIGHTNESS, 1.0) obs.obs_data_set_default_double(settings, SETTING_BLACK_LEVEL, 0.055) obs.obs_data_set_default_double(settings, SETTING_GAIN, 1.35) obs.obs_data_set_default_double(settings, SETTING_GREEN_THRESH, 0.10) - obs.obs_data_set_default_double(settings, SETTING_OUT_WIDTH, 2.0) + obs.obs_data_set_default_double(settings, SETTING_OUT_WIDTH, 0.0) obs.obs_data_set_default_double(settings, SETTING_OUT_ALPHA, 0.9) obs.obs_data_set_default_double(settings, SETTING_OPACITY, 1.0) end @@ -449,6 +500,8 @@ end function script_update(settings) front_path = obs.obs_data_get_string(settings, SETTING_FRONT_PATH) back_path = obs.obs_data_get_string(settings, SETTING_BACK_PATH) + color_index = obs.obs_data_get_int(settings, SETTING_COLOR) + symbol_brightness = obs.obs_data_get_double(settings, SETTING_BRIGHTNESS) black_level = obs.obs_data_get_double(settings, SETTING_BLACK_LEVEL) alpha_gain = obs.obs_data_get_double(settings, SETTING_GAIN) green_thresh = obs.obs_data_get_double(settings, SETTING_GREEN_THRESH) diff --git a/deploy/HUD_Overlay_Outline-V2.lua b/deploy/HUD_Overlay_Outline-V2.lua new file mode 100644 index 0000000..ac4740d --- /dev/null +++ b/deploy/HUD_Overlay_Outline-V2.lua @@ -0,0 +1,605 @@ +-- HUD Overlay Outline Script for OBS (separate Create / Update buttons) +-- New approach: detect the green symbols first, then draw them in a configurable +-- color (preset list x brightness), so they stay readable over ANY background +-- (bright sky, green terrain, clouds). +-- Principle: +-- 1. symbol detection: subtract a black level, alpha = max(R, G, B) x gain, +-- multiplied by a greenness factor (G - max(R, B)) to reject gray/white noise +-- 2. optional outline: sample the symbol alpha in a ring around each pixel; pixels +-- near a symbol get the outline color (default blue, x outline brightness) +-- underneath the fill +-- 3. premultiplied output: symbol color x symbol alpha + outline color x outline alpha +-- Requires: obs-shaderfilter plugin (installed) +-- Install: OBS -> Tools -> Scripts -> + -> select this file +-- "Create Scene" : builds the scene from scratch (deletes and recreates both media sources) +-- "Update Scene" : updates the existing scene in place (video paths, color, keying parameters) + +obs = obslua + +local SETTING_FRONT_PATH = "front_path" +local SETTING_BACK_PATH = "back_path" +local SETTING_COLOR = "hud_color" -- preset color index +local SETTING_BRIGHTNESS = "symbol_brightness" +local SETTING_BLACK_LEVEL = "black_level" +local SETTING_GAIN = "alpha_gain" +local SETTING_GREEN_THRESH = "green_thresh" +local SETTING_OUT_WIDTH = "outline_width" +local SETTING_OUT_ALPHA = "outline_alpha" +local SETTING_OUT_COLOR = "outline_color" -- outline preset color index (1 = blue) +local SETTING_OPACITY = "opacity" + +local SCENE_NAME = "HUD Overlay Outline" +local FRONT_NAME = "HUD - Front Outline" +local BACK_NAME = "Center IG - Back Outline" +local FILTER_NAME = "HUD Outline" + +-- Preset symbol colors at full brightness; the Brightness slider scales the chosen color +local COLOR_PRESETS = { + { name = "Pure green", r = 0.0, g = 1.0, b = 0.0 }, + { name = "HUD green (measured from source video)", r = 0.15, g = 1.0, b = 0.10 }, + { name = "Bright green (neon green)", r = 0.25, g = 1.0, b = 0.1 }, + { name = "Red (complementary to teal sky)", r = 1.0, g = 0.0, b = 0.0 }, + { name = "Magenta", r = 1.0, g = 0.0, b = 1.0 }, + { name = "Orange", r = 1.0, g = 0.5, b = 0.0 }, + { name = "Yellow", r = 1.0, g = 1.0, b = 0.0 }, + { name = "White", r = 1.0, g = 1.0, b = 1.0 }, + { name = "Black", r = 0.0, g = 0.0, b = 0.0 }, +} + +-- Preset outline colors; index 1 is blue (the outline default) +local OUTLINE_COLOR_PRESETS = { + { name = "Blue", r = 0.0, g = 0.0, b = 1.0 }, + { name = "Green", r = 0.0, g = 1.0, b = 0.0 }, + { name = "Cyan", r = 0.0, g = 1.0, b = 1.0 }, + { name = "White", r = 1.0, g = 1.0, b = 1.0 }, + { name = "Black", r = 0.0, g = 0.0, b = 0.0 }, + { name = "Red", r = 1.0, g = 0.0, b = 0.0 }, + { name = "Yellow", r = 1.0, g = 1.0, b = 0.0 }, +} + +local front_path = "" +local back_path = "" +local color_index = 1 +local outline_color_index = 1 +local symbol_brightness = 1.0 +local black_level = 0.055 -- 14/255, measured noise floor of the HUD capture +local alpha_gain = 1.35 +local green_thresh = 0.10 -- greenness (G - max(R,B)) needed to count as a green symbol +local outline_width = 0.0 -- stroke width adjustment in source pixels: + thicker, - thinner, 0 = original +local outline_alpha = 0.9 +local opacity = 1.0 + +local fit_retries = 0 + +-- Symbol-detect + outline shader: +-- sym_alpha(uv): black level subtraction -> brightness key x gain -> greenness gate +-- main: symbol = pure green x sym_alpha; ring of neighbor sym_alpha forms the outline +local SHADER_TEXT = [==[ +uniform float black_level < + string label = "Black level"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 0.3; + float step = 0.001; +> = 0.055; + +uniform float alpha_gain < + string label = "Alpha gain"; + string widget_type = "slider"; + float minimum = 0.5; + float maximum = 4.0; + float step = 0.01; +> = 1.35; + +uniform float green_thresh < + string label = "Green threshold"; + string widget_type = "slider"; + float minimum = 0.01; + float maximum = 0.5; + float step = 0.005; +> = 0.10; + +uniform float outline_width < + string label = "Symbol width (px, + thicker / - thinner)"; + string widget_type = "slider"; + float minimum = -4.0; + float maximum = 8.0; + float step = 0.5; +> = 0.0; + +uniform float outline_alpha < + string label = "Outline strength"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 0.9; + +uniform float hud_r < + string label = "Symbol color R"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 0.0; + +uniform float hud_g < + string label = "Symbol color G"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 1.0; + +uniform float hud_b < + string label = "Symbol color B"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 0.0; + +uniform float outline_brightness < + string label = "Outline brightness"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 1.0; + +uniform float outline_r < + string label = "Outline color R"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 0.0; + +uniform float outline_g < + string label = "Outline color G"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 0.0; + +uniform float outline_b < + string label = "Outline color B"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 1.0; + +uniform float opacity < + string label = "Opacity"; + string widget_type = "slider"; + float minimum = 0.0; + float maximum = 1.0; + float step = 0.01; +> = 1.0; + +// Symbol alpha at a given UV: 1 = solid green symbol, 0 = background/noise +float sym_alpha(float2 uv) +{ + float4 px = image.Sample(textureSampler, uv); + px.rgb *= (px.a > 0.0) ? (1.0 / px.a) : 0.0; + + // drop the codec noise floor; pure black becomes exactly 0 + float3 h = max(px.rgb - black_level, 0.0); + + // brightness key + float k = max(h.r, max(h.g, h.b)); + + // greenness gate: green symbols pass, gray/white noise is rejected + float gn = h.g - max(h.r, h.b); + float gf = clamp(gn / green_thresh, 0.0, 1.0); + + return clamp(k * alpha_gain, 0.0, 1.0) * gf; +} + +float4 mainImage(VertData v_in) : TARGET +{ + float a_sym = sym_alpha(v_in.uv); + + // Symbol width: positive = dilate (thicker strokes), negative = erode (thinner) + float2 d = uv_pixel_interval * abs(outline_width); + float2 dg = d * 0.7071; + float a_out = 0.0; + + if (outline_width > 0.0) { + // Ring max -> same-color outline under the fill = thicker strokes + float ring = 0.0; + ring = max(ring, sym_alpha(v_in.uv + float2( d.x, 0.0))); + ring = max(ring, sym_alpha(v_in.uv + float2(-d.x, 0.0))); + ring = max(ring, sym_alpha(v_in.uv + float2( 0.0, d.y))); + ring = max(ring, sym_alpha(v_in.uv + float2( 0.0, -d.y))); + ring = max(ring, sym_alpha(v_in.uv + float2( dg.x, dg.y))); + ring = max(ring, sym_alpha(v_in.uv + float2(-dg.x, dg.y))); + ring = max(ring, sym_alpha(v_in.uv + float2( dg.x, -dg.y))); + ring = max(ring, sym_alpha(v_in.uv + float2(-dg.x, -dg.y))); + // Outline only where the symbol itself is not already opaque + a_out = min(ring * outline_alpha, 1.0 - a_sym); + } else if (outline_width < 0.0) { + // Ring min -> erode the strokes thinner + float core = 1.0; + core = min(core, sym_alpha(v_in.uv + float2( d.x, 0.0))); + core = min(core, sym_alpha(v_in.uv + float2(-d.x, 0.0))); + core = min(core, sym_alpha(v_in.uv + float2( 0.0, d.y))); + core = min(core, sym_alpha(v_in.uv + float2( 0.0, -d.y))); + core = min(core, sym_alpha(v_in.uv + float2( dg.x, dg.y))); + core = min(core, sym_alpha(v_in.uv + float2(-dg.x, dg.y))); + core = min(core, sym_alpha(v_in.uv + float2( dg.x, -dg.y))); + core = min(core, sym_alpha(v_in.uv + float2(-dg.x, -dg.y))); + a_sym = min(a_sym, core); + } + + float a_sym_o = a_sym * opacity; + float a_out_o = a_out * opacity; + + // Premultiplied: symbol color over an outline in its own color (x outline brightness) + float3 rgb = float3(hud_r, hud_g, hud_b) * a_sym_o + + float3(outline_r, outline_g, outline_b) * outline_brightness * a_out_o; + return float4(rgb, a_sym_o + a_out_o); +} +]==] + +------------------------------------------------------------ +-- Utility functions +------------------------------------------------------------ + +-- Matches the exact base name or an OBS auto-numbered duplicate (" 2", " 3", ...) +local function source_name_matches(name, base) + if name == base then return true end + return name:sub(1, #base) == base and name:sub(#base + 1):match("^%s*%d+$") ~= nil +end + +-- Finds a source by base name, falling back to auto-numbered duplicates. +-- Returns nil or a reference the caller must release. +local function get_source_by_base_name(base) + local exact = obs.obs_get_source_by_name(base) + if exact ~= nil then return exact end + local found = nil + local sources = obs.obs_enum_sources() + if sources ~= nil then + for _, s in ipairs(sources) do + if source_name_matches(obs.obs_source_get_name(s), base) then + found = obs.obs_source_get_ref(s) + break + end + end + obs.source_list_release(sources) + end + return found +end + +-- Removes every source matching the base name, including duplicates left over +-- from earlier runs. The scene items are removed first so the sources are +-- destroyed immediately; otherwise OBS keeps them alive under the same name +-- and renames newly created sources to " 2", " 3", ... +local function remove_sources_by_base_name(scene, base) + local items = obs.obs_scene_enum_items(scene) + for _, item in ipairs(items) do + local s = obs.obs_sceneitem_get_source(item) + if s ~= nil and source_name_matches(obs.obs_source_get_name(s), base) then + obs.obs_sceneitem_remove(item) + end + end + obs.sceneitem_list_release(items) + + local sources = obs.obs_enum_sources() + if sources ~= nil then + for _, s in ipairs(sources) do + if source_name_matches(obs.obs_source_get_name(s), base) then + obs.obs_source_remove(s) + end + end + obs.source_list_release(sources) + end +end + +local function create_media_source(name, path) + local s = obs.obs_data_create() + obs.obs_data_set_string(s, "local_file", path) + obs.obs_data_set_bool(s, "is_local_file", true) + obs.obs_data_set_bool(s, "looping", true) + obs.obs_data_set_bool(s, "restart_on_activate", true) + obs.obs_data_set_bool(s, "close_when_inactive", false) + local src = obs.obs_source_create("ffmpeg_source", name, s, nil) + obs.obs_data_release(s) + return src +end + +-- Point an existing media source at a new file without recreating it +local function update_media_source_path(src, path) + local s = obs.obs_source_get_settings(src) + obs.obs_data_set_string(s, "local_file", path) + obs.obs_source_update(src, s) + obs.obs_data_release(s) +end + +local function apply_key_filter(src) + local old = obs.obs_source_get_filter_by_name(src, FILTER_NAME) + if old ~= nil then + obs.obs_source_filter_remove(src, old) + obs.obs_source_release(old) + end + + local preset = COLOR_PRESETS[color_index] or COLOR_PRESETS[1] + local outline_preset = OUTLINE_COLOR_PRESETS[outline_color_index] or OUTLINE_COLOR_PRESETS[1] + + local fs = obs.obs_data_create() + obs.obs_data_set_bool(fs, "from_file", false) + obs.obs_data_set_string(fs, "shader_text", SHADER_TEXT) + obs.obs_data_set_double(fs, "hud_r", preset.r * symbol_brightness) + obs.obs_data_set_double(fs, "hud_g", preset.g * symbol_brightness) + obs.obs_data_set_double(fs, "hud_b", preset.b * symbol_brightness) + obs.obs_data_set_double(fs, "outline_r", outline_preset.r) + obs.obs_data_set_double(fs, "outline_g", outline_preset.g) + obs.obs_data_set_double(fs, "outline_b", outline_preset.b) + obs.obs_data_set_double(fs, "black_level", black_level) + obs.obs_data_set_double(fs, "alpha_gain", alpha_gain) + obs.obs_data_set_double(fs, "green_thresh", green_thresh) + obs.obs_data_set_double(fs, "outline_width", outline_width) + obs.obs_data_set_double(fs, "outline_alpha", outline_alpha) + obs.obs_data_set_double(fs, "opacity", opacity) + + -- First check whether the obs-shaderfilter plugin is loaded + if obs.obs_source_get_display_name("shader_filter") == nil then + obs.obs_data_release(fs) + obs.script_log(obs.LOG_WARNING, + "HUD Overlay Outline: obs-shaderfilter plugin not detected; keying was not applied. Please fully quit OBS (including the system tray) and restart it.") + return + end + + local f = obs.obs_source_create("shader_filter", FILTER_NAME, fs, nil) + obs.obs_data_release(fs) + + if f ~= nil then + obs.obs_source_filter_add(src, f) + obs.obs_source_release(f) + end +end + +local function fit_items_to_canvas() + local vi = obs.obs_video_info() + if obs.obs_get_video_info(vi) == false then + return false + end + local cw, ch = vi.base_width, vi.base_height + + local scene_src = obs.obs_get_source_by_name(SCENE_NAME) + if scene_src == nil then + return false + end + local scene = obs.obs_scene_from_source(scene_src) + local items = obs.obs_scene_enum_items(scene) + local all_ready = true + + for _, item in ipairs(items) do + local s = obs.obs_sceneitem_get_source(item) + local w = obs.obs_source_get_width(s) + local h = obs.obs_source_get_height(s) + if w > 0 and h > 0 then + local scale = math.min(cw / w, ch / h) + local sc = obs.vec2() + sc.x = scale + sc.y = scale + obs.obs_sceneitem_set_scale(item, sc) + local pos = obs.vec2() + pos.x = (cw - w * scale) / 2 + pos.y = (ch - h * scale) / 2 + obs.obs_sceneitem_set_pos(item, pos) + -- Use Lanczos when upscaling line-art footage; sharper than the default bilinear + if obs.obs_sceneitem_set_scale_filter ~= nil then + obs.obs_sceneitem_set_scale_filter(item, obs.OBS_SCALE_LANCZOS or 4) + end + else + all_ready = false + end + end + + obs.sceneitem_list_release(items) + obs.obs_source_release(scene_src) + return all_ready +end + +local function fit_retry() + fit_retries = fit_retries - 1 + if fit_items_to_canvas() or fit_retries <= 0 then + obs.timer_remove(fit_retry) + end +end + +local function schedule_fit() + fit_retries = 10 + obs.timer_remove(fit_retry) + obs.timer_add(fit_retry, 500) +end + +------------------------------------------------------------ +-- Create scene (from scratch) +------------------------------------------------------------ + +local function create_scene() + if front_path == "" or back_path == "" then + obs.script_log(obs.LOG_WARNING, "HUD Overlay Outline: please set the foreground and background video paths first") + return + end + + local scene = nil + local scene_src = obs.obs_get_source_by_name(SCENE_NAME) + if scene_src ~= nil then + scene = obs.obs_scene_from_source(scene_src) + else + scene = obs.obs_scene_create(SCENE_NAME) + scene_src = obs.obs_scene_get_source(scene) + end + + remove_sources_by_base_name(scene, FRONT_NAME) + remove_sources_by_base_name(scene, BACK_NAME) + + local back = create_media_source(BACK_NAME, back_path) + obs.obs_scene_add(scene, back) + + local front = create_media_source(FRONT_NAME, front_path) + apply_key_filter(front) + obs.obs_scene_add(scene, front) + + obs.obs_source_release(back) + obs.obs_source_release(front) + + obs.obs_frontend_set_current_scene(scene_src) + obs.obs_source_release(scene_src) + + schedule_fit() + + obs.script_log(obs.LOG_INFO, "HUD Overlay Outline: scene created") +end + +------------------------------------------------------------ +-- Update scene (in place, without recreating sources) +------------------------------------------------------------ + +local function update_scene() + if front_path == "" or back_path == "" then + obs.script_log(obs.LOG_WARNING, "HUD Overlay Outline: please set the foreground and background video paths first") + return + end + + local scene_src = obs.obs_get_source_by_name(SCENE_NAME) + if scene_src == nil then + obs.script_log(obs.LOG_WARNING, + "HUD Overlay Outline: scene \"" .. SCENE_NAME .. "\" does not exist yet; please use \"Create Scene\" first") + return + end + + local back = get_source_by_base_name(BACK_NAME) + local front = get_source_by_base_name(FRONT_NAME) + if back == nil or front == nil then + if back ~= nil then obs.obs_source_release(back) end + if front ~= nil then obs.obs_source_release(front) end + obs.obs_source_release(scene_src) + obs.script_log(obs.LOG_WARNING, + "HUD Overlay Outline: media sources are missing; please use \"Create Scene\" to rebuild the scene") + return + end + + -- Update video files in place + update_media_source_path(back, back_path) + update_media_source_path(front, front_path) + + -- Re-apply the keying filter with the current keying parameters + apply_key_filter(front) + + obs.obs_source_release(back) + obs.obs_source_release(front) + + obs.obs_frontend_set_current_scene(scene_src) + obs.obs_source_release(scene_src) + + schedule_fit() + + obs.script_log(obs.LOG_INFO, "HUD Overlay Outline: scene updated") +end + +local function on_create_clicked(props, p) + create_scene() + return true +end + +local function on_update_clicked(props, p) + update_scene() + return true +end + +------------------------------------------------------------ +-- Script UI and settings +------------------------------------------------------------ + +function script_description() + return "Detects the green HUD symbols and draws them in a configurable color and brightness,\n" .. + "so they stay readable over any background (bright sky, green terrain, clouds).\n" .. + "Symbol detection: alpha = max(R, G, B) x gain after black-level subtraction, gated by greenness G - max(R, B) to reject noise.\n" .. + "\"Symbol color\" picks the hue, \"Symbol brightness\" scales it (0.35 = dark green, 1.0 = full brightness).\n" .. + "\"Symbol width\" thickens (positive) or thins (negative) the strokes; 0 keeps the original width.\n" .. + "\"Outline color\" picks the outline hue (default blue); the outline only appears when \"Symbol width\" is positive.\n" .. + "\"Create Scene\": builds the scene from scratch (deletes and recreates both media sources).\n" .. + "\"Update Scene\": updates the existing scene in place (video paths, color, keying parameters).\n" .. + "The filter panel has sliders for fine-tuning (symbol color R/G/B, outline color R/G/B, width, strength and brightness)." +end + +function script_properties() + local props = obs.obs_properties_create() + + obs.obs_properties_add_path(props, SETTING_FRONT_PATH, + "Foreground video (HUD, green symbols on black)", obs.OBS_PATH_FILE, + "Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "") + + obs.obs_properties_add_path(props, SETTING_BACK_PATH, + "Background video (bottom layer)", obs.OBS_PATH_FILE, + "Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "") + + local clist = obs.obs_properties_add_list(props, SETTING_COLOR, + "Symbol color", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT) + for i, c in ipairs(COLOR_PRESETS) do + obs.obs_property_list_add_int(clist, c.name, i) + end + + obs.obs_properties_add_float_slider(props, SETTING_BRIGHTNESS, + "Symbol brightness (scales the chosen color)", 0.05, 1.0, 0.01) + obs.obs_properties_add_float_slider(props, SETTING_BLACK_LEVEL, + "Black level (increase if a dark halo/noise remains)", 0.0, 0.3, 0.001) + obs.obs_properties_add_float_slider(props, SETTING_GAIN, + "Alpha gain (increase if symbols look too transparent)", 0.5, 4.0, 0.01) + obs.obs_properties_add_float_slider(props, SETTING_GREEN_THRESH, + "Green threshold (increase if non-green noise appears)", 0.01, 0.5, 0.005) + obs.obs_properties_add_float_slider(props, SETTING_OUT_WIDTH, + "Symbol width (px, + thicker / - thinner, 0 = original)", -4.0, 8.0, 0.5) + obs.obs_properties_add_float_slider(props, SETTING_OUT_ALPHA, + "Outline strength", 0.0, 1.0, 0.01) + + local olist = obs.obs_properties_add_list(props, SETTING_OUT_COLOR, + "Outline color (only visible when Symbol width > 0)", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT) + for i, c in ipairs(OUTLINE_COLOR_PRESETS) do + obs.obs_property_list_add_int(olist, c.name, i) + end + + obs.obs_properties_add_float_slider(props, SETTING_OPACITY, + "Overall opacity", 0.0, 1.0, 0.01) + + obs.obs_properties_add_button(props, "create_btn", "Create Scene", on_create_clicked) + obs.obs_properties_add_button(props, "update_btn", "Update Scene", on_update_clicked) + + return props +end + +function script_defaults(settings) + obs.obs_data_set_default_string(settings, SETTING_FRONT_PATH, + "D:\\Test Pictures + Video\\HUD - Front Video.avi") + obs.obs_data_set_default_string(settings, SETTING_BACK_PATH, + "D:\\Test Pictures + Video\\Center IG - Back Videos.avi") + obs.obs_data_set_default_int(settings, SETTING_COLOR, 1) + obs.obs_data_set_default_double(settings, SETTING_BRIGHTNESS, 1.0) + obs.obs_data_set_default_double(settings, SETTING_BLACK_LEVEL, 0.055) + obs.obs_data_set_default_double(settings, SETTING_GAIN, 1.35) + obs.obs_data_set_default_double(settings, SETTING_GREEN_THRESH, 0.10) + obs.obs_data_set_default_double(settings, SETTING_OUT_WIDTH, 0.0) + obs.obs_data_set_default_double(settings, SETTING_OUT_ALPHA, 0.9) + obs.obs_data_set_default_int(settings, SETTING_OUT_COLOR, 1) + obs.obs_data_set_default_double(settings, SETTING_OPACITY, 1.0) +end + +function script_update(settings) + front_path = obs.obs_data_get_string(settings, SETTING_FRONT_PATH) + back_path = obs.obs_data_get_string(settings, SETTING_BACK_PATH) + color_index = obs.obs_data_get_int(settings, SETTING_COLOR) + outline_color_index = obs.obs_data_get_int(settings, SETTING_OUT_COLOR) + symbol_brightness = obs.obs_data_get_double(settings, SETTING_BRIGHTNESS) + black_level = obs.obs_data_get_double(settings, SETTING_BLACK_LEVEL) + alpha_gain = obs.obs_data_get_double(settings, SETTING_GAIN) + green_thresh = obs.obs_data_get_double(settings, SETTING_GREEN_THRESH) + outline_width = obs.obs_data_get_double(settings, SETTING_OUT_WIDTH) + outline_alpha = obs.obs_data_get_double(settings, SETTING_OUT_ALPHA) + opacity = obs.obs_data_get_double(settings, SETTING_OPACITY) +end