obs-filters: Apply chroma key filter in linear space
Also consolidate behavior of contrast/brightness/gamma across chroma key, color correction, and color key filters. The contrast range has been expanded to approximtely match the range when the filter applied to nonlinear colors.
This commit is contained in:
@@ -54,19 +54,45 @@ struct chroma_key_filter_data {
|
||||
float spill;
|
||||
};
|
||||
|
||||
struct chroma_key_filter_data_v2 {
|
||||
obs_source_t *context;
|
||||
|
||||
gs_effect_t *effect;
|
||||
|
||||
gs_eparam_t *opacity_param;
|
||||
gs_eparam_t *contrast_param;
|
||||
gs_eparam_t *brightness_param;
|
||||
gs_eparam_t *gamma_param;
|
||||
|
||||
gs_eparam_t *pixel_size_param;
|
||||
gs_eparam_t *chroma_param;
|
||||
gs_eparam_t *similarity_param;
|
||||
gs_eparam_t *smoothness_param;
|
||||
gs_eparam_t *spill_param;
|
||||
|
||||
float opacity;
|
||||
float contrast;
|
||||
float brightness;
|
||||
float gamma;
|
||||
|
||||
struct vec2 chroma;
|
||||
float similarity;
|
||||
float smoothness;
|
||||
float spill;
|
||||
};
|
||||
|
||||
static const char *chroma_key_name(void *unused)
|
||||
{
|
||||
UNUSED_PARAMETER(unused);
|
||||
return obs_module_text("ChromaKeyFilter");
|
||||
}
|
||||
|
||||
static const float yuv_mat[16] = {0.182586f, -0.100644f, 0.439216f, 0.0f,
|
||||
0.614231f, -0.338572f, -0.398942f, 0.0f,
|
||||
0.062007f, 0.439216f, -0.040274f, 0.0f,
|
||||
0.062745f, 0.501961f, 0.501961f, 1.0f};
|
||||
static const float cb_vec[] = {-0.100644f, -0.338572f, 0.439216f, 0.501961f};
|
||||
static const float cr_vec[] = {0.439216f, -0.398942f, -0.040274f, 0.501961f};
|
||||
|
||||
static inline void color_settings_update(struct chroma_key_filter_data *filter,
|
||||
obs_data_t *settings)
|
||||
static inline void
|
||||
color_settings_update_v1(struct chroma_key_filter_data *filter,
|
||||
obs_data_t *settings)
|
||||
{
|
||||
uint32_t opacity =
|
||||
(uint32_t)obs_data_get_int(settings, SETTING_OPACITY);
|
||||
@@ -89,8 +115,29 @@ static inline void color_settings_update(struct chroma_key_filter_data *filter,
|
||||
vec4_from_rgba(&filter->color, color);
|
||||
}
|
||||
|
||||
static inline void chroma_settings_update(struct chroma_key_filter_data *filter,
|
||||
obs_data_t *settings)
|
||||
static inline void
|
||||
color_settings_update_v2(struct chroma_key_filter_data_v2 *filter,
|
||||
obs_data_t *settings)
|
||||
{
|
||||
filter->opacity =
|
||||
(float)obs_data_get_int(settings, SETTING_OPACITY) * 0.01f;
|
||||
|
||||
double contrast = obs_data_get_double(settings, SETTING_CONTRAST);
|
||||
contrast = (contrast < 0.0) ? (1.0 / (-contrast + 1.0))
|
||||
: (contrast + 1.0);
|
||||
filter->contrast = (float)contrast;
|
||||
|
||||
filter->brightness =
|
||||
(float)obs_data_get_double(settings, SETTING_BRIGHTNESS);
|
||||
|
||||
double gamma = obs_data_get_double(settings, SETTING_GAMMA);
|
||||
gamma = (gamma < 0.0) ? (-gamma + 1.0) : (1.0 / (gamma + 1.0));
|
||||
filter->gamma = (float)gamma;
|
||||
}
|
||||
|
||||
static inline void
|
||||
chroma_settings_update_v1(struct chroma_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);
|
||||
@@ -100,8 +147,8 @@ static inline void chroma_settings_update(struct chroma_key_filter_data *filter,
|
||||
const char *key_type =
|
||||
obs_data_get_string(settings, SETTING_COLOR_TYPE);
|
||||
struct vec4 key_rgb;
|
||||
struct vec4 key_color_v4;
|
||||
struct matrix4 yuv_mat_m4;
|
||||
struct vec4 cb_v4;
|
||||
struct vec4 cr_v4;
|
||||
|
||||
if (strcmp(key_type, "green") == 0)
|
||||
key_color = 0x00FF00;
|
||||
@@ -112,24 +159,67 @@ static inline void chroma_settings_update(struct chroma_key_filter_data *filter,
|
||||
|
||||
vec4_from_rgba(&key_rgb, key_color | 0xFF000000);
|
||||
|
||||
memcpy(&yuv_mat_m4, yuv_mat, sizeof(yuv_mat));
|
||||
vec4_transform(&key_color_v4, &key_rgb, &yuv_mat_m4);
|
||||
vec2_set(&filter->chroma, key_color_v4.y, key_color_v4.z);
|
||||
memcpy(&cb_v4, cb_vec, sizeof(cb_v4));
|
||||
memcpy(&cr_v4, cr_vec, sizeof(cr_v4));
|
||||
filter->chroma.x = vec4_dot(&key_rgb, &cb_v4);
|
||||
filter->chroma.y = vec4_dot(&key_rgb, &cr_v4);
|
||||
|
||||
filter->similarity = (float)similarity / 1000.0f;
|
||||
filter->smoothness = (float)smoothness / 1000.0f;
|
||||
filter->spill = (float)spill / 1000.0f;
|
||||
}
|
||||
|
||||
static void chroma_key_update(void *data, obs_data_t *settings)
|
||||
static inline void
|
||||
chroma_settings_update_v2(struct chroma_key_filter_data_v2 *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);
|
||||
int64_t spill = obs_data_get_int(settings, SETTING_SPILL);
|
||||
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);
|
||||
struct vec4 key_rgb;
|
||||
struct vec4 cb_v4;
|
||||
struct vec4 cr_v4;
|
||||
|
||||
if (strcmp(key_type, "green") == 0)
|
||||
key_color = 0x00FF00;
|
||||
else if (strcmp(key_type, "blue") == 0)
|
||||
key_color = 0xFF9900;
|
||||
else if (strcmp(key_type, "magenta") == 0)
|
||||
key_color = 0xFF00FF;
|
||||
|
||||
vec4_from_rgba_srgb(&key_rgb, key_color | 0xFF000000);
|
||||
|
||||
memcpy(&cb_v4, cb_vec, sizeof(cb_v4));
|
||||
memcpy(&cr_v4, cr_vec, sizeof(cr_v4));
|
||||
filter->chroma.x = vec4_dot(&key_rgb, &cb_v4);
|
||||
filter->chroma.y = vec4_dot(&key_rgb, &cr_v4);
|
||||
|
||||
filter->similarity = (float)similarity / 1000.0f;
|
||||
filter->smoothness = (float)smoothness / 1000.0f;
|
||||
filter->spill = (float)spill / 1000.0f;
|
||||
}
|
||||
|
||||
static void chroma_key_update_v1(void *data, obs_data_t *settings)
|
||||
{
|
||||
struct chroma_key_filter_data *filter = data;
|
||||
|
||||
color_settings_update(filter, settings);
|
||||
chroma_settings_update(filter, settings);
|
||||
color_settings_update_v1(filter, settings);
|
||||
chroma_settings_update_v1(filter, settings);
|
||||
}
|
||||
|
||||
static void chroma_key_destroy(void *data)
|
||||
static void chroma_key_update_v2(void *data, obs_data_t *settings)
|
||||
{
|
||||
struct chroma_key_filter_data_v2 *filter = data;
|
||||
|
||||
color_settings_update_v2(filter, settings);
|
||||
chroma_settings_update_v2(filter, settings);
|
||||
}
|
||||
|
||||
static void chroma_key_destroy_v1(void *data)
|
||||
{
|
||||
struct chroma_key_filter_data *filter = data;
|
||||
|
||||
@@ -142,7 +232,20 @@ static void chroma_key_destroy(void *data)
|
||||
bfree(data);
|
||||
}
|
||||
|
||||
static void *chroma_key_create(obs_data_t *settings, obs_source_t *context)
|
||||
static void chroma_key_destroy_v2(void *data)
|
||||
{
|
||||
struct chroma_key_filter_data_v2 *filter = data;
|
||||
|
||||
if (filter->effect) {
|
||||
obs_enter_graphics();
|
||||
gs_effect_destroy(filter->effect);
|
||||
obs_leave_graphics();
|
||||
}
|
||||
|
||||
bfree(data);
|
||||
}
|
||||
|
||||
static void *chroma_key_create_v1(obs_data_t *settings, obs_source_t *context)
|
||||
{
|
||||
struct chroma_key_filter_data *filter =
|
||||
bzalloc(sizeof(struct chroma_key_filter_data));
|
||||
@@ -179,15 +282,60 @@ static void *chroma_key_create(obs_data_t *settings, obs_source_t *context)
|
||||
bfree(effect_path);
|
||||
|
||||
if (!filter->effect) {
|
||||
chroma_key_destroy(filter);
|
||||
chroma_key_destroy_v1(filter);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
chroma_key_update(filter, settings);
|
||||
chroma_key_update_v1(filter, settings);
|
||||
return filter;
|
||||
}
|
||||
|
||||
static void chroma_key_render(void *data, gs_effect_t *effect)
|
||||
static void *chroma_key_create_v2(obs_data_t *settings, obs_source_t *context)
|
||||
{
|
||||
struct chroma_key_filter_data_v2 *filter =
|
||||
bzalloc(sizeof(struct chroma_key_filter_data_v2));
|
||||
char *effect_path = obs_module_file("chroma_key_filter_v2.effect");
|
||||
|
||||
filter->context = context;
|
||||
|
||||
obs_enter_graphics();
|
||||
|
||||
filter->effect = gs_effect_create_from_file(effect_path, NULL);
|
||||
if (filter->effect) {
|
||||
filter->opacity_param =
|
||||
gs_effect_get_param_by_name(filter->effect, "opacity");
|
||||
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->chroma_param = gs_effect_get_param_by_name(
|
||||
filter->effect, "chroma_key");
|
||||
filter->pixel_size_param = gs_effect_get_param_by_name(
|
||||
filter->effect, "pixel_size");
|
||||
filter->similarity_param = gs_effect_get_param_by_name(
|
||||
filter->effect, "similarity");
|
||||
filter->smoothness_param = gs_effect_get_param_by_name(
|
||||
filter->effect, "smoothness");
|
||||
filter->spill_param =
|
||||
gs_effect_get_param_by_name(filter->effect, "spill");
|
||||
}
|
||||
|
||||
obs_leave_graphics();
|
||||
|
||||
bfree(effect_path);
|
||||
|
||||
if (!filter->effect) {
|
||||
chroma_key_destroy_v2(filter);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
chroma_key_update_v2(filter, settings);
|
||||
return filter;
|
||||
}
|
||||
|
||||
static void chroma_key_render_v1(void *data, gs_effect_t *effect)
|
||||
{
|
||||
struct chroma_key_filter_data *filter = data;
|
||||
obs_source_t *target = obs_filter_get_target(filter->context);
|
||||
@@ -216,6 +364,37 @@ static void chroma_key_render(void *data, gs_effect_t *effect)
|
||||
UNUSED_PARAMETER(effect);
|
||||
}
|
||||
|
||||
static void chroma_key_render_v2(void *data, gs_effect_t *effect)
|
||||
{
|
||||
struct chroma_key_filter_data_v2 *filter = data;
|
||||
obs_source_t *target = obs_filter_get_target(filter->context);
|
||||
uint32_t width = obs_source_get_base_width(target);
|
||||
uint32_t height = obs_source_get_base_height(target);
|
||||
struct vec2 pixel_size;
|
||||
|
||||
if (!obs_source_process_filter_begin(filter->context, GS_RGBA,
|
||||
OBS_ALLOW_DIRECT_RENDERING))
|
||||
return;
|
||||
|
||||
vec2_set(&pixel_size, 1.0f / (float)width, 1.0f / (float)height);
|
||||
|
||||
gs_effect_set_float(filter->opacity_param, filter->opacity);
|
||||
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_vec2(filter->chroma_param, &filter->chroma);
|
||||
gs_effect_set_vec2(filter->pixel_size_param, &pixel_size);
|
||||
gs_effect_set_float(filter->similarity_param, filter->similarity);
|
||||
gs_effect_set_float(filter->smoothness_param, filter->smoothness);
|
||||
gs_effect_set_float(filter->spill_param, filter->spill);
|
||||
|
||||
const bool previous = gs_set_linear_srgb(true);
|
||||
obs_source_process_filter_end(filter->context, filter->effect, 0, 0);
|
||||
gs_set_linear_srgb(previous);
|
||||
|
||||
UNUSED_PARAMETER(effect);
|
||||
}
|
||||
|
||||
static bool key_type_changed(obs_properties_t *props, obs_property_t *p,
|
||||
obs_data_t *settings)
|
||||
{
|
||||
@@ -229,7 +408,7 @@ static bool key_type_changed(obs_properties_t *props, obs_property_t *p,
|
||||
return true;
|
||||
}
|
||||
|
||||
static obs_properties_t *chroma_key_properties(void *data)
|
||||
static obs_properties_t *chroma_key_properties_v1(void *data)
|
||||
{
|
||||
obs_properties_t *props = obs_properties_create();
|
||||
|
||||
@@ -265,6 +444,42 @@ static obs_properties_t *chroma_key_properties(void *data)
|
||||
return props;
|
||||
}
|
||||
|
||||
static obs_properties_t *chroma_key_properties_v2(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("Magenta"), "magenta");
|
||||
obs_property_list_add_string(p, obs_module_text("Custom"), "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_slider(props, SETTING_SPILL, TEXT_SPILL, 1, 1000,
|
||||
1);
|
||||
|
||||
obs_properties_add_int_slider(props, SETTING_OPACITY, TEXT_OPACITY, 0,
|
||||
100, 1);
|
||||
obs_properties_add_float_slider(props, SETTING_CONTRAST, TEXT_CONTRAST,
|
||||
-4.0, 4.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 chroma_key_defaults(obs_data_t *settings)
|
||||
{
|
||||
obs_data_set_default_int(settings, SETTING_OPACITY, 100);
|
||||
@@ -281,12 +496,26 @@ static void chroma_key_defaults(obs_data_t *settings)
|
||||
struct obs_source_info chroma_key_filter = {
|
||||
.id = "chroma_key_filter",
|
||||
.type = OBS_SOURCE_TYPE_FILTER,
|
||||
.output_flags = OBS_SOURCE_VIDEO,
|
||||
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CAP_OBSOLETE,
|
||||
.get_name = chroma_key_name,
|
||||
.create = chroma_key_create,
|
||||
.destroy = chroma_key_destroy,
|
||||
.video_render = chroma_key_render,
|
||||
.update = chroma_key_update,
|
||||
.get_properties = chroma_key_properties,
|
||||
.create = chroma_key_create_v1,
|
||||
.destroy = chroma_key_destroy_v1,
|
||||
.video_render = chroma_key_render_v1,
|
||||
.update = chroma_key_update_v1,
|
||||
.get_properties = chroma_key_properties_v1,
|
||||
.get_defaults = chroma_key_defaults,
|
||||
};
|
||||
|
||||
struct obs_source_info chroma_key_filter_v2 = {
|
||||
.id = "chroma_key_filter",
|
||||
.version = 2,
|
||||
.type = OBS_SOURCE_TYPE_FILTER,
|
||||
.output_flags = OBS_SOURCE_VIDEO,
|
||||
.get_name = chroma_key_name,
|
||||
.create = chroma_key_create_v2,
|
||||
.destroy = chroma_key_destroy_v2,
|
||||
.video_render = chroma_key_render_v2,
|
||||
.update = chroma_key_update_v2,
|
||||
.get_properties = chroma_key_properties_v2,
|
||||
.get_defaults = chroma_key_defaults,
|
||||
};
|
||||
|
97
plugins/obs-filters/data/chroma_key_filter_v2.effect
Normal file
97
plugins/obs-filters/data/chroma_key_filter_v2.effect
Normal file
@@ -0,0 +1,97 @@
|
||||
uniform float4x4 ViewProj;
|
||||
uniform texture2d image;
|
||||
|
||||
uniform float4 cb_v4 = { -0.100644, -0.338572, 0.439216, 0.501961 };
|
||||
uniform float4 cr_v4 = { 0.439216, -0.398942, -0.040274, 0.501961 };
|
||||
|
||||
uniform float opacity;
|
||||
uniform float contrast;
|
||||
uniform float brightness;
|
||||
uniform float gamma;
|
||||
|
||||
uniform float2 chroma_key;
|
||||
uniform float2 pixel_size;
|
||||
uniform float similarity;
|
||||
uniform float smoothness;
|
||||
uniform float spill;
|
||||
|
||||
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, gamma) * contrast + brightness, rgba.a);
|
||||
}
|
||||
|
||||
float GetChromaDist(float3 rgb)
|
||||
{
|
||||
float cb = dot(rgb.rgb, cb_v4.xyz) + cb_v4.w;
|
||||
float cr = dot(rgb.rgb, cr_v4.xyz) + cr_v4.w;
|
||||
return distance(chroma_key, float2(cr, cb));
|
||||
}
|
||||
|
||||
float4 SampleTexture(float2 uv)
|
||||
{
|
||||
return image.Sample(textureSampler, uv);
|
||||
}
|
||||
|
||||
float GetBoxFilteredChromaDist(float3 rgb, float2 texCoord)
|
||||
{
|
||||
float2 h_pixel_size = pixel_size / 2.0;
|
||||
float2 point_0 = float2(pixel_size.x, h_pixel_size.y);
|
||||
float2 point_1 = float2(h_pixel_size.x, -pixel_size.y);
|
||||
float distVal = GetChromaDist(SampleTexture(texCoord-point_0).rgb);
|
||||
distVal += GetChromaDist(SampleTexture(texCoord+point_0).rgb);
|
||||
distVal += GetChromaDist(SampleTexture(texCoord-point_1).rgb);
|
||||
distVal += GetChromaDist(SampleTexture(texCoord+point_1).rgb);
|
||||
distVal *= 2.0;
|
||||
distVal += GetChromaDist(rgb);
|
||||
return distVal / 9.0;
|
||||
}
|
||||
|
||||
float4 ProcessChromaKey(float4 rgba, VertData v_in)
|
||||
{
|
||||
float chromaDist = GetBoxFilteredChromaDist(rgba.rgb, v_in.uv);
|
||||
float baseMask = chromaDist - similarity;
|
||||
float fullMask = pow(saturate(baseMask / smoothness), 1.5);
|
||||
float spillVal = pow(saturate(baseMask / spill), 1.5);
|
||||
|
||||
rgba.a *= opacity;
|
||||
rgba.a *= fullMask;
|
||||
|
||||
float desat = dot(rgba.rgb, float3(0.2126, 0.7152, 0.0722));
|
||||
rgba.rgb = lerp(float3(desat, desat, desat), rgba.rgb, spillVal);
|
||||
|
||||
return CalcColor(rgba);
|
||||
}
|
||||
|
||||
float4 PSChromaKeyRGBA(VertData v_in) : TARGET
|
||||
{
|
||||
float4 rgba = image.Sample(textureSampler, v_in.uv);
|
||||
return ProcessChromaKey(rgba, v_in);
|
||||
}
|
||||
|
||||
technique Draw
|
||||
{
|
||||
pass
|
||||
{
|
||||
vertex_shader = VSDefault(v_in);
|
||||
pixel_shader = PSChromaKeyRGBA(v_in);
|
||||
}
|
||||
}
|
@@ -19,6 +19,7 @@ extern struct obs_source_info color_key_filter;
|
||||
extern struct obs_source_info color_grade_filter;
|
||||
extern struct obs_source_info sharpness_filter;
|
||||
extern struct obs_source_info chroma_key_filter;
|
||||
extern struct obs_source_info chroma_key_filter_v2;
|
||||
extern struct obs_source_info async_delay_filter;
|
||||
#if NOISEREDUCTION_ENABLED
|
||||
extern struct obs_source_info noise_suppress_filter;
|
||||
@@ -44,6 +45,7 @@ bool obs_module_load(void)
|
||||
obs_register_source(&color_grade_filter);
|
||||
obs_register_source(&sharpness_filter);
|
||||
obs_register_source(&chroma_key_filter);
|
||||
obs_register_source(&chroma_key_filter_v2);
|
||||
obs_register_source(&async_delay_filter);
|
||||
#if NOISEREDUCTION_ENABLED
|
||||
obs_register_source(&noise_suppress_filter);
|
||||
|
Reference in New Issue
Block a user