diff --git a/plugins/obs-filters/CMakeLists.txt b/plugins/obs-filters/CMakeLists.txt index 3313a02078cd4d6aecc6196c631f6e577e445f0c..58c63a99f1600b604db5edd89b37cdee9b511bdd 100644 --- a/plugins/obs-filters/CMakeLists.txt +++ b/plugins/obs-filters/CMakeLists.txt @@ -2,7 +2,8 @@ project(obs-filters) set(obs-filters_SOURCES obs-filters.c - async-delay-filter.c) + async-delay-filter.c + mask-filter.c) add_library(obs-filters MODULE ${obs-filters_SOURCES}) diff --git a/plugins/obs-filters/data/blend_add_filter.effect b/plugins/obs-filters/data/blend_add_filter.effect new file mode 100644 index 0000000000000000000000000000000000000000..dc64c3408fb4d4a1146f842769ed535e2edec5c6 --- /dev/null +++ b/plugins/obs-filters/data/blend_add_filter.effect @@ -0,0 +1,67 @@ +uniform float4x4 ViewProj; +uniform texture2d image; +uniform float4x4 color_matrix; +uniform float3 color_range_min = {0.0, 0.0, 0.0}; +uniform float3 color_range_max = {1.0, 1.0, 1.0}; + +uniform texture2d target; +uniform float4 color; + +sampler_state textureSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct VertData { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertData VSDefault(VertData v_in) +{ + VertData vert_out; + vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = v_in.uv; + return vert_out; +} + +float4 PSAddImageRGBA(VertData v_in) : TARGET +{ + float4 rgba = image.Sample(textureSampler, v_in.uv) * color; + + float4 targetRGB = target.Sample(textureSampler, v_in.uv); + rgba.rgb = saturate(rgba.rgb + targetRGB.rgb); + return rgba; +} + +float4 PSAddImageMatrix(VertData v_in) : TARGET +{ + float4 yuv = image.Sample(textureSampler, v_in.uv); + yuv.xyz = clamp(yuv.xyz, color_range_min, color_range_max); + + float4 rgba = saturate(mul(float4(yuv.xyz, 1.0), color_matrix)) * + color; + + float4 targetRGB = target.Sample(textureSampler, v_in.uv); + rgba.rgb = saturate(rgba.rgb + targetRGB.rgb); + return rgba; +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSAddImageRGBA(v_in); + } +} + +technique DrawMatrix +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSAddImageMatrix(v_in); + } +} diff --git a/plugins/obs-filters/data/blend_mul_filter.effect b/plugins/obs-filters/data/blend_mul_filter.effect new file mode 100644 index 0000000000000000000000000000000000000000..6ec1a8152a930966ddd143f81ef4933abc8ed5ac --- /dev/null +++ b/plugins/obs-filters/data/blend_mul_filter.effect @@ -0,0 +1,67 @@ +uniform float4x4 ViewProj; +uniform texture2d image; +uniform float4x4 color_matrix; +uniform float3 color_range_min = {0.0, 0.0, 0.0}; +uniform float3 color_range_max = {1.0, 1.0, 1.0}; + +uniform texture2d target; +uniform float4 color; + +sampler_state textureSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct VertData { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertData VSDefault(VertData v_in) +{ + VertData vert_out; + vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = v_in.uv; + return vert_out; +} + +float4 PSMuliplyImageRGBA(VertData v_in) : TARGET +{ + float4 rgba = image.Sample(textureSampler, v_in.uv) * color; + + float4 targetRGB = target.Sample(textureSampler, v_in.uv); + rgba.rgb = saturate(rgba.rgb * targetRGB.rgb); + return rgba; +} + +float4 PSMuliplyImageMatrix(VertData v_in) : TARGET +{ + float4 yuv = image.Sample(textureSampler, v_in.uv); + yuv.xyz = clamp(yuv.xyz, color_range_min, color_range_max); + + float4 rgba = saturate(mul(float4(yuv.xyz, 1.0), color_matrix)) * + color; + + float4 targetRGB = target.Sample(textureSampler, v_in.uv); + rgba.rgb = saturate(rgba.rgb * targetRGB.rgb); + return rgba; +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSMuliplyImageRGBA(v_in); + } +} + +technique DrawMatrix +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSMuliplyImageMatrix(v_in); + } +} diff --git a/plugins/obs-filters/data/blend_sub_filter.effect b/plugins/obs-filters/data/blend_sub_filter.effect new file mode 100644 index 0000000000000000000000000000000000000000..8414734192f8eed4f9b92d8ded0dae2b0ce97fbd --- /dev/null +++ b/plugins/obs-filters/data/blend_sub_filter.effect @@ -0,0 +1,67 @@ +uniform float4x4 ViewProj; +uniform texture2d image; +uniform float4x4 color_matrix; +uniform float3 color_range_min = {0.0, 0.0, 0.0}; +uniform float3 color_range_max = {1.0, 1.0, 1.0}; + +uniform texture2d target; +uniform float4 color; + +sampler_state textureSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct VertData { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertData VSDefault(VertData v_in) +{ + VertData vert_out; + vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = v_in.uv; + return vert_out; +} + +float4 PSSubtractImageRGBA(VertData v_in) : TARGET +{ + float4 rgba = image.Sample(textureSampler, v_in.uv) * color; + + float4 targetRGB = target.Sample(textureSampler, v_in.uv); + rgba.rgb = saturate(rgba.rgb - targetRGB.rgb); + return rgba; +} + +float4 PSSubtractImageMatrix(VertData v_in) : TARGET +{ + float4 yuv = image.Sample(textureSampler, v_in.uv); + yuv.xyz = clamp(yuv.xyz, color_range_min, color_range_max); + + float4 rgba = saturate(mul(float4(yuv.xyz, 1.0), color_matrix)) * + color; + + float4 targetRGB = target.Sample(textureSampler, v_in.uv); + rgba.rgb = saturate(rgba.rgb - targetRGB.rgb); + return rgba; +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSSubtractImageRGBA(v_in); + } +} + +technique DrawMatrix +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSSubtractImageMatrix(v_in); + } +} diff --git a/plugins/obs-filters/data/locale/en-US.ini b/plugins/obs-filters/data/locale/en-US.ini index c8905c7e486ee281402b58519e4112ded00ea228..f961b36e70fcaecda108c1cf92250f477fe2ebf0 100644 --- a/plugins/obs-filters/data/locale/en-US.ini +++ b/plugins/obs-filters/data/locale/en-US.ini @@ -1,2 +1,17 @@ +MaskFilter="Image Mask/Blend" AsyncDelayFilter="Video Delay (Async)" DelayMs="Delay (milliseconds)" +Type="Type" +MaskBlendType.MaskColor="Alpha Mask (Color Channel)" +MaskBlendType.MaskAlpha="Alpha Mask (Alpha Channel)" +MaskBlendType.BlendMultiply="Blend (Multiply)" +MaskBlendType.BlendAddition="Blend (Addition)" +MaskBlendType.BlendSubtraction="Blend (Subtraction)" +Path="Path" +Color="Color" +Opacity="Opacity" +Contrast="Contrast" +Brightness="Brightness" +Gamma="Gamma" +BrowsePath.Images="All Image Files" +BrowsePath.AllFiles="All Files" diff --git a/plugins/obs-filters/data/mask_alpha_filter.effect b/plugins/obs-filters/data/mask_alpha_filter.effect new file mode 100644 index 0000000000000000000000000000000000000000..e02f94150c43f6feff950a57638b1bd5fb7b4d00 --- /dev/null +++ b/plugins/obs-filters/data/mask_alpha_filter.effect @@ -0,0 +1,67 @@ +uniform float4x4 ViewProj; +uniform texture2d image; +uniform float4x4 color_matrix; +uniform float3 color_range_min = {0.0, 0.0, 0.0}; +uniform float3 color_range_max = {1.0, 1.0, 1.0}; + +uniform texture2d target; +uniform float4 color; + +sampler_state textureSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct VertData { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertData VSDefault(VertData v_in) +{ + VertData vert_out; + vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = v_in.uv; + return vert_out; +} + +float4 PSAlphaMaskRGBA(VertData v_in) : TARGET +{ + float4 rgba = image.Sample(textureSampler, v_in.uv) * color; + + float4 targetRGB = target.Sample(textureSampler, v_in.uv); + rgba.a *= targetRGB.a; + return rgba; +} + +float4 PSAlphaMaskMatrix(VertData v_in) : TARGET +{ + float4 yuv = image.Sample(textureSampler, v_in.uv); + yuv.xyz = clamp(yuv.xyz, color_range_min, color_range_max); + + float4 rgba = saturate(mul(float4(yuv.xyz, 1.0), color_matrix)) * + color; + + float4 targetRGB = target.Sample(textureSampler, v_in.uv); + rgba.a = targetRGB.a; + return rgba; +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSAlphaMaskRGBA(v_in); + } +} + +technique DrawMatrix +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSAlphaMaskMatrix(v_in); + } +} diff --git a/plugins/obs-filters/data/mask_color_filter.effect b/plugins/obs-filters/data/mask_color_filter.effect new file mode 100644 index 0000000000000000000000000000000000000000..0e0fd3ba6b76934a99f4ac2b49a79d030a437564 --- /dev/null +++ b/plugins/obs-filters/data/mask_color_filter.effect @@ -0,0 +1,67 @@ +uniform float4x4 ViewProj; +uniform texture2d image; +uniform float4x4 color_matrix; +uniform float3 color_range_min = {0.0, 0.0, 0.0}; +uniform float3 color_range_max = {1.0, 1.0, 1.0}; + +uniform texture2d target; +uniform float4 color; + +sampler_state textureSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; +}; + +struct VertData { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertData VSDefault(VertData v_in) +{ + VertData vert_out; + vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = v_in.uv; + return vert_out; +} + +float4 PSColorMaskRGBA(VertData v_in) : TARGET +{ + float4 rgba = image.Sample(textureSampler, v_in.uv) * color; + + float4 targetRGB = target.Sample(textureSampler, v_in.uv); + rgba.a *= (targetRGB.r + targetRGB.g + targetRGB.b) / 3.0; + return rgba; +} + +float4 PSColorMaskMatrix(VertData v_in) : TARGET +{ + float4 yuv = image.Sample(textureSampler, v_in.uv); + yuv.xyz = clamp(yuv.xyz, color_range_min, color_range_max); + + float4 rgba = saturate(mul(float4(yuv.xyz, 1.0), color_matrix)) * + color; + + float4 targetRGB = target.Sample(textureSampler, v_in.uv); + rgba.a = (targetRGB.r + targetRGB.g + targetRGB.b) / 3.0; + return rgba; +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSColorMaskRGBA(v_in); + } +} + +technique DrawMatrix +{ + pass + { + vertex_shader = VSDefault(v_in); + pixel_shader = PSColorMaskMatrix(v_in); + } +} diff --git a/plugins/obs-filters/mask-filter.c b/plugins/obs-filters/mask-filter.c new file mode 100644 index 0000000000000000000000000000000000000000..ebf58132b516e71bde30bc887f730d0f3e52598c --- /dev/null +++ b/plugins/obs-filters/mask-filter.c @@ -0,0 +1,166 @@ +#include +#include +#include + +#define SETTING_TYPE "type" +#define SETTING_IMAGE_PATH "image_path" +#define SETTING_COLOR "color" +#define SETTING_OPACITY "opacity" + +#define TEXT_TYPE obs_module_text("Type") +#define TEXT_IMAGE_PATH obs_module_text("Path") +#define TEXT_COLOR obs_module_text("Color") +#define TEXT_OPACITY obs_module_text("Opacity") +#define TEXT_PATH_IMAGES obs_module_text("BrowsePath.Images") +#define TEXT_PATH_ALL_FILES obs_module_text("BrowsePath.AllFiles") + +struct mask_filter_data { + obs_source_t *context; + gs_effect_t *effect; + + gs_texture_t *target; + struct vec4 color; +}; + +static const char *mask_filter_get_name(void) +{ + return obs_module_text("MaskFilter"); +} + +static void mask_filter_update(void *data, obs_data_t *settings) +{ + struct mask_filter_data *filter = data; + + const char *path = obs_data_get_string(settings, SETTING_IMAGE_PATH); + const char *effect_file = obs_data_get_string(settings, SETTING_TYPE); + uint32_t color = (uint32_t)obs_data_get_int(settings, SETTING_COLOR); + int opacity = (int)obs_data_get_int(settings, SETTING_OPACITY); + char *effect_path; + + color |= (uint32_t)(((double)opacity) * 2.55) << 24; + + vec4_from_rgba(&filter->color, color); + + obs_enter_graphics(); + + gs_texture_destroy(filter->target); + filter->target = (path) ? gs_texture_create_from_file(path) : NULL; + + effect_path = obs_module_file(effect_file); + gs_effect_destroy(filter->effect); + filter->effect = gs_effect_create_from_file(effect_path, NULL); + bfree(effect_path); + + obs_leave_graphics(); +} + +static void mask_filter_defaults(obs_data_t *settings) +{ + obs_data_set_default_string(settings, SETTING_TYPE, + "mask_color_filter.effect"); + obs_data_set_default_int(settings, SETTING_COLOR, 0xFFFFFF); + obs_data_set_default_int(settings, SETTING_OPACITY, 100); +} + +#define IMAGE_FILTER_EXTENSIONS \ + " (*.bmp *.jpg *.jpeg *.tga *.gif *.png)" + +static obs_properties_t *mask_filter_properties(void *data) +{ + obs_properties_t *props = obs_properties_create(); + struct dstr filter_str = {0}; + obs_property_t *p; + + dstr_copy(&filter_str, TEXT_PATH_IMAGES); + dstr_cat(&filter_str, IMAGE_FILTER_EXTENSIONS ";;"); + dstr_cat(&filter_str, TEXT_PATH_ALL_FILES); + dstr_cat(&filter_str, " (*.*)"); + + p = obs_properties_add_list(props, SETTING_TYPE, TEXT_TYPE, + OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + + obs_property_list_add_string(p, + obs_module_text("MaskBlendType.MaskColor"), + "mask_color_filter.effect"); + obs_property_list_add_string(p, + obs_module_text("MaskBlendType.MaskAlpha"), + "mask_alpha_filter.effect"); + obs_property_list_add_string(p, + obs_module_text("MaskBlendType.BlendMultiply"), + "blend_mul_filter.effect"); + obs_property_list_add_string(p, + obs_module_text("MaskBlendType.BlendAddition"), + "blend_add_filter.effect"); + obs_property_list_add_string(p, + obs_module_text("MaskBlendType.BlendSubtraction"), + "blend_sub_filter.effect"); + + obs_properties_add_path(props, SETTING_IMAGE_PATH, TEXT_IMAGE_PATH, + OBS_PATH_FILE, filter_str.array, NULL); + obs_properties_add_color(props, SETTING_COLOR, TEXT_COLOR); + obs_properties_add_int(props, SETTING_OPACITY, TEXT_OPACITY, 0, 100, 1); + + dstr_free(&filter_str); + + UNUSED_PARAMETER(data); + return props; +} + +static void *mask_filter_create(obs_data_t *settings, obs_source_t *context) +{ + struct mask_filter_data *filter = + bzalloc(sizeof(struct mask_filter_data)); + filter->context = context; + + obs_source_update(context, settings); + return filter; +} + +static void mask_filter_destroy(void *data) +{ + struct mask_filter_data *filter = data; + + obs_enter_graphics(); + gs_effect_destroy(filter->effect); + gs_texture_destroy(filter->target); + obs_leave_graphics(); + + bfree(filter); +} + +static void mask_filter_render(void *data, gs_effect_t *effect) +{ + struct mask_filter_data *filter = data; + gs_eparam_t *param; + + if (!filter->target || !filter->effect) { + obs_source_skip_video_filter(filter->context); + return; + } + + obs_source_process_filter_begin(filter->context, GS_RGBA, + OBS_ALLOW_DIRECT_RENDERING); + + param = gs_effect_get_param_by_name(filter->effect, "target"); + gs_effect_set_texture(param, filter->target); + + param = gs_effect_get_param_by_name(filter->effect, "color"); + gs_effect_set_vec4(param, &filter->color); + + obs_source_process_filter_end(filter->context, filter->effect, 0, 0); + + UNUSED_PARAMETER(effect); +} + +struct obs_source_info mask_filter = { + .id = "mask_filter", + .type = OBS_SOURCE_TYPE_FILTER, + .output_flags = OBS_SOURCE_VIDEO, + .get_name = mask_filter_get_name, + .create = mask_filter_create, + .destroy = mask_filter_destroy, + .update = mask_filter_update, + .get_defaults = mask_filter_defaults, + .get_properties = mask_filter_properties, + .video_render = mask_filter_render +}; diff --git a/plugins/obs-filters/obs-filters.c b/plugins/obs-filters/obs-filters.c index 2f7c53838288080faa1f40bfd2985611b0644c42..23f9fed15083d0068bde9945bbe3a227f0e2789f 100644 --- a/plugins/obs-filters/obs-filters.c +++ b/plugins/obs-filters/obs-filters.c @@ -4,10 +4,12 @@ OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("obs-filters", "en-US") +extern struct obs_source_info mask_filter; extern struct obs_source_info async_delay_filter; bool obs_module_load(void) { + obs_register_source(&mask_filter); obs_register_source(&async_delay_filter); return true; }