diff --git a/plugins/obs-filters/color-correction-filter.c b/plugins/obs-filters/color-correction-filter.c index 6723d6fd9..a49d11961 100644 --- a/plugins/obs-filters/color-correction-filter.c +++ b/plugins/obs-filters/color-correction-filter.c @@ -63,6 +63,27 @@ struct color_correction_filter_data { struct vec3 half_unit; }; +struct color_correction_filter_data_v2 { + obs_source_t *context; + + gs_effect_t *effect; + + gs_eparam_t *gamma_param; + gs_eparam_t *final_matrix_param; + + float gamma; + + /* Pre-Computes */ + struct matrix4 con_matrix; + struct matrix4 bright_matrix; + struct matrix4 sat_matrix; + struct matrix4 hue_op_matrix; + struct matrix4 color_matrix; + struct matrix4 final_matrix; + + struct vec3 half_unit; +}; + static const float root3 = 0.57735f; static const float red_weight = 0.299f; static const float green_weight = 0.587f; @@ -78,10 +99,166 @@ static const char *color_correction_filter_name(void *unused) return obs_module_text("ColorFilter"); } -static void color_correction_filter_update(void *data, obs_data_t *settings) +/* + * This function is called (see bottom of this file for more details) + * whenever the OBS filter interface changes. So when the user is messing + * with a slider this function is called to update the internal settings + * in OBS, and hence the settings being passed to the CPU/GPU. + */ +static void color_correction_filter_update_v1(void *data, obs_data_t *settings) { struct color_correction_filter_data *filter = data; - uint32_t version = obs_source_get_version(filter->context); + + /* Build our Gamma numbers. */ + 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; + + /* Build our contrast number. */ + float contrast = + (float)obs_data_get_double(settings, SETTING_CONTRAST) + 1.0f; + float one_minus_con = (1.0f - contrast) / 2.0f; + + /* Now let's build our Contrast matrix. */ + filter->con_matrix = (struct matrix4){ + contrast, 0.0f, 0.0f, 0.0f, + 0.0f, contrast, 0.0f, 0.0f, + 0.0f, 0.0f, contrast, 0.0f, + one_minus_con, one_minus_con, one_minus_con, 1.0f}; + + /* Build our brightness number. */ + float brightness = + (float)obs_data_get_double(settings, SETTING_BRIGHTNESS); + + /* + * Now let's build our Brightness matrix. + * Earlier (in the function color_correction_filter_create) we set + * this matrix to the identity matrix, so now we only need + * to set the 3 variables that have changed. + */ + filter->bright_matrix.t.x = brightness; + filter->bright_matrix.t.y = brightness; + filter->bright_matrix.t.z = brightness; + + /* Build our Saturation number. */ + float saturation = + (float)obs_data_get_double(settings, SETTING_SATURATION) + 1.0f; + + /* Factor in the selected color weights. */ + float one_minus_sat_red = (1.0f - saturation) * red_weight; + float one_minus_sat_green = (1.0f - saturation) * green_weight; + float one_minus_sat_blue = (1.0f - saturation) * blue_weight; + float sat_val_red = one_minus_sat_red + saturation; + float sat_val_green = one_minus_sat_green + saturation; + float sat_val_blue = one_minus_sat_blue + saturation; + + /* Now we build our Saturation matrix. */ + filter->sat_matrix = (struct matrix4){sat_val_red, + one_minus_sat_red, + one_minus_sat_red, + 0.0f, + one_minus_sat_green, + sat_val_green, + one_minus_sat_green, + 0.0f, + one_minus_sat_blue, + one_minus_sat_blue, + sat_val_blue, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f}; + + /* Build our Hue number. */ + float hue_shift = + (float)obs_data_get_double(settings, SETTING_HUESHIFT); + + /* Build our Transparency number. */ + float opacity = + (float)obs_data_get_int(settings, SETTING_OPACITY) * 0.01f; + + /* Hue is the radian of 0 to 360 degrees. */ + float half_angle = 0.5f * (float)(hue_shift / (180.0f / M_PI)); + + /* Pseudo-Quaternion To Matrix. */ + float rot_quad1 = root3 * (float)sin(half_angle); + struct vec3 rot_quaternion; + vec3_set(&rot_quaternion, rot_quad1, rot_quad1, rot_quad1); + float rot_quaternion_w = (float)cos(half_angle); + + struct vec3 cross; + vec3_mul(&cross, &rot_quaternion, &rot_quaternion); + struct vec3 square; + vec3_mul(&square, &rot_quaternion, &rot_quaternion); + struct vec3 wimag; + vec3_mulf(&wimag, &rot_quaternion, rot_quaternion_w); + + vec3_mulf(&square, &square, 2.0f); + struct vec3 diag; + vec3_sub(&diag, &filter->half_unit, &square); + struct vec3 a_line; + vec3_add(&a_line, &cross, &wimag); + struct vec3 b_line; + vec3_sub(&b_line, &cross, &wimag); + + /* Now we build our Hue and Opacity matrix. */ + filter->hue_op_matrix = (struct matrix4){diag.x * 2.0f, + b_line.z * 2.0f, + a_line.y * 2.0f, + 0.0f, + + a_line.z * 2.0f, + diag.y * 2.0f, + b_line.x * 2.0f, + 0.0f, + + b_line.y * 2.0f, + a_line.x * 2.0f, + diag.z * 2.0f, + 0.0f, + + 0.0f, + 0.0f, + 0.0f, + opacity}; + + /* Now get the overlay color data. */ + uint32_t color = (uint32_t)obs_data_get_int(settings, SETTING_COLOR); + struct vec4 color_v4; + vec4_from_rgba(&color_v4, color); + + /* + * Now let's build our Color 'overlay' matrix. + * Earlier (in the function color_correction_filter_create) we set + * this matrix to the identity matrix, so now we only need + * to set the 6 variables that have changed. + */ + filter->color_matrix.x.x = color_v4.x; + filter->color_matrix.y.y = color_v4.y; + filter->color_matrix.z.z = color_v4.z; + + filter->color_matrix.t.x = color_v4.w * color_v4.x; + filter->color_matrix.t.y = color_v4.w * color_v4.y; + filter->color_matrix.t.z = color_v4.w * color_v4.z; + + /* First we apply the Contrast & Brightness matrix. */ + matrix4_mul(&filter->final_matrix, &filter->bright_matrix, + &filter->con_matrix); + /* Now we apply the Saturation matrix. */ + matrix4_mul(&filter->final_matrix, &filter->final_matrix, + &filter->sat_matrix); + /* Next we apply the Hue+Opacity matrix. */ + matrix4_mul(&filter->final_matrix, &filter->final_matrix, + &filter->hue_op_matrix); + /* Lastly we apply the Color Wash matrix. */ + matrix4_mul(&filter->final_matrix, &filter->final_matrix, + &filter->color_matrix); +} + +static void color_correction_filter_update_v2(void *data, obs_data_t *settings) +{ + struct color_correction_filter_data_v2 *filter = data; /* Build our Gamma numbers. */ double gamma = obs_data_get_double(settings, SETTING_GAMMA); @@ -148,13 +325,7 @@ static void color_correction_filter_update(void *data, obs_data_t *settings) (float)obs_data_get_double(settings, SETTING_HUESHIFT); /* Build our Transparency number. */ - float opacity = 1.0f; - - if (version == 1) - opacity = (float)obs_data_get_int(settings, SETTING_OPACITY) * - 0.01f; - else if (version == 2) - opacity = (float)obs_data_get_double(settings, SETTING_OPACITY); + float opacity = (float)obs_data_get_double(settings, SETTING_OPACITY); /* Hue is the radian of 0 to 360 degrees. */ float half_angle = 0.5f * (float)(hue_shift / (180.0f / M_PI)); @@ -201,38 +372,31 @@ static void color_correction_filter_update(void *data, obs_data_t *settings) 0.0f, opacity}; - if (version == 1) { - uint32_t color = - (uint32_t)obs_data_get_int(settings, SETTING_COLOR); - struct vec4 color_v4; - vec4_from_rgba(&color_v4, color); + /* Now get the overlay color multiply data. */ + uint32_t color_multiply = + (uint32_t)obs_data_get_int(settings, SETTING_COLOR_MULTIPLY); + struct vec4 color_multiply_v4; + vec4_from_rgba_srgb(&color_multiply_v4, color_multiply); - filter->color_matrix.x.x = color_v4.x; - filter->color_matrix.y.y = color_v4.y; - filter->color_matrix.z.z = color_v4.z; + /* Now get the overlay color add data. */ + uint32_t color_add = + (uint32_t)obs_data_get_int(settings, SETTING_COLOR_ADD); + struct vec4 color_add_v4; + vec4_from_rgba_srgb(&color_add_v4, color_add); - filter->color_matrix.t.x = color_v4.w * color_v4.x; - filter->color_matrix.t.y = color_v4.w * color_v4.y; - filter->color_matrix.t.z = color_v4.w * color_v4.z; - } else if (version == 2) { - uint32_t color_multiply = (uint32_t)obs_data_get_int( - settings, SETTING_COLOR_MULTIPLY); - struct vec4 color_multiply_v4; - vec4_from_rgba_srgb(&color_multiply_v4, color_multiply); + /* + * Now let's build our Color 'overlay' matrix. + * Earlier (in the function color_correction_filter_create) we set + * this matrix to the identity matrix, so now we only need + * to set the 6 variables that have changed. + */ + filter->color_matrix.x.x = color_multiply_v4.x; + filter->color_matrix.y.y = color_multiply_v4.y; + filter->color_matrix.z.z = color_multiply_v4.z; - uint32_t color_add = - (uint32_t)obs_data_get_int(settings, SETTING_COLOR_ADD); - struct vec4 color_add_v4; - vec4_from_rgba_srgb(&color_add_v4, color_add); - - filter->color_matrix.x.x = color_multiply_v4.x; - filter->color_matrix.y.y = color_multiply_v4.y; - filter->color_matrix.z.z = color_multiply_v4.z; - - filter->color_matrix.t.x = color_add_v4.x; - filter->color_matrix.t.y = color_add_v4.y; - filter->color_matrix.t.z = color_add_v4.z; - } + filter->color_matrix.t.x = color_add_v4.x; + filter->color_matrix.t.y = color_add_v4.y; + filter->color_matrix.t.z = color_add_v4.z; /* First we apply the Contrast & Brightness matrix. */ matrix4_mul(&filter->final_matrix, &filter->con_matrix, @@ -253,7 +417,7 @@ static void color_correction_filter_update(void *data, obs_data_t *settings) * OBS. Jim has added several useful functions to help keep memory leaks to * a minimum, and handle the destruction and construction of these filters. */ -static void color_correction_filter_destroy(void *data) +static void color_correction_filter_destroy_v1(void *data) { struct color_correction_filter_data *filter = data; @@ -266,14 +430,27 @@ static void color_correction_filter_destroy(void *data) bfree(data); } +static void color_correction_filter_destroy_v2(void *data) +{ + struct color_correction_filter_data_v2 *filter = data; + + if (filter->effect) { + obs_enter_graphics(); + gs_effect_destroy(filter->effect); + obs_leave_graphics(); + } + + bfree(data); +} + /* * When you apply a filter OBS creates it, and adds it to the source. OBS also * starts rendering it immediately. This function doesn't just 'create' the * filter, it also calls the render function (farther below) that contains the * actual rendering code. */ -static void *color_correction_filter_create(obs_data_t *settings, - obs_source_t *context) +static void *color_correction_filter_create_v1(obs_data_t *settings, + obs_source_t *context) { /* * Because of limitations of pre-c99 compilers, you can't create an @@ -321,7 +498,7 @@ static void *color_correction_filter_create(obs_data_t *settings, * values that don't exist anymore. */ if (!filter->effect) { - color_correction_filter_destroy(filter); + color_correction_filter_destroy_v1(filter); return NULL; } @@ -330,12 +507,74 @@ static void *color_correction_filter_create(obs_data_t *settings, * we could end up with the user controlled sliders and values * updating, but the visuals not updating to match. */ - color_correction_filter_update(filter, settings); + color_correction_filter_update_v1(filter, settings); + return filter; +} + +static void *color_correction_filter_create_v2(obs_data_t *settings, + obs_source_t *context) +{ + /* + * Because of limitations of pre-c99 compilers, you can't create an + * array that doesn't have a known size at compile time. The below + * function calculates the size needed and allocates memory to + * handle the source. + */ + struct color_correction_filter_data_v2 *filter = + bzalloc(sizeof(struct color_correction_filter_data_v2)); + + /* + * By default the effect file is stored in the ./data directory that + * your filter resides in. + */ + char *effect_path = obs_module_file("color_correction_filter.effect"); + + filter->context = context; + + /* Set/clear/assign for all necessary vectors. */ + vec3_set(&filter->half_unit, 0.5f, 0.5f, 0.5f); + matrix4_identity(&filter->bright_matrix); + matrix4_identity(&filter->color_matrix); + + /* Here we enter the GPU drawing/shader portion of our code. */ + obs_enter_graphics(); + + /* Load the shader on the GPU. */ + filter->effect = gs_effect_create_from_file(effect_path, NULL); + + /* If the filter is active pass the parameters to the filter. */ + if (filter->effect) { + filter->gamma_param = gs_effect_get_param_by_name( + filter->effect, SETTING_GAMMA); + filter->final_matrix_param = gs_effect_get_param_by_name( + filter->effect, "color_matrix"); + } + + obs_leave_graphics(); + + bfree(effect_path); + + /* + * If the filter has been removed/deactivated, destroy the filter + * and exit out so we don't crash OBS by telling it to update + * values that don't exist anymore. + */ + if (!filter->effect) { + color_correction_filter_destroy_v2(filter); + return NULL; + } + + /* + * It's important to call the update function here. If we don't + * we could end up with the user controlled sliders and values + * updating, but the visuals not updating to match. + */ + color_correction_filter_update_v2(filter, settings); return filter; } /* This is where the actual rendering of the filter takes place. */ -static void color_correction_filter_render(void *data, gs_effect_t *effect) +static void color_correction_filter_render_v1(void *data, gs_effect_t *effect) { struct color_correction_filter_data *filter = data; @@ -358,13 +597,60 @@ static void color_correction_filter_render(void *data, gs_effect_t *effect) UNUSED_PARAMETER(effect); } +static void color_correction_filter_render_v2(void *data, gs_effect_t *effect) +{ + struct color_correction_filter_data_v2 *filter = data; + + if (!obs_source_process_filter_begin(filter->context, GS_RGBA, + OBS_ALLOW_DIRECT_RENDERING)) + return; + + /* Now pass the interface variables to the .effect file. */ + gs_effect_set_float(filter->gamma_param, filter->gamma); + gs_effect_set_matrix4(filter->final_matrix_param, + &filter->final_matrix); + + gs_blend_state_push(); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA); + + obs_source_process_filter_end(filter->context, filter->effect, 0, 0); + + gs_blend_state_pop(); + + UNUSED_PARAMETER(effect); +} + /* * This function sets the interface. the types (add_*_Slider), the type of * data collected (int), the internal name, user-facing name, minimum, * maximum and step values. While a custom interface can be built, for a * simple filter like this it's better to use the supplied functions. */ -static obs_properties_t *color_correction_filter_properties(uint32_t version) +static obs_properties_t *color_correction_filter_properties_v1(void *data) +{ + obs_properties_t *props = obs_properties_create(); + + obs_properties_add_float_slider(props, SETTING_GAMMA, TEXT_GAMMA, -3.0, + 3.0, 0.01); + + obs_properties_add_float_slider(props, SETTING_CONTRAST, TEXT_CONTRAST, + -2.0, 2.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_SATURATION, + TEXT_SATURATION, -1.0, 5.0, 0.01); + obs_properties_add_float_slider(props, SETTING_HUESHIFT, TEXT_HUESHIFT, + -180.0, 180.0, 0.01); + obs_properties_add_int_slider(props, SETTING_OPACITY, TEXT_OPACITY, 0, + 100, 1); + + obs_properties_add_color_alpha(props, SETTING_COLOR, TEXT_COLOR); + + UNUSED_PARAMETER(data); + return props; +} + +static obs_properties_t *color_correction_filter_properties_v2(void *data) { obs_properties_t *props = obs_properties_create(); @@ -379,37 +665,17 @@ static obs_properties_t *color_correction_filter_properties(uint32_t version) TEXT_SATURATION, -1.0, 5.0, 0.01); obs_properties_add_float_slider(props, SETTING_HUESHIFT, TEXT_HUESHIFT, -180.0, 180.0, 0.01); + obs_properties_add_float_slider(props, SETTING_OPACITY, TEXT_OPACITY, + 0.0, 1.0, 0.0001); - if (version == 1) { - obs_properties_add_int_slider(props, SETTING_OPACITY, - TEXT_OPACITY, 0, 100, 1); - obs_properties_add_color_alpha(props, SETTING_COLOR, - TEXT_COLOR); - } else if (version == 2) { - obs_properties_add_float_slider(props, SETTING_OPACITY, - TEXT_OPACITY, 0.0, 1.0, 0.0001); - - obs_properties_add_color(props, SETTING_COLOR_MULTIPLY, - TEXT_COLOR_MULTIPLY); - obs_properties_add_color(props, SETTING_COLOR_ADD, - TEXT_COLOR_ADD); - } + obs_properties_add_color(props, SETTING_COLOR_MULTIPLY, + TEXT_COLOR_MULTIPLY); + obs_properties_add_color(props, SETTING_COLOR_ADD, TEXT_COLOR_ADD); + UNUSED_PARAMETER(data); return props; } -static obs_properties_t *color_correction_filter_properties_v1(void *data) -{ - UNUSED_PARAMETER(data); - return color_correction_filter_properties(1); -} - -static obs_properties_t *color_correction_filter_properties_v2(void *data) -{ - UNUSED_PARAMETER(data); - return color_correction_filter_properties(2); -} - /* * As the functions' namesake, this provides the default settings for any * options you wish to provide a default for. Try to select defaults that @@ -417,27 +683,24 @@ static obs_properties_t *color_correction_filter_properties_v2(void *data) * *NOTE* this function is completely optional, as is providing a default * for any particular setting. */ -static void color_correction_filter_defaults(obs_data_t *settings) +static void color_correction_filter_defaults_v1(obs_data_t *settings) { obs_data_set_default_double(settings, SETTING_GAMMA, 0.0); 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_SATURATION, 0.0); obs_data_set_default_double(settings, SETTING_HUESHIFT, 0.0); -} - -static void color_correction_filter_defaults_v1(obs_data_t *settings) -{ - color_correction_filter_defaults(settings); - obs_data_set_default_int(settings, SETTING_OPACITY, 100); obs_data_set_default_int(settings, SETTING_COLOR, 0x00FFFFFF); } static void color_correction_filter_defaults_v2(obs_data_t *settings) { - color_correction_filter_defaults(settings); - + obs_data_set_default_double(settings, SETTING_GAMMA, 0.0); + 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_SATURATION, 0.0); + obs_data_set_default_double(settings, SETTING_HUESHIFT, 0.0); obs_data_set_default_double(settings, SETTING_OPACITY, 1.0); obs_data_set_default_int(settings, SETTING_COLOR_MULTIPLY, 0x00FFFFFF); obs_data_set_default_int(settings, SETTING_COLOR_ADD, 0x00000000); @@ -458,10 +721,10 @@ struct obs_source_info color_filter = { .type = OBS_SOURCE_TYPE_FILTER, .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CAP_OBSOLETE, .get_name = color_correction_filter_name, - .create = color_correction_filter_create, - .destroy = color_correction_filter_destroy, - .video_render = color_correction_filter_render, - .update = color_correction_filter_update, + .create = color_correction_filter_create_v1, + .destroy = color_correction_filter_destroy_v1, + .video_render = color_correction_filter_render_v1, + .update = color_correction_filter_update_v1, .get_properties = color_correction_filter_properties_v1, .get_defaults = color_correction_filter_defaults_v1, }; @@ -472,10 +735,10 @@ struct obs_source_info color_filter_v2 = { .type = OBS_SOURCE_TYPE_FILTER, .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB, .get_name = color_correction_filter_name, - .create = color_correction_filter_create, - .destroy = color_correction_filter_destroy, - .video_render = color_correction_filter_render, - .update = color_correction_filter_update, + .create = color_correction_filter_create_v2, + .destroy = color_correction_filter_destroy_v2, + .video_render = color_correction_filter_render_v2, + .update = color_correction_filter_update_v2, .get_properties = color_correction_filter_properties_v2, .get_defaults = color_correction_filter_defaults_v2, };