obs-filters: Add color key filter
Masks out a specific color range in RGB color space. Commonly used with solid image data or animated video rather than live video.
This commit is contained in:
@@ -6,6 +6,7 @@ set(obs-filters_SOURCES
|
||||
async-delay-filter.c
|
||||
crop-filter.c
|
||||
chroma-key-filter.c
|
||||
color-key-filter.c
|
||||
mask-filter.c)
|
||||
|
||||
add_library(obs-filters MODULE
|
||||
|
254
plugins/obs-filters/color-key-filter.c
Normal file
254
plugins/obs-filters/color-key-filter.c
Normal file
@@ -0,0 +1,254 @@
|
||||
#include <obs-module.h>
|
||||
#include <graphics/matrix4.h>
|
||||
#include <graphics/vec2.h>
|
||||
#include <graphics/vec4.h>
|
||||
|
||||
#define SETTING_OPACITY "opacity"
|
||||
#define SETTING_CONTRAST "contrast"
|
||||
#define SETTING_BRIGHTNESS "brightness"
|
||||
#define SETTING_GAMMA "gamma"
|
||||
#define SETTING_COLOR_TYPE "key_color_type"
|
||||
#define SETTING_KEY_COLOR "key_color"
|
||||
#define SETTING_SIMILARITY "similarity"
|
||||
#define SETTING_SMOOTHNESS "smoothness"
|
||||
|
||||
#define TEXT_OPACITY obs_module_text("Opacity")
|
||||
#define TEXT_CONTRAST obs_module_text("Contrast")
|
||||
#define TEXT_BRIGHTNESS obs_module_text("Brightness")
|
||||
#define TEXT_GAMMA obs_module_text("Gamma")
|
||||
#define TEXT_COLOR_TYPE obs_module_text("KeyColorType")
|
||||
#define TEXT_KEY_COLOR obs_module_text("KeyColor")
|
||||
#define TEXT_SIMILARITY obs_module_text("Similarity")
|
||||
#define TEXT_SMOOTHNESS obs_module_text("Smoothness")
|
||||
|
||||
struct color_key_filter_data {
|
||||
obs_source_t *context;
|
||||
|
||||
gs_effect_t *effect;
|
||||
|
||||
gs_eparam_t *color_param;
|
||||
gs_eparam_t *contrast_param;
|
||||
gs_eparam_t *brightness_param;
|
||||
gs_eparam_t *gamma_param;
|
||||
|
||||
gs_eparam_t *key_color_param;
|
||||
gs_eparam_t *similarity_param;
|
||||
gs_eparam_t *smoothness_param;
|
||||
|
||||
struct vec4 color;
|
||||
float contrast;
|
||||
float brightness;
|
||||
float gamma;
|
||||
|
||||
struct vec4 key_color;
|
||||
float similarity;
|
||||
float smoothness;
|
||||
};
|
||||
|
||||
static const char *color_key_name(void)
|
||||
{
|
||||
return obs_module_text("ColorKeyFilter");
|
||||
}
|
||||
|
||||
static inline void color_settings_update(
|
||||
struct color_key_filter_data *filter, obs_data_t *settings)
|
||||
{
|
||||
uint32_t opacity = (uint32_t)obs_data_get_int(settings,
|
||||
SETTING_OPACITY);
|
||||
uint32_t color = 0xFFFFFF | (((opacity * 255) / 100) << 24);
|
||||
double contrast = obs_data_get_double(settings, SETTING_CONTRAST);
|
||||
double brightness = obs_data_get_double(settings, SETTING_BRIGHTNESS);
|
||||
double gamma = obs_data_get_double(settings, SETTING_GAMMA);
|
||||
|
||||
contrast = (contrast < 0.0) ?
|
||||
(1.0 / (-contrast + 1.0)) : (contrast + 1.0);
|
||||
|
||||
brightness *= 0.5;
|
||||
|
||||
gamma = (gamma < 0.0) ? (-gamma + 1.0) : (1.0 / (gamma + 1.0));
|
||||
|
||||
filter->contrast = (float)contrast;
|
||||
filter->brightness = (float)brightness;
|
||||
filter->gamma = (float)gamma;
|
||||
|
||||
vec4_from_rgba(&filter->color, color);
|
||||
}
|
||||
|
||||
static inline void key_settings_update(
|
||||
struct color_key_filter_data *filter, obs_data_t *settings)
|
||||
{
|
||||
int64_t similarity = obs_data_get_int(settings, SETTING_SIMILARITY);
|
||||
int64_t smoothness = obs_data_get_int(settings, SETTING_SMOOTHNESS);
|
||||
uint32_t key_color = (uint32_t)obs_data_get_int(settings,
|
||||
SETTING_KEY_COLOR);
|
||||
const char *key_type = obs_data_get_string(settings,
|
||||
SETTING_COLOR_TYPE);
|
||||
|
||||
if (strcmp(key_type, "green") == 0)
|
||||
key_color = 0x00FF00;
|
||||
else if (strcmp(key_type, "blue") == 0)
|
||||
key_color = 0xFF0000;
|
||||
else if (strcmp(key_type, "red") == 0)
|
||||
key_color = 0x0000FF;
|
||||
else if (strcmp(key_type, "magenta") == 0)
|
||||
key_color = 0xFF00FF;
|
||||
|
||||
vec4_from_rgba(&filter->key_color, key_color | 0xFF000000);
|
||||
|
||||
filter->similarity = (float)similarity / 1000.0f;
|
||||
filter->smoothness = (float)smoothness / 1000.0f;
|
||||
}
|
||||
|
||||
static void color_key_update(void *data, obs_data_t *settings)
|
||||
{
|
||||
struct color_key_filter_data *filter = data;
|
||||
|
||||
color_settings_update(filter, settings);
|
||||
key_settings_update(filter, settings);
|
||||
}
|
||||
|
||||
static void color_key_destroy(void *data)
|
||||
{
|
||||
struct color_key_filter_data *filter = data;
|
||||
|
||||
if (filter->effect) {
|
||||
obs_enter_graphics();
|
||||
gs_effect_destroy(filter->effect);
|
||||
obs_leave_graphics();
|
||||
}
|
||||
|
||||
bfree(data);
|
||||
}
|
||||
|
||||
static void *color_key_create(obs_data_t *settings, obs_source_t *context)
|
||||
{
|
||||
struct color_key_filter_data *filter =
|
||||
bzalloc(sizeof(struct color_key_filter_data));
|
||||
char *effect_path = obs_module_file("color_key_filter.effect");
|
||||
|
||||
filter->context = context;
|
||||
|
||||
obs_enter_graphics();
|
||||
|
||||
filter->effect = gs_effect_create_from_file(effect_path, NULL);
|
||||
if (filter) {
|
||||
filter->color_param = gs_effect_get_param_by_name(
|
||||
filter->effect, "color");
|
||||
filter->contrast_param = gs_effect_get_param_by_name(
|
||||
filter->effect, "contrast");
|
||||
filter->brightness_param = gs_effect_get_param_by_name(
|
||||
filter->effect, "brightness");
|
||||
filter->gamma_param = gs_effect_get_param_by_name(
|
||||
filter->effect, "gamma");
|
||||
filter->key_color_param = gs_effect_get_param_by_name(
|
||||
filter->effect, "key_color");
|
||||
filter->similarity_param = gs_effect_get_param_by_name(
|
||||
filter->effect, "similarity");
|
||||
filter->smoothness_param = gs_effect_get_param_by_name(
|
||||
filter->effect, "smoothness");
|
||||
}
|
||||
|
||||
obs_leave_graphics();
|
||||
|
||||
bfree(effect_path);
|
||||
|
||||
if (!filter->effect) {
|
||||
color_key_destroy(filter);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
color_key_update(filter, settings);
|
||||
return filter;
|
||||
}
|
||||
|
||||
static void color_key_render(void *data, gs_effect_t *effect)
|
||||
{
|
||||
struct color_key_filter_data *filter = data;
|
||||
|
||||
obs_source_process_filter_begin(filter->context, GS_RGBA,
|
||||
OBS_ALLOW_DIRECT_RENDERING);
|
||||
|
||||
gs_effect_set_vec4(filter->color_param, &filter->color);
|
||||
gs_effect_set_float(filter->contrast_param, filter->contrast);
|
||||
gs_effect_set_float(filter->brightness_param, filter->brightness);
|
||||
gs_effect_set_float(filter->gamma_param, filter->gamma);
|
||||
gs_effect_set_vec4(filter->key_color_param, &filter->key_color);
|
||||
gs_effect_set_float(filter->similarity_param, filter->similarity);
|
||||
gs_effect_set_float(filter->smoothness_param, filter->smoothness);
|
||||
|
||||
obs_source_process_filter_end(filter->context, filter->effect, 0, 0);
|
||||
|
||||
UNUSED_PARAMETER(effect);
|
||||
}
|
||||
|
||||
static bool key_type_changed(obs_properties_t *props, obs_property_t *p,
|
||||
obs_data_t *settings)
|
||||
{
|
||||
const char *type = obs_data_get_string(settings, SETTING_COLOR_TYPE);
|
||||
bool custom = strcmp(type, "custom") == 0;
|
||||
|
||||
obs_property_set_visible(obs_properties_get(props, SETTING_KEY_COLOR),
|
||||
custom);
|
||||
|
||||
UNUSED_PARAMETER(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
static obs_properties_t *color_key_properties(void *data)
|
||||
{
|
||||
obs_properties_t *props = obs_properties_create();
|
||||
|
||||
obs_property_t *p = obs_properties_add_list(props,
|
||||
SETTING_COLOR_TYPE, TEXT_COLOR_TYPE,
|
||||
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
|
||||
obs_property_list_add_string(p, obs_module_text("Green"), "green");
|
||||
obs_property_list_add_string(p, obs_module_text("Blue"), "blue");
|
||||
obs_property_list_add_string(p, obs_module_text("Red"), "red");
|
||||
obs_property_list_add_string(p, obs_module_text("Magenta"), "magenta");
|
||||
obs_property_list_add_string(p, obs_module_text("CustomColor"),
|
||||
"custom");
|
||||
|
||||
obs_property_set_modified_callback(p, key_type_changed);
|
||||
|
||||
obs_properties_add_color(props, SETTING_KEY_COLOR, TEXT_KEY_COLOR);
|
||||
obs_properties_add_int_slider(props, SETTING_SIMILARITY,
|
||||
TEXT_SIMILARITY, 1, 1000, 1);
|
||||
obs_properties_add_int_slider(props, SETTING_SMOOTHNESS,
|
||||
TEXT_SMOOTHNESS, 1, 1000, 1);
|
||||
|
||||
obs_properties_add_int(props, SETTING_OPACITY, TEXT_OPACITY, 0, 100, 1);
|
||||
obs_properties_add_float_slider(props, SETTING_CONTRAST,
|
||||
TEXT_CONTRAST, -1.0, 1.0, 0.01);
|
||||
obs_properties_add_float_slider(props, SETTING_BRIGHTNESS,
|
||||
TEXT_BRIGHTNESS, -1.0, 1.0, 0.01);
|
||||
obs_properties_add_float_slider(props, SETTING_GAMMA,
|
||||
TEXT_GAMMA, -1.0, 1.0, 0.01);
|
||||
|
||||
UNUSED_PARAMETER(data);
|
||||
return props;
|
||||
}
|
||||
|
||||
static void color_key_defaults(obs_data_t *settings)
|
||||
{
|
||||
obs_data_set_default_int(settings, SETTING_OPACITY, 100);
|
||||
obs_data_set_default_double(settings, SETTING_CONTRAST, 0.0);
|
||||
obs_data_set_default_double(settings, SETTING_BRIGHTNESS, 0.0);
|
||||
obs_data_set_default_double(settings, SETTING_GAMMA, 0.0);
|
||||
obs_data_set_default_int(settings, SETTING_KEY_COLOR, 0x00FF00);
|
||||
obs_data_set_default_string(settings, SETTING_COLOR_TYPE, "green");
|
||||
obs_data_set_default_int(settings, SETTING_SIMILARITY, 80);
|
||||
obs_data_set_default_int(settings, SETTING_SMOOTHNESS, 50);
|
||||
}
|
||||
|
||||
struct obs_source_info color_key_filter = {
|
||||
.id = "color_key_filter",
|
||||
.type = OBS_SOURCE_TYPE_FILTER,
|
||||
.output_flags = OBS_SOURCE_VIDEO,
|
||||
.get_name = color_key_name,
|
||||
.create = color_key_create,
|
||||
.destroy = color_key_destroy,
|
||||
.video_render = color_key_render,
|
||||
.update = color_key_update,
|
||||
.get_properties = color_key_properties,
|
||||
.get_defaults = color_key_defaults
|
||||
};
|
92
plugins/obs-filters/data/color_key_filter.effect
Normal file
92
plugins/obs-filters/data/color_key_filter.effect
Normal file
@@ -0,0 +1,92 @@
|
||||
uniform float4x4 ViewProj;
|
||||
uniform texture2d image;
|
||||
uniform float4x4 color_matrix = {1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0};
|
||||
uniform float3 color_range_min = {0.0, 0.0, 0.0};
|
||||
uniform float3 color_range_max = {1.0, 1.0, 1.0};
|
||||
|
||||
uniform float4 color;
|
||||
uniform float contrast;
|
||||
uniform float brightness;
|
||||
uniform float gamma;
|
||||
|
||||
uniform float4 key_color;
|
||||
uniform float similarity;
|
||||
uniform float smoothness;
|
||||
|
||||
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 CalcColor(float4 rgba)
|
||||
{
|
||||
return float4(pow(rgba.rgb, float3(gamma, gamma, gamma)) * contrast + brightness, rgba.a);
|
||||
}
|
||||
|
||||
float GetColorDist(float3 rgb)
|
||||
{
|
||||
return distance(key_color.rgb, rgb);
|
||||
}
|
||||
|
||||
float4 SampleYUVToRGB(float2 uv)
|
||||
{
|
||||
float4 yuv = image.Sample(textureSampler, uv);
|
||||
yuv.xyz = clamp(yuv.xyz, color_range_min, color_range_max);
|
||||
return saturate(mul(float4(yuv.xyz, 1.0), color_matrix));
|
||||
}
|
||||
|
||||
float4 ProcessColorKey(float4 rgba, VertData v_in)
|
||||
{
|
||||
float colorDist = GetColorDist(rgba.rgb);
|
||||
float baseMask = colorDist - similarity;
|
||||
rgba.a *= saturate(max(colorDist - similarity, 0.0) / smoothness);
|
||||
|
||||
return CalcColor(rgba);
|
||||
}
|
||||
|
||||
float4 PSColorKeyRGBA(VertData v_in) : TARGET
|
||||
{
|
||||
float4 rgba = image.Sample(textureSampler, v_in.uv) * color;
|
||||
return ProcessColorKey(rgba, v_in);
|
||||
}
|
||||
|
||||
float4 PSColorKeyMatrix(VertData v_in) : TARGET
|
||||
{
|
||||
float4 rgba = SampleYUVToRGB(v_in.uv) * color;
|
||||
return ProcessColorKey(rgba, v_in);
|
||||
}
|
||||
|
||||
technique Draw
|
||||
{
|
||||
pass
|
||||
{
|
||||
vertex_shader = VSDefault(v_in);
|
||||
pixel_shader = PSColorKeyRGBA(v_in);
|
||||
}
|
||||
}
|
||||
|
||||
technique DrawMatrix
|
||||
{
|
||||
pass
|
||||
{
|
||||
vertex_shader = VSDefault(v_in);
|
||||
pixel_shader = PSColorKeyMatrix(v_in);
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ MaskFilter="Image Mask/Blend"
|
||||
AsyncDelayFilter="Video Delay (Async)"
|
||||
CropFilter="Crop"
|
||||
ChromaKeyFilter="Chroma Key"
|
||||
ColorKeyFilter="Color Key"
|
||||
DelayMs="Delay (milliseconds)"
|
||||
Type="Type"
|
||||
MaskBlendType.MaskColor="Alpha Mask (Color Channel)"
|
||||
@@ -31,6 +32,7 @@ Crop.Width="Width"
|
||||
Crop.Height="Height"
|
||||
Crop.Relative="Relative"
|
||||
CustomColor="Custom Color"
|
||||
Red="Red"
|
||||
Green="Green"
|
||||
Blue="Blue"
|
||||
Magenta="Magenta"
|
||||
|
@@ -7,6 +7,7 @@ OBS_MODULE_USE_DEFAULT_LOCALE("obs-filters", "en-US")
|
||||
extern struct obs_source_info mask_filter;
|
||||
extern struct obs_source_info crop_filter;
|
||||
extern struct obs_source_info color_filter;
|
||||
extern struct obs_source_info color_key_filter;
|
||||
extern struct obs_source_info chroma_key_filter;
|
||||
extern struct obs_source_info async_delay_filter;
|
||||
|
||||
@@ -15,6 +16,7 @@ bool obs_module_load(void)
|
||||
obs_register_source(&mask_filter);
|
||||
obs_register_source(&crop_filter);
|
||||
obs_register_source(&color_filter);
|
||||
obs_register_source(&color_key_filter);
|
||||
obs_register_source(&chroma_key_filter);
|
||||
obs_register_source(&async_delay_filter);
|
||||
return true;
|
||||
|
Reference in New Issue
Block a user