diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 24f23b14..6edfa6ef 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -9,6 +9,17 @@ FileType.Sounds="Sounds" FileType.Effect="Effect" FileType.Effects="Effects" +# Blur +Blur.Type.Box="Box" +Blur.Type.BoxLinear="Box Linear" +Blur.Type.Gaussian="Gaussian" +Blur.Type.GaussianLinear="Gaussian Linear" +Blur.Type.DualFiltering="Dual Filtering" +Blur.Subtype.Area="Area" +Blur.Subtype.Directional="Directional" +Blur.Subtype.Rotational="Rotational" +Blur.Subtype.Zoom="Zoom" + # Mip Generator MipGenerator="Mipmap Generator" MipGenerator.Description="Which mip generator should be used?" @@ -38,40 +49,26 @@ CustomShader.Texture.Type.Source="Source" # Filter - Blur Filter.Blur="Blur" Filter.Blur.Type="Type" -Filter.Blur.Type.Description="The type of blur to apply:\n- 'Box' smoothes pixels equally.\n- 'Box Linear' uses linear sampling to reduce the GPU usage, sacrificing minimal quality.\n- 'Gaussian' applies a gaussian curve to the smoothed pixels.\n- 'Gaussian Linear' again uses linear sampling to reduce the total samples, sacrificing more quality this time.\n- 'Bilateral' is an edge detection variant of 'Gaussian'." +Filter.Blur.Type.Description="The type of blur to apply." Filter.Blur.Type.Box="Box" Filter.Blur.Type.BoxLinear="Box Linear" Filter.Blur.Type.Gaussian="Gaussian" Filter.Blur.Type.GaussianLinear="Gaussian Linear" Filter.Blur.Type.Bilateral="Bilateral" -Filter.Blur.Size="Size (Pixel)" -Filter.Blur.Size.Description="Area size of the blur, large sizes may cause:\n- Skipped frames\n- Frame loss or drops\n- Input lag\n- GPU overheating\n- or other issues." -Filter.Blur.Bilateral.Smoothing="Smoothing" -Filter.Blur.Bilateral.Sharpness="Sharpness" -Filter.Blur.Directional="Directional Blur" -Filter.Blur.Directional.Description="Change the Blur into a Directional Blur. May not work with all Blur Types." -Filter.Blur.Directional.Angle="Angle" -Filter.Blur.Directional.Angle.Description="The angle in degrees for the Directional Blur." +Filter.Blur.Subtype="Subtype" +Filter.Blur.Subtype.Description="The way this blur should be applied." +Filter.Blur.Size="Size" +Filter.Blur.Size.Description="Size of the blur filter to apply. Large sizes may have a negative effect on performance." +Filter.Blur.Angle="Angle (Degrees)" +Filter.Blur.Angle.Description="Angle of the Blur" +Filter.Blur.Center.X="Center (X) (Percent)" +Filter.Blur.Center.X.Description="The horizontal center of the blur effect, in percent." +Filter.Blur.Center.Y="Center (Y) (Percent)" +Filter.Blur.Center.Y.Description="The vertical center of the blur effect, in percent." Filter.Blur.StepScale="Step Scaling" Filter.Blur.StepScale.Description="Scale the texel step used in the Blur shader, which allows for smaller Blur sizes to cover more space, at the cost of some quality.\nCan be combined with Directional Blur to change the behavior drastically." Filter.Blur.StepScale.X="Step Scale X" Filter.Blur.StepScale.Y="Step Scale Y" -Filter.Blur.Region="Apply to Region only" -Filter.Blur.Region.Description="Only apply the blur to a region inside the source." -Filter.Blur.Region.Left="Left Edge" -Filter.Blur.Region.Left.Description="Distance to left edge of the source in percent." -Filter.Blur.Region.Top="Top Edge" -Filter.Blur.Region.Top.Description="Distance to top edge of the source in percent." -Filter.Blur.Region.Right="Right Edge" -Filter.Blur.Region.Right.Description="Distance to right edge of the source in percent." -Filter.Blur.Region.Bottom="Bottom Edge" -Filter.Blur.Region.Bottom.Description="Distance to bottom edge of the source in percent." -Filter.Blur.Region.Feather="Feather Area" -Filter.Blur.Region.Feather.Description="Size of the smoothing area in percent, or 0 to turn off feather." -Filter.Blur.Region.Feather.Shift="Feather Shift" -Filter.Blur.Region.Feather.Shift.Description="Shift of the Feather area, positive is inwards, negative is outwards." -Filter.Blur.Region.Invert="Invert Region" -Filter.Blur.Region.Invert.Description="Invert the region so that everything but this area is blurred." Filter.Blur.Mask="Apply a Mask" Filter.Blur.Mask.Description="Apply a mask to the area that needs to be blurred, which allows for more control over the blurred area." Filter.Blur.Mask.Type="Mask Type" @@ -103,7 +100,6 @@ Filter.Blur.Mask.Alpha="Mask Alpha Filter" Filter.Blur.Mask.Alpha.Description="Filter the mask by this alpha value before applying it." Filter.Blur.Mask.Multiplier="Mask Multiplier" Filter.Blur.Mask.Multiplier.Description="Multiply the final mask value by this value." -Filter.Blur.ColorFormat="Color Format" # Filter - Custom Shader Filter.CustomShader="Custom Shader" diff --git a/source/filters/filter-blur.cpp b/source/filters/filter-blur.cpp index 82ec8df3..fe3d456e 100644 --- a/source/filters/filter-blur.cpp +++ b/source/filters/filter-blur.cpp @@ -22,6 +22,11 @@ #include #include #include +#include "gfx/blur/gfx-blur-box-linear.hpp" +#include "gfx/blur/gfx-blur-box.hpp" +#include "gfx/blur/gfx-blur-dual-filtering.hpp" +#include "gfx/blur/gfx-blur-gaussian-linear.hpp" +#include "gfx/blur/gfx-blur-gaussian.hpp" #include "obs/obs-source-tracker.hpp" #include "strings.hpp" #include "util-math.hpp" @@ -43,16 +48,12 @@ #define SOURCE_NAME "Filter.Blur" #define P_TYPE "Filter.Blur.Type" -#define P_TYPE_BOX "Filter.Blur.Type.Box" -#define P_TYPE_BOXLINEAR "Filter.Blur.Type.BoxLinear" -#define P_TYPE_GAUSSIAN "Filter.Blur.Type.Gaussian" -#define P_TYPE_GAUSSIANLINEAR "Filter.Blur.Type.GaussianLinear" -#define P_TYPE_BILATERAL "Filter.Blur.Type.Bilateral" +#define P_SUBTYPE "Filter.Blur.SubType" #define P_SIZE "Filter.Blur.Size" -#define P_BILATERAL_SMOOTHING "Filter.Blur.Bilateral.Smoothing" -#define P_BILATERAL_SHARPNESS "Filter.Blur.Bilateral.Sharpness" -#define P_DIRECTIONAL "Filter.Blur.Directional" -#define P_DIRECTIONAL_ANGLE "Filter.Blur.Directional.Angle" +#define P_ANGLE "Filter.Blur.Angle" +#define P_CENTER "Filter.Blur.Center" +#define P_CENTER_X "Filter.Blur.Center.X" +#define P_CENTER_Y "Filter.Blur.Center.Y" #define P_STEPSCALE "Filter.Blur.StepScale" #define P_STEPSCALE_X "Filter.Blur.StepScale.X" #define P_STEPSCALE_Y "Filter.Blur.StepScale.Y" @@ -73,17 +74,29 @@ #define P_MASK_COLOR "Filter.Blur.Mask.Color" #define P_MASK_ALPHA "Filter.Blur.Mask.Alpha" #define P_MASK_MULTIPLIER "Filter.Blur.Mask.Multiplier" -#define P_COLORFORMAT "Filter.Blur.ColorFormat" -enum ColorFormat : uint64_t { // ToDo: Refactor into full class. - RGB, - YUV, // 701 +struct local_blur_type_t { + std::function<::gfx::blur::ifactory&()> fn; + const char* name; +}; +struct local_blur_subtype_t { + ::gfx::blur::type type; + const char* name; }; -static uint8_t const max_kernel_size = 25; -// Search density for proper gaussian curve size. Lower means more accurate, but takes more time to calculate. -static double_t const search_density = 1. / 5000.; -static double_t const search_threshold = 1. / 256.; +std::map list_of_types = { + {"box", {&::gfx::blur::box_factory::get, S_BLUR_TYPE_BOX}}, + {"box_linear", {&::gfx::blur::box_linear_factory::get, S_BLUR_TYPE_BOX_LINEAR}}, + {"gaussian", {&::gfx::blur::gaussian_factory::get, S_BLUR_TYPE_GAUSSIAN}}, + {"gaussian_linear", {&::gfx::blur::gaussian_linear_factory::get, S_BLUR_TYPE_GAUSSIAN_LINEAR}}, + {"dual_filtering", {&::gfx::blur::dual_filtering_factory::get, S_BLUR_TYPE_DUALFILTERING}}, +}; +std::map list_of_subtypes = { + {"area", {::gfx::blur::type::Area, S_BLUR_SUBTYPE_AREA}}, + {"directional", {::gfx::blur::type::Directional, S_BLUR_SUBTYPE_DIRECTIONAL}}, + {"rotational", {::gfx::blur::type::Rotational, S_BLUR_SUBTYPE_ROTATIONAL}}, + {"zoom", {::gfx::blur::type::Zoom, S_BLUR_SUBTYPE_ZOOM}}, +}; // Initializer & Finalizer INITIALIZER(filterBlurFactoryInitializer) @@ -130,22 +143,11 @@ filter::blur::blur_factory::blur_factory() obs_register_source(&source_info); - P_LOG_INFO(" Precalculating Gaussian Blur Kernel..."); - this->gaussian_widths.resize(max_kernel_size + 1); - for (size_t w = 1; w <= max_kernel_size; w++) { - for (double_t h = FLT_EPSILON; h <= w; h += search_density) { - if (util::math::gaussian(double_t(w), h) > search_threshold) { - this->gaussian_widths[w] = h; - break; - } - } - } - // Translation Cache /// File Filter for Images translation_map.insert({std::string("image-filter"), std::string(P_TRANSLATE(S_FILETYPE_IMAGES)) - + std::string(" (" T_FILEFILTERS_IMAGE ");;") - + std::string("* (*.*)")}); + + std::string(" (" T_FILEFILTERS_IMAGE ");;") + + std::string("* (*.*)")}); } filter::blur::blur_factory::~blur_factory() {} @@ -154,15 +156,6 @@ void filter::blur::blur_factory::on_list_fill() { obs_enter_graphics(); - { - char* file = obs_module_file("effects/blur.effect"); - try { - blur_effect = std::make_shared(file); - } catch (std::runtime_error ex) { - P_LOG_ERROR(" Loading effect '%s' failed with error(s): %s", file, ex.what()); - } - bfree(file); - } { char* file = obs_module_file("effects/color-conversion.effect"); try { @@ -182,51 +175,17 @@ void filter::blur::blur_factory::on_list_fill() bfree(file); } - generate_kernel_textures(); obs_leave_graphics(); } void filter::blur::blur_factory::on_list_empty() { obs_enter_graphics(); - blur_effect.reset(); color_converter_effect.reset(); mask_effect.reset(); obs_leave_graphics(); } -void filter::blur::blur_factory::generate_gaussian_kernels() -{ - // 2D texture, horizontal is value, vertical is kernel size. - size_t size_power_of_two = size_t(pow(2, util::math::get_power_of_two_exponent_ceil(max_kernel_size))); - std::vector math_data(size_power_of_two); - std::shared_ptr> kernel_data; - - for (size_t width = 1; width <= max_kernel_size; width++) { - kernel_data = std::make_shared>(size_power_of_two); - - // Calculate and normalize - double_t sum = 0; - for (size_t p = 0; p <= width; p++) { - math_data[p] = float_t(Gaussian1D(double_t(p), double_t(gaussian_widths[width]))); - sum += math_data[p] * (p > 0 ? 2 : 1); - } - - // Normalize to Texture Buffer - double_t inverse_sum = 1.0 / sum; - for (size_t p = 0; p <= width; p++) { - kernel_data->at(p) = float_t(math_data[p] * inverse_sum); - } - - gaussian_kernels.insert({uint8_t(width), kernel_data}); - } -} - -void filter::blur::blur_factory::generate_kernel_textures() -{ - generate_gaussian_kernels(); -} - std::string const& filter::blur::blur_factory::get_translation(std::string const key) { static std::string none(""); @@ -259,12 +218,18 @@ void filter::blur::blur_factory::destroy(void* inptr) void filter::blur::blur_factory::get_defaults(obs_data_t* data) { - obs_data_set_default_int(data, P_TYPE, filter::blur::type::Box); - obs_data_set_default_int(data, P_SIZE, 5); + // Type, Subtype + obs_data_set_default_string(data, P_TYPE, "box"); + obs_data_set_default_string(data, P_SUBTYPE, "area"); - // Bilateral Only - obs_data_set_default_double(data, P_BILATERAL_SMOOTHING, 50.0); - obs_data_set_default_double(data, P_BILATERAL_SHARPNESS, 90.0); + // Parameters + obs_data_set_default_int(data, P_SIZE, 5); + obs_data_set_default_double(data, P_ANGLE, 0.); + obs_data_set_default_double(data, P_CENTER_X, 50.); + obs_data_set_default_double(data, P_CENTER_Y, 50.); + obs_data_set_default_bool(data, P_STEPSCALE, false); + obs_data_set_default_double(data, P_STEPSCALE_X, 1.); + obs_data_set_default_double(data, P_STEPSCALE_Y, 1.); // Masking obs_data_set_default_bool(data, P_MASK, false); @@ -276,25 +241,14 @@ void filter::blur::blur_factory::get_defaults(obs_data_t* data) obs_data_set_default_double(data, P_MASK_REGION_FEATHER, 0.0); obs_data_set_default_double(data, P_MASK_REGION_FEATHER_SHIFT, 0.0); obs_data_set_default_bool(data, P_MASK_REGION_INVERT, false); - char* default_file = obs_module_file("white.png"); - obs_data_set_default_string(data, P_MASK_IMAGE, default_file); - bfree(default_file); + { + char* default_file = obs_module_file("white.png"); + obs_data_set_default_string(data, P_MASK_IMAGE, default_file); + bfree(default_file); + } obs_data_set_default_string(data, P_MASK_SOURCE, ""); obs_data_set_default_int(data, P_MASK_COLOR, 0xFFFFFFFFull); obs_data_set_default_double(data, P_MASK_MULTIPLIER, 1.0); - - // Directional Blur - obs_data_set_default_bool(data, P_DIRECTIONAL, false); - obs_data_set_default_double(data, P_DIRECTIONAL_ANGLE, 0.0); - - // Scaling - obs_data_set_default_bool(data, P_STEPSCALE, false); - obs_data_set_default_double(data, P_STEPSCALE_X, 100.0); - obs_data_set_default_double(data, P_STEPSCALE_Y, 100.0); - - // Advanced - obs_data_set_default_bool(data, S_ADVANCED, false); - obs_data_set_default_int(data, P_COLORFORMAT, ColorFormat::RGB); } obs_properties_t* filter::blur::blur_factory::get_properties(void* inptr) @@ -347,28 +301,6 @@ void filter::blur::blur_factory::video_render(void* inptr, gs_effect_t* effect) reinterpret_cast(inptr)->video_render(effect); } -std::shared_ptr filter::blur::blur_factory::get_effect(filter::blur::type) -{ - return blur_effect; -} - -std::string filter::blur::blur_factory::get_technique(filter::blur::type type) -{ - switch (type) { - case type::Box: - return "Box"; - case type::Gaussian: - return "Gaussian"; - case type::Bilateral: - return "Bilateral"; - case type::BoxLinear: - return "BoxLinear"; - case type::GaussianLinear: - return "GaussianLinear"; - } - return ""; -} - std::shared_ptr filter::blur::blur_factory::get_color_converter_effect() { return color_converter_effect; @@ -379,11 +311,6 @@ std::shared_ptr filter::blur::blur_factory::get_mask_effect() return mask_effect; } -std::shared_ptr> filter::blur::blur_factory::get_gaussian_kernel(uint8_t size) -{ - return gaussian_kernels.at(size); -} - filter::blur::blur_instance::blur_instance(obs_data_t* settings, obs_source_t* parent) : m_self(parent), m_source_rendered(false), m_output_rendered(false) { @@ -391,83 +318,23 @@ filter::blur::blur_instance::blur_instance(obs_data_t* settings, obs_source_t* p // Create RenderTargets try { - this->m_source_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); - this->m_output_rt1 = std::make_shared(GS_RGBA, GS_ZS_NONE); - this->m_output_rt2 = std::make_shared(GS_RGBA, GS_ZS_NONE); + this->m_source_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); + this->m_output_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); } catch (std::exception ex) { P_LOG_ERROR(" Failed to create rendertargets, error %s.", obs_source_get_name(m_self), ex.what()); } - // Get initial Blur effect. - m_blur_effect = filter::blur::blur_factory::get()->get_effect(filter::blur::type::Box); - update(settings); } filter::blur::blur_instance::~blur_instance() { this->m_mask.source.source_texture.reset(); - this->m_output_rt1.reset(); - this->m_output_rt2.reset(); this->m_source_rt.reset(); - this->m_blur_effect.reset(); this->m_output_texture.reset(); } -bool filter::blur::blur_instance::apply_shared_param(gs_texture_t* input, float texelX, float texelY) -{ - bool result = true; - - result = result && gs_set_param_texture(m_blur_effect->get_object(), "u_image", input); - - vec2 imageSize; - vec2_set(&imageSize, (float)gs_texture_get_width(input), (float)gs_texture_get_height(input)); - result = result && gs_set_param_float2(m_blur_effect->get_object(), "u_imageSize", &imageSize); - - vec2 imageTexelDelta; - vec2_set(&imageTexelDelta, 1.0f, 1.0f); - vec2_div(&imageTexelDelta, &imageTexelDelta, &imageSize); - result = result && gs_set_param_float2(m_blur_effect->get_object(), "u_imageTexel", &imageTexelDelta); - - vec2 texel; - vec2_set(&texel, texelX, texelY); - result = result && gs_set_param_float2(m_blur_effect->get_object(), "u_texelDelta", &texel); - - result = result && gs_set_param_int(m_blur_effect->get_object(), "u_radius", (int)m_blur_size); - result = result && gs_set_param_int(m_blur_effect->get_object(), "u_diameter", (int)(1 + (m_blur_size * 2))); - - return result; -} - -bool filter::blur::blur_instance::apply_bilateral_param() -{ - if (m_blur_type != type::Bilateral) - return false; - - if (m_blur_effect->has_parameter("bilateralSmoothing")) { - m_blur_effect->get_parameter("bilateralSmoothing") - .set_float((float)(m_blur_bilateral_smoothing * (1 + m_blur_size * 2))); - } - - if (m_blur_effect->has_parameter("bilateralSharpness")) { - m_blur_effect->get_parameter("bilateralSharpness").set_float((float)(1.0 - m_blur_bilateral_sharpness)); - } - - return true; -} - -bool filter::blur::blur_instance::apply_gaussian_param(uint8_t width) -{ - auto kernel = filter::blur::blur_factory::get()->get_gaussian_kernel(width); - - if (m_blur_effect->has_parameter("kernel")) { - m_blur_effect->get_parameter("kernel").set_float_array(&(kernel->front()), kernel->size()); - } - - return true; -} - bool filter::blur::blur_instance::apply_mask_parameters(std::shared_ptr effect, gs_texture_t* original_texture, gs_texture_t* blurred_texture) { @@ -533,215 +400,351 @@ bool filter::blur::blur_instance::apply_mask_parameters(std::shared_ptr(obs_data_get_int(settings, P_MASK_TYPE)); - bool show_region = (mtype == mask_type::Region) && show_mask; - bool show_image = (mtype == mask_type::Image) && show_mask; - bool show_source = (mtype == mask_type::Source) && show_mask; - obs_property_set_visible(obs_properties_get(props, P_MASK_TYPE), show_mask); - obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_LEFT), show_region); - obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_TOP), show_region); - obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_RIGHT), show_region); - obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_BOTTOM), show_region); - obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_FEATHER), show_region); - obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_FEATHER_SHIFT), show_region); - obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_INVERT), show_region); - obs_property_set_visible(obs_properties_get(props, P_MASK_IMAGE), show_image); - obs_property_set_visible(obs_properties_get(props, P_MASK_SOURCE), show_source); - obs_property_set_visible(obs_properties_get(props, P_MASK_COLOR), show_image || show_source); - obs_property_set_visible(obs_properties_get(props, P_MASK_ALPHA), show_image || show_source); - obs_property_set_visible(obs_properties_get(props, P_MASK_MULTIPLIER), show_image || show_source); + // Find new Type + auto type_found = list_of_types.find(vtype); + if (type_found == list_of_types.end()) { + return false; + } - // Directional Blur - bool show_directional = obs_data_get_bool(settings, P_DIRECTIONAL); - obs_property_set_visible(obs_properties_get(props, P_DIRECTIONAL_ANGLE), show_directional); + // Find new Subtype + auto found = list_of_subtypes.find(vsubtype); + if (found == list_of_subtypes.end()) { + return false; + } - // Scaling - bool show_scaling = obs_data_get_bool(settings, P_STEPSCALE); - obs_property_set_visible(obs_properties_get(props, P_STEPSCALE_X), show_scaling); - obs_property_set_visible(obs_properties_get(props, P_STEPSCALE_Y), show_scaling); + // Blur Type + if (strcmp(propname, P_TYPE) == 0) { + obs_property_t* prop_subtype = obs_properties_get(props, P_SUBTYPE); - // advanced - bool showAdvanced = obs_data_get_bool(settings, S_ADVANCED); - obs_property_set_visible(obs_properties_get(props, P_COLORFORMAT), showAdvanced); + /// Disable unsupported items. + size_t subvalue_idx = 0; + for (size_t idx = 0, edx = obs_property_list_item_count(prop_subtype); idx < edx; idx++) { + const char* subtype = obs_property_list_item_string(prop_subtype, idx); + bool disabled = false; + + auto found = list_of_subtypes.find(subtype); + if (found != list_of_subtypes.end()) { + disabled = !type_found->second.fn().is_type_supported(found->second.type); + } else { + disabled = true; + } + + obs_property_list_item_disable(prop_subtype, idx, disabled); + if (strcmp(subtype, vsubtype) == 0) { + subvalue_idx = idx; + } + } + + /// Ensure that there is a valid item selected. + if (obs_property_list_item_disabled(prop_subtype, subvalue_idx)) { + for (size_t idx = 0, edx = obs_property_list_item_count(prop_subtype); idx < edx; idx++) { + if (!obs_property_list_item_disabled(prop_subtype, idx)) { + obs_data_set_string(settings, P_SUBTYPE, obs_property_list_item_string(prop_subtype, idx)); + break; + } + } + } + } + + // Blur Sub-Type + { + bool has_angle_support = (found->second.type == ::gfx::blur::type::Directional) + || (found->second.type == ::gfx::blur::type::Rotational); + bool has_center_support = + (found->second.type == ::gfx::blur::type::Rotational) || (found->second.type == ::gfx::blur::type::Zoom); + bool has_stepscale_support = type_found->second.fn().is_step_scale_supported(found->second.type); + bool show_scaling = obs_data_get_bool(settings, P_STEPSCALE) && has_stepscale_support; + + /// Size + p = obs_properties_get(props, P_SIZE); + obs_property_float_set_limits(p, type_found->second.fn().get_min_size(found->second.type), + type_found->second.fn().get_max_size(found->second.type), + type_found->second.fn().get_step_size(found->second.type)); + + /// Angle + p = obs_properties_get(props, P_ANGLE); + obs_property_set_visible(p, has_angle_support); + obs_property_float_set_limits(p, type_found->second.fn().get_min_angle(found->second.type), + type_found->second.fn().get_max_angle(found->second.type), + type_found->second.fn().get_step_angle(found->second.type)); + + /// Center, Radius + obs_property_set_visible(obs_properties_get(props, P_CENTER_X), has_center_support); + obs_property_set_visible(obs_properties_get(props, P_CENTER_Y), has_center_support); + + /// Step Scaling + obs_property_set_visible(obs_properties_get(props, P_STEPSCALE), has_stepscale_support); + p = obs_properties_get(props, P_STEPSCALE_X); + obs_property_set_visible(p, show_scaling); + obs_property_float_set_limits(p, type_found->second.fn().get_min_step_scale_x(found->second.type), + type_found->second.fn().get_max_step_scale_x(found->second.type), + type_found->second.fn().get_step_step_scale_x(found->second.type)); + p = obs_properties_get(props, P_STEPSCALE_Y); + obs_property_set_visible(p, show_scaling); + obs_property_float_set_limits(p, type_found->second.fn().get_min_step_scale_x(found->second.type), + type_found->second.fn().get_max_step_scale_x(found->second.type), + type_found->second.fn().get_step_step_scale_x(found->second.type)); + } + + { // Masking + bool show_mask = obs_data_get_bool(settings, P_MASK); + mask_type mtype = static_cast(obs_data_get_int(settings, P_MASK_TYPE)); + bool show_region = (mtype == mask_type::Region) && show_mask; + bool show_image = (mtype == mask_type::Image) && show_mask; + bool show_source = (mtype == mask_type::Source) && show_mask; + obs_property_set_visible(obs_properties_get(props, P_MASK_TYPE), show_mask); + obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_LEFT), show_region); + obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_TOP), show_region); + obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_RIGHT), show_region); + obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_BOTTOM), show_region); + obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_FEATHER), show_region); + obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_FEATHER_SHIFT), show_region); + obs_property_set_visible(obs_properties_get(props, P_MASK_REGION_INVERT), show_region); + obs_property_set_visible(obs_properties_get(props, P_MASK_IMAGE), show_image); + obs_property_set_visible(obs_properties_get(props, P_MASK_SOURCE), show_source); + obs_property_set_visible(obs_properties_get(props, P_MASK_COLOR), show_image || show_source); + obs_property_set_visible(obs_properties_get(props, P_MASK_ALPHA), show_image || show_source); + obs_property_set_visible(obs_properties_get(props, P_MASK_MULTIPLIER), show_image || show_source); + } return true; } +void filter::blur::blur_instance::translate_old_settings(obs_data_t* settings) +{ + obs_data_set_default_int(settings, S_VERSION, -1); + int64_t version = obs_data_get_int(settings, S_VERSION); + + // If it's the same as the current version, return. + if (version == PROJECT_VERSION) { + return; + } + + // Now we use a fall-through switch to gradually upgrade each known version change. + switch (version) { + case -1: + /// Blur Type + int64_t old_blur = obs_data_get_int(settings, "Filter.Blur.Type"); + if (old_blur == 0) { // Box + obs_data_set_string(settings, P_TYPE, "box"); + } else if (old_blur == 1) { // Gaussian + obs_data_set_string(settings, P_TYPE, "gaussian"); + } else if (old_blur == 2) { // Bilateral, no longer included. + obs_data_set_string(settings, P_TYPE, "box"); + } else if (old_blur == 3) { // Box Linear + obs_data_set_string(settings, P_TYPE, "box_linear"); + } else if (old_blur == 4) { // Gaussian Linear + obs_data_set_string(settings, P_TYPE, "gaussian_linear"); + } else { + obs_data_set_string(settings, P_TYPE, "box"); + } + obs_data_unset_user_value(settings, "Filter.Blur.Type"); + + /// Directional Blur + bool directional = obs_data_get_bool(settings, "Filter.Blur.Directional"); + if (directional) { + obs_data_set_string(settings, P_SUBTYPE, "directional"); + } else { + obs_data_set_string(settings, P_SUBTYPE, "area"); + } + obs_data_unset_user_value(settings, "Filter.Blur.Directional"); + + /// Directional Blur Angle + double_t angle = obs_data_get_double(settings, "Filter.Blur.Directional.Angle"); + obs_data_set_double(settings, P_ANGLE, angle); + obs_data_unset_user_value(settings, "Filter.Blur.Directional.Angle"); + } + + obs_data_set_int(settings, S_VERSION, PROJECT_VERSION); +} + obs_properties_t* filter::blur::blur_instance::get_properties() { obs_properties_t* pr = obs_properties_create(); obs_property_t* p = NULL; - p = obs_properties_add_list(pr, P_TYPE, P_TRANSLATE(P_TYPE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_TYPE))); - obs_property_set_modified_callback2(p, modified_properties, this); - obs_property_list_add_int(p, P_TRANSLATE(P_TYPE_BOX), filter::blur::type::Box); - obs_property_list_add_int(p, P_TRANSLATE(P_TYPE_BOXLINEAR), filter::blur::type::BoxLinear); - obs_property_list_add_int(p, P_TRANSLATE(P_TYPE_GAUSSIAN), filter::blur::type::Gaussian); - obs_property_list_add_int(p, P_TRANSLATE(P_TYPE_GAUSSIANLINEAR), filter::blur::type::GaussianLinear); - obs_property_list_add_int(p, P_TRANSLATE(P_TYPE_BILATERAL), filter::blur::type::Bilateral); + // Blur Type and Sub-Type + { + p = obs_properties_add_list(pr, P_TYPE, P_TRANSLATE(P_TYPE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_TYPE))); + obs_property_set_modified_callback2(p, modified_properties, this); + obs_property_list_add_string(p, P_TRANSLATE(S_BLUR_TYPE_BOX), "box"); + obs_property_list_add_string(p, P_TRANSLATE(S_BLUR_TYPE_BOX_LINEAR), "box_linear"); + obs_property_list_add_string(p, P_TRANSLATE(S_BLUR_TYPE_GAUSSIAN), "gaussian"); + obs_property_list_add_string(p, P_TRANSLATE(S_BLUR_TYPE_GAUSSIAN_LINEAR), "gaussian_linear"); + obs_property_list_add_string(p, P_TRANSLATE(S_BLUR_TYPE_DUALFILTERING), "dual_filtering"); - p = obs_properties_add_int_slider(pr, P_SIZE, P_TRANSLATE(P_SIZE), 1, 25, 1); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SIZE))); - //obs_property_set_modified_callback(p, modified_properties); + p = obs_properties_add_list(pr, P_SUBTYPE, P_TRANSLATE(P_SUBTYPE), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SUBTYPE))); + obs_property_set_modified_callback2(p, modified_properties, this); + obs_property_list_add_string(p, P_TRANSLATE(S_BLUR_SUBTYPE_AREA), "area"); + obs_property_list_add_string(p, P_TRANSLATE(S_BLUR_SUBTYPE_DIRECTIONAL), "directional"); + obs_property_list_add_string(p, P_TRANSLATE(S_BLUR_SUBTYPE_ROTATIONAL), "rotational"); + obs_property_list_add_string(p, P_TRANSLATE(S_BLUR_SUBTYPE_ZOOM), "zoom"); + } - // bilateral Only - p = obs_properties_add_float_slider(pr, P_BILATERAL_SMOOTHING, P_TRANSLATE(P_BILATERAL_SMOOTHING), 0.0, 100.0, - 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_BILATERAL_SMOOTHING))); - p = obs_properties_add_float_slider(pr, P_BILATERAL_SHARPNESS, P_TRANSLATE(P_BILATERAL_SHARPNESS), 0.0, 100.0, - 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_BILATERAL_SHARPNESS))); + // Blur Parameters + { + p = obs_properties_add_float_slider(pr, P_SIZE, P_TRANSLATE(P_SIZE), 1, 32767, 1); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_SIZE))); - // Mask - p = obs_properties_add_bool(pr, P_MASK, P_TRANSLATE(P_MASK)); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK))); - obs_property_set_modified_callback2(p, modified_properties, this); - p = obs_properties_add_list(pr, P_MASK_TYPE, P_TRANSLATE(P_MASK_TYPE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_TYPE))); - obs_property_set_modified_callback2(p, modified_properties, this); - obs_property_list_add_int(p, P_TRANSLATE(P_MASK_TYPE_REGION), mask_type::Region); - obs_property_list_add_int(p, P_TRANSLATE(P_MASK_TYPE_IMAGE), mask_type::Image); - obs_property_list_add_int(p, P_TRANSLATE(P_MASK_TYPE_SOURCE), mask_type::Source); - /// Region - p = obs_properties_add_float_slider(pr, P_MASK_REGION_LEFT, P_TRANSLATE(P_MASK_REGION_LEFT), 0.0, 100.0, 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_LEFT))); - p = obs_properties_add_float_slider(pr, P_MASK_REGION_TOP, P_TRANSLATE(P_MASK_REGION_TOP), 0.0, 100.0, 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_TOP))); - p = obs_properties_add_float_slider(pr, P_MASK_REGION_RIGHT, P_TRANSLATE(P_MASK_REGION_RIGHT), 0.0, 100.0, 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_RIGHT))); - p = obs_properties_add_float_slider(pr, P_MASK_REGION_BOTTOM, P_TRANSLATE(P_MASK_REGION_BOTTOM), 0.0, 100.0, 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_BOTTOM))); - p = obs_properties_add_float_slider(pr, P_MASK_REGION_FEATHER, P_TRANSLATE(P_MASK_REGION_FEATHER), 0.0, 50.0, 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_FEATHER))); - p = obs_properties_add_float_slider(pr, P_MASK_REGION_FEATHER_SHIFT, P_TRANSLATE(P_MASK_REGION_FEATHER_SHIFT), - -100.0, 100.0, 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_FEATHER_SHIFT))); - p = obs_properties_add_bool(pr, P_MASK_REGION_INVERT, P_TRANSLATE(P_MASK_REGION_INVERT)); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_INVERT))); - /// Image - p = obs_properties_add_path(pr, P_MASK_IMAGE, P_TRANSLATE(P_MASK_IMAGE), OBS_PATH_FILE, - filter::blur::blur_factory::get()->get_translation("image-filter").c_str(), nullptr); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_IMAGE))); - /// Source - p = obs_properties_add_list(pr, P_MASK_SOURCE, P_TRANSLATE(P_MASK_SOURCE), OBS_COMBO_TYPE_LIST, - OBS_COMBO_FORMAT_STRING); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_SOURCE))); - obs::source_tracker::get()->enumerate( - [this, &p](std::string name, obs_source_t*) { - obs_property_list_add_string(p, std::string(name + " (Source)").c_str(), name.c_str()); - return false; - }, - obs::source_tracker::filter_video_sources); - obs::source_tracker::get()->enumerate( - [this, &p](std::string name, obs_source_t*) { - obs_property_list_add_string(p, std::string(name + " (Scene)").c_str(), name.c_str()); - return false; - }, - obs::source_tracker::filter_scenes); + p = obs_properties_add_float_slider(pr, P_ANGLE, P_TRANSLATE(P_ANGLE), -180.0, 180.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_ANGLE))); - /// Shared - p = obs_properties_add_color(pr, P_MASK_COLOR, P_TRANSLATE(P_MASK_COLOR)); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_COLOR))); - p = obs_properties_add_float_slider(pr, P_MASK_ALPHA, P_TRANSLATE(P_MASK_ALPHA), 0.0, 100.0, 0.1); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_ALPHA))); - p = obs_properties_add_float_slider(pr, P_MASK_MULTIPLIER, P_TRANSLATE(P_MASK_MULTIPLIER), 0.0, 10.0, 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_MULTIPLIER))); + p = obs_properties_add_float_slider(pr, P_CENTER_X, P_TRANSLATE(P_CENTER_X), 0.00, 100.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_CENTER_X))); + p = obs_properties_add_float_slider(pr, P_CENTER_Y, P_TRANSLATE(P_CENTER_Y), 0.00, 100.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_CENTER_Y))); - // Directional Blur - p = obs_properties_add_bool(pr, P_DIRECTIONAL, P_TRANSLATE(P_DIRECTIONAL)); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_DIRECTIONAL))); - obs_property_set_modified_callback2(p, modified_properties, this); - p = obs_properties_add_float_slider(pr, P_DIRECTIONAL_ANGLE, P_TRANSLATE(P_DIRECTIONAL_ANGLE), -180.0, 180.0, 0.01); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_DIRECTIONAL_ANGLE))); + p = obs_properties_add_bool(pr, P_STEPSCALE, P_TRANSLATE(P_STEPSCALE)); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_STEPSCALE))); + obs_property_set_modified_callback2(p, modified_properties, this); + p = obs_properties_add_float_slider(pr, P_STEPSCALE_X, P_TRANSLATE(P_STEPSCALE_X), 0.0, 1000.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_STEPSCALE_X))); + p = obs_properties_add_float_slider(pr, P_STEPSCALE_Y, P_TRANSLATE(P_STEPSCALE_Y), 0.0, 1000.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_STEPSCALE_Y))); + } - // Scaling - p = obs_properties_add_bool(pr, P_STEPSCALE, P_TRANSLATE(P_STEPSCALE)); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_STEPSCALE))); - obs_property_set_modified_callback2(p, modified_properties, this); - p = obs_properties_add_float_slider(pr, P_STEPSCALE_X, P_TRANSLATE(P_STEPSCALE_X), 0.0, 1000.0, 0.01); - p = obs_properties_add_float_slider(pr, P_STEPSCALE_Y, P_TRANSLATE(P_STEPSCALE_Y), 0.0, 1000.0, 0.01); + // Masking + { + p = obs_properties_add_bool(pr, P_MASK, P_TRANSLATE(P_MASK)); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK))); + obs_property_set_modified_callback2(p, modified_properties, this); + p = obs_properties_add_list(pr, P_MASK_TYPE, P_TRANSLATE(P_MASK_TYPE), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_INT); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_TYPE))); + obs_property_set_modified_callback2(p, modified_properties, this); + obs_property_list_add_int(p, P_TRANSLATE(P_MASK_TYPE_REGION), mask_type::Region); + obs_property_list_add_int(p, P_TRANSLATE(P_MASK_TYPE_IMAGE), mask_type::Image); + obs_property_list_add_int(p, P_TRANSLATE(P_MASK_TYPE_SOURCE), mask_type::Source); + /// Region + p = obs_properties_add_float_slider(pr, P_MASK_REGION_LEFT, P_TRANSLATE(P_MASK_REGION_LEFT), 0.0, 100.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_LEFT))); + p = obs_properties_add_float_slider(pr, P_MASK_REGION_TOP, P_TRANSLATE(P_MASK_REGION_TOP), 0.0, 100.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_TOP))); + p = obs_properties_add_float_slider(pr, P_MASK_REGION_RIGHT, P_TRANSLATE(P_MASK_REGION_RIGHT), 0.0, 100.0, + 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_RIGHT))); + p = obs_properties_add_float_slider(pr, P_MASK_REGION_BOTTOM, P_TRANSLATE(P_MASK_REGION_BOTTOM), 0.0, 100.0, + 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_BOTTOM))); + p = obs_properties_add_float_slider(pr, P_MASK_REGION_FEATHER, P_TRANSLATE(P_MASK_REGION_FEATHER), 0.0, 50.0, + 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_FEATHER))); + p = obs_properties_add_float_slider(pr, P_MASK_REGION_FEATHER_SHIFT, P_TRANSLATE(P_MASK_REGION_FEATHER_SHIFT), + -100.0, 100.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_FEATHER_SHIFT))); + p = obs_properties_add_bool(pr, P_MASK_REGION_INVERT, P_TRANSLATE(P_MASK_REGION_INVERT)); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_REGION_INVERT))); + /// Image + p = obs_properties_add_path(pr, P_MASK_IMAGE, P_TRANSLATE(P_MASK_IMAGE), OBS_PATH_FILE, + filter::blur::blur_factory::get()->get_translation("image-filter").c_str(), + nullptr); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_IMAGE))); + /// Source + p = obs_properties_add_list(pr, P_MASK_SOURCE, P_TRANSLATE(P_MASK_SOURCE), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_SOURCE))); + obs::source_tracker::get()->enumerate( + [this, &p](std::string name, obs_source_t*) { + obs_property_list_add_string(p, std::string(name + " (Source)").c_str(), name.c_str()); + return false; + }, + obs::source_tracker::filter_video_sources); + obs::source_tracker::get()->enumerate( + [this, &p](std::string name, obs_source_t*) { + obs_property_list_add_string(p, std::string(name + " (Scene)").c_str(), name.c_str()); + return false; + }, + obs::source_tracker::filter_scenes); - // advanced - p = obs_properties_add_bool(pr, S_ADVANCED, P_TRANSLATE(S_ADVANCED)); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(S_ADVANCED))); - obs_property_set_modified_callback2(p, modified_properties, this); - - p = obs_properties_add_list(pr, P_COLORFORMAT, P_TRANSLATE(P_COLORFORMAT), OBS_COMBO_TYPE_LIST, - OBS_COMBO_FORMAT_INT); - obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_COLORFORMAT))); - obs_property_list_add_int(p, "RGB", ColorFormat::RGB); - obs_property_list_add_int(p, "YUV", ColorFormat::YUV); + /// Shared + p = obs_properties_add_color(pr, P_MASK_COLOR, P_TRANSLATE(P_MASK_COLOR)); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_COLOR))); + p = obs_properties_add_float_slider(pr, P_MASK_ALPHA, P_TRANSLATE(P_MASK_ALPHA), 0.0, 100.0, 0.1); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_ALPHA))); + p = obs_properties_add_float_slider(pr, P_MASK_MULTIPLIER, P_TRANSLATE(P_MASK_MULTIPLIER), 0.0, 10.0, 0.01); + obs_property_set_long_description(p, P_TRANSLATE(P_DESC(P_MASK_MULTIPLIER))); + } return pr; } void filter::blur::blur_instance::update(obs_data_t* settings) { - m_blur_type = (blur::type)obs_data_get_int(settings, P_TYPE); - m_blur_effect = blur_factory::get()->get_effect(m_blur_type); - m_blur_technique = blur_factory::get()->get_technique(m_blur_type); - m_blur_size = (uint64_t)obs_data_get_int(settings, P_SIZE); + // Ensure backwards compatibility. + this->translate_old_settings(settings); - // bilateral blur - m_blur_bilateral_smoothing = obs_data_get_double(settings, P_BILATERAL_SMOOTHING) / 100.0; - m_blur_bilateral_sharpness = obs_data_get_double(settings, P_BILATERAL_SHARPNESS) / 100.0; + { // Blur Type + const char* blur_type = obs_data_get_string(settings, P_TYPE); + const char* blur_subtype = obs_data_get_string(settings, P_SUBTYPE); + const char* last_blur_type = obs_data_get_string(settings, P_TYPE ".last"); - // region - m_mask.enabled = obs_data_get_bool(settings, P_MASK); - if (m_mask.enabled) { - m_mask.type = static_cast(obs_data_get_int(settings, P_MASK_TYPE)); - switch (m_mask.type) { - case mask_type::Region: - m_mask.region.left = float_t(obs_data_get_double(settings, P_MASK_REGION_LEFT) / 100.0); - m_mask.region.top = float_t(obs_data_get_double(settings, P_MASK_REGION_TOP) / 100.0); - m_mask.region.right = 1.0f - float_t(obs_data_get_double(settings, P_MASK_REGION_RIGHT) / 100.0); - m_mask.region.bottom = 1.0f - float_t(obs_data_get_double(settings, P_MASK_REGION_BOTTOM) / 100.0); - m_mask.region.feather = float_t(obs_data_get_double(settings, P_MASK_REGION_FEATHER) / 100.0); - m_mask.region.feather_shift = float_t(obs_data_get_double(settings, P_MASK_REGION_FEATHER_SHIFT) / 100.0); - m_mask.region.invert = obs_data_get_bool(settings, P_MASK_REGION_INVERT); - break; - case mask_type::Image: - m_mask.image.path = obs_data_get_string(settings, P_MASK_IMAGE); - break; - case mask_type::Source: - m_mask.source.name = obs_data_get_string(settings, P_MASK_SOURCE); - break; - } - if ((m_mask.type == mask_type::Image) || (m_mask.type == mask_type::Source)) { - uint32_t color = static_cast(obs_data_get_int(settings, P_MASK_COLOR)); - m_mask.color.r = ((color >> 0) & 0xFF) / 255.0f; - m_mask.color.g = ((color >> 8) & 0xFF) / 255.0f; - m_mask.color.b = ((color >> 16) & 0xFF) / 255.0f; - m_mask.color.a = static_cast(obs_data_get_double(settings, P_MASK_ALPHA)); - m_mask.multiplier = float_t(obs_data_get_double(settings, P_MASK_MULTIPLIER)); + auto type_found = list_of_types.find(blur_type); + if (type_found != list_of_types.end()) { + auto subtype_found = list_of_subtypes.find(blur_subtype); + if (subtype_found != list_of_subtypes.end()) { + if ((strcmp(last_blur_type, blur_type) != 0) || (m_blur->get_type() != subtype_found->second.type)) { + if (type_found->second.fn().is_type_supported(subtype_found->second.type)) { + m_blur = type_found->second.fn().create(subtype_found->second.type); + } + } + } } } - // Directional Blur - this->m_blur_directional = obs_data_get_bool(settings, P_DIRECTIONAL); - this->m_blur_angle = obs_data_get_double(settings, P_DIRECTIONAL_ANGLE); + { // Blur Parameters + this->m_blur_size = obs_data_get_double(settings, P_SIZE); + this->m_blur_angle = obs_data_get_double(settings, P_ANGLE); + this->m_blur_center.first = obs_data_get_double(settings, P_CENTER_X) / 100.0; + this->m_blur_center.second = obs_data_get_double(settings, P_CENTER_Y) / 100.0; - // Scaling - this->m_blur_step_scaling = obs_data_get_bool(settings, P_STEPSCALE); - this->m_blur_step_scale.first = obs_data_get_double(settings, P_STEPSCALE_X) / 100.0; - this->m_blur_step_scale.second = obs_data_get_double(settings, P_STEPSCALE_Y) / 100.0; + // Scaling + this->m_blur_step_scaling = obs_data_get_bool(settings, P_STEPSCALE); + this->m_blur_step_scale.first = obs_data_get_double(settings, P_STEPSCALE_X) / 100.0; + this->m_blur_step_scale.second = obs_data_get_double(settings, P_STEPSCALE_Y) / 100.0; + } - // advanced - if (obs_data_get_bool(settings, S_ADVANCED)) { - m_color_format = obs_data_get_int(settings, P_COLORFORMAT); - } else { - m_color_format = obs_data_get_default_int(settings, P_COLORFORMAT); + { // Masking + m_mask.enabled = obs_data_get_bool(settings, P_MASK); + if (m_mask.enabled) { + m_mask.type = static_cast(obs_data_get_int(settings, P_MASK_TYPE)); + switch (m_mask.type) { + case mask_type::Region: + m_mask.region.left = float_t(obs_data_get_double(settings, P_MASK_REGION_LEFT) / 100.0); + m_mask.region.top = float_t(obs_data_get_double(settings, P_MASK_REGION_TOP) / 100.0); + m_mask.region.right = 1.0f - float_t(obs_data_get_double(settings, P_MASK_REGION_RIGHT) / 100.0); + m_mask.region.bottom = 1.0f - float_t(obs_data_get_double(settings, P_MASK_REGION_BOTTOM) / 100.0); + m_mask.region.feather = float_t(obs_data_get_double(settings, P_MASK_REGION_FEATHER) / 100.0); + m_mask.region.feather_shift = + float_t(obs_data_get_double(settings, P_MASK_REGION_FEATHER_SHIFT) / 100.0); + m_mask.region.invert = obs_data_get_bool(settings, P_MASK_REGION_INVERT); + break; + case mask_type::Image: + m_mask.image.path = obs_data_get_string(settings, P_MASK_IMAGE); + break; + case mask_type::Source: + m_mask.source.name = obs_data_get_string(settings, P_MASK_SOURCE); + break; + } + if ((m_mask.type == mask_type::Image) || (m_mask.type == mask_type::Source)) { + uint32_t color = static_cast(obs_data_get_int(settings, P_MASK_COLOR)); + m_mask.color.r = ((color >> 0) & 0xFF) / 255.0f; + m_mask.color.g = ((color >> 8) & 0xFF) / 255.0f; + m_mask.color.b = ((color >> 16) & 0xFF) / 255.0f; + m_mask.color.a = static_cast(obs_data_get_double(settings, P_MASK_ALPHA)); + m_mask.multiplier = float_t(obs_data_get_double(settings, P_MASK_MULTIPLIER)); + } + } } } @@ -766,6 +769,25 @@ void filter::blur::blur_instance::deactivate() {} void filter::blur::blur_instance::video_tick(float) { + // Blur + if (m_blur) { + m_blur->set_size(m_blur_size); + if (m_blur_step_scaling) { + m_blur->set_step_scale(m_blur_step_scale.first, m_blur_step_scale.second); + } else { + m_blur->set_step_scale(1.0, 1.0); + } + if ((m_blur->get_type() == ::gfx::blur::type::Directional) + || (m_blur->get_type() == ::gfx::blur::type::Rotational)) { + auto obj = std::dynamic_pointer_cast<::gfx::blur::ibase_angle>(m_blur); + obj->set_angle(m_blur_angle); + } + if ((m_blur->get_type() == ::gfx::blur::type::Zoom) || (m_blur->get_type() == ::gfx::blur::type::Rotational)) { + auto obj = std::dynamic_pointer_cast<::gfx::blur::ibase_center>(m_blur); + obj->set_center(m_blur_center.first, m_blur_center.second); + } + } + // Load Mask if (m_mask.type == mask_type::Image) { if (m_mask.image.path_old != m_mask.image.path) { @@ -796,28 +818,19 @@ void filter::blur::blur_instance::video_tick(float) void filter::blur::blur_instance::video_render(gs_effect_t* effect) { - obs_source_t* parent = obs_filter_get_parent(this->m_self); - obs_source_t* target = obs_filter_get_target(this->m_self); - uint32_t baseW = obs_source_get_base_width(target); - uint32_t baseH = obs_source_get_base_height(target); - vec4 black; - bool failed = false; + obs_source_t* parent = obs_filter_get_parent(this->m_self); + obs_source_t* target = obs_filter_get_target(this->m_self); + gs_effect_t* defaultEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); + uint32_t baseW = obs_source_get_base_width(target); + uint32_t baseH = obs_source_get_base_height(target); std::shared_ptr colorConversionEffect = blur_factory::get()->get_color_converter_effect(); - gs_effect_t* defaultEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT); + vec4 black; vec4_set(&black, 0, 0, 0, 0); // Verify that we can actually run first. - if (!target || !parent || !this->m_self) { - obs_source_skip_video_filter(this->m_self); - return; - } - if ((baseW == 0) || (baseH == 0)) { - obs_source_skip_video_filter(this->m_self); - return; - } - if (!this->m_output_rt1 || !this->m_output_rt2 || !this->m_blur_effect) { + if (!target || !parent || !this->m_self || !this->m_blur || (baseW == 0) || (baseH == 0)) { obs_source_skip_video_filter(this->m_self); return; } @@ -825,41 +838,43 @@ void filter::blur::blur_instance::video_render(gs_effect_t* effect) if (!m_source_rendered) { // Source To Texture { - gs_blend_state_push(); - gs_reset_blend_state(); - gs_enable_color(true, true, true, true); - gs_enable_blending(true); - gs_enable_depth_test(false); - gs_enable_stencil_test(false); - gs_enable_stencil_write(false); - gs_set_cull_mode(GS_NEITHER); - gs_depth_function(GS_ALWAYS); - gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA); - gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS); - gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO); + if (obs_source_process_filter_begin(this->m_self, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING)) { + { + auto op = this->m_source_rt->render(baseW, baseH); - try { - auto op = this->m_source_rt->render(baseW, baseH); + gs_blend_state_push(); + gs_reset_blend_state(); + gs_enable_blending(false); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); - // Orthographic Camera and clear RenderTarget. - gs_ortho(0, (float)baseW, 0, (float)baseH, -1., 1.); - gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0); + gs_set_cull_mode(GS_NEITHER); + gs_enable_color(true, true, true, true); - // Render - if (obs_source_process_filter_begin(this->m_self, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING)) { + gs_enable_depth_test(false); + gs_depth_function(GS_ALWAYS); + + gs_enable_stencil_test(false); + gs_enable_stencil_write(false); + gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS); + gs_stencil_op(GS_STENCIL_BOTH, GS_KEEP, GS_KEEP, GS_KEEP); + + // Orthographic Camera and clear RenderTarget. + gs_ortho(0, (float)baseW, 0, (float)baseH, -1., 1.); + //gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0); + + // Render obs_source_process_filter_end(this->m_self, defaultEffect, baseW, baseH); - } else { - throw std::runtime_error("Failed to render source"); - } - } catch (std::exception ex) { - gs_blend_state_pop(); - obs_source_skip_video_filter(this->m_self); - return; - } - gs_blend_state_pop(); - if (!(m_source_texture = this->m_source_rt->get_texture())) { - obs_source_skip_video_filter(m_self); + gs_blend_state_pop(); + } + + m_source_texture = this->m_source_rt->get_texture(); + if (!m_source_texture) { + obs_source_skip_video_filter(this->m_self); + return; + } + } else { + obs_source_skip_video_filter(this->m_self); return; } } @@ -868,144 +883,8 @@ void filter::blur::blur_instance::video_render(gs_effect_t* effect) } if (!m_output_rendered) { - m_output_texture = m_source_texture; - - // Color Conversion RGB-YUV - if ((m_color_format != ColorFormat::RGB) && colorConversionEffect) { - gs_blend_state_push(); - gs_reset_blend_state(); - gs_enable_color(true, true, true, true); - gs_enable_blending(false); - gs_enable_depth_test(false); - gs_enable_stencil_test(false); - gs_enable_stencil_write(false); - gs_set_cull_mode(GS_NEITHER); - gs_depth_function(GS_ALWAYS); - gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); - gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS); - gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO); - - try { - auto op = this->m_output_rt1->render(baseW, baseH); - gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); - - if (colorConversionEffect->has_parameter("image")) { - colorConversionEffect->get_parameter("image").set_texture(m_source_texture->get_object()); - } - while (gs_effect_loop(colorConversionEffect->get_object(), "RGBToYUV")) { - gs_draw_sprite(m_source_texture->get_object(), 0, baseW, baseH); - } - } catch (std::exception ex) { - gs_blend_state_pop(); - obs_source_skip_video_filter(m_self); - return; - } - gs_blend_state_pop(); - - if (!(m_output_texture = this->m_output_rt1->get_texture())) { - obs_source_skip_video_filter(m_self); - return; - } - - // Swap RTs for further rendering. - std::swap(this->m_output_rt1, this->m_output_rt2); - } - - // Blur - { - gs_blend_state_push(); - gs_reset_blend_state(); - gs_enable_color(true, true, true, true); - gs_enable_blending(false); - gs_enable_depth_test(false); - gs_enable_stencil_test(false); - gs_enable_stencil_write(false); - gs_set_cull_mode(GS_NEITHER); - gs_depth_function(GS_ALWAYS); - gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); - gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS); - gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO); - - std::pair kvs[] = {{1.0f / baseW, 0.0f}, {0.0f, 1.0f / baseH}}; - - // Directional Blur - if (this->m_blur_directional) { - // Directional Blur changes how - - kvs[0].first = (1.0f / baseW); - kvs[0].second = (1.0f / baseH); - kvs[1].first = kvs[0].first; - kvs[1].second = kvs[0].second; - - double_t rad = this->m_blur_angle * PI / 180.0; - double_t c0 = cos(rad); - double_t s0 = sin(rad); - - kvs[0].first *= float_t(c0); - kvs[0].second *= float_t(s0); - - if (!this->m_blur_step_scaling) { - kvs[1].first *= 0.0f; - kvs[1].second *= 0.0f; - } else { - kvs[1].first *= float_t(s0); - kvs[1].second *= float_t(c0); - } - } - - // Apply scaling - if (this->m_blur_step_scaling) { - if (!this->m_blur_directional) { - kvs[0].first *= float_t(this->m_blur_step_scale.first); - kvs[0].second *= float_t(this->m_blur_step_scale.second); - kvs[1].first *= float_t(this->m_blur_step_scale.first); - kvs[1].second *= float_t(this->m_blur_step_scale.second); - } else { - // Directional Blur changes how scaling works as it rotates and needs to be relative to the axis of rotation. - kvs[0].first *= float_t(this->m_blur_step_scale.first); - kvs[0].second *= float_t(this->m_blur_step_scale.first); - kvs[1].first *= float_t(this->m_blur_step_scale.second); - kvs[1].second *= float_t(this->m_blur_step_scale.second); - } - } - - try { - for (auto v : kvs) { - float xpel = std::get<0>(v); - float ypel = std::get<1>(v); - if ((abs(xpel) <= FLT_EPSILON) && (abs(ypel) <= FLT_EPSILON)) { - // Ignore passes that have a 0 texel modifier. - continue; - } - - { - auto op = this->m_output_rt1->render(baseW, baseH); - gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); - - apply_shared_param(m_output_texture->get_object(), xpel, ypel); - apply_gaussian_param(uint8_t(this->m_blur_size)); - apply_bilateral_param(); - - // Render - while (gs_effect_loop(this->m_blur_effect->get_object(), this->m_blur_technique.c_str())) { - gs_draw_sprite(m_output_texture->get_object(), 0, baseW, baseH); - } - } - - if (!(m_output_texture = this->m_output_rt1->get_texture())) { - throw("Failed to get blur texture."); - } - - // Swap RTs for further rendering. - std::swap(this->m_output_rt1, this->m_output_rt2); - } - } catch (std::exception ex) { - gs_blend_state_pop(); - obs_source_skip_video_filter(this->m_self); - return; - } - gs_blend_state_pop(); - } + m_blur->set_input(m_source_texture); + m_output_texture = m_blur->render(); // Mask if (m_mask.enabled) { @@ -1070,57 +949,49 @@ void filter::blur::blur_instance::video_render(gs_effect_t* effect) apply_mask_parameters(mask_effect, m_source_texture->get_object(), m_output_texture->get_object()); try { - auto op = this->m_output_rt1->render(baseW, baseH); + auto op = this->m_output_rt->render(baseW, baseH); gs_ortho(0, (float)baseW, 0, (float)baseH, -1, 1); // Render while (gs_effect_loop(mask_effect->get_object(), technique.c_str())) { gs_draw_sprite(m_output_texture->get_object(), 0, baseW, baseH); } - - } catch (std::exception ex) { + } catch (const std::exception&) { gs_blend_state_pop(); obs_source_skip_video_filter(this->m_self); return; } gs_blend_state_pop(); - if (!(m_output_texture = this->m_output_rt1->get_texture())) { + if (!(m_output_texture = this->m_output_rt->get_texture())) { obs_source_skip_video_filter(this->m_self); return; } - - // Swap RTs for further rendering. - std::swap(this->m_output_rt1, this->m_output_rt2); } m_output_rendered = true; } - // Color Conversion RGB-YUV or Straight Draw + // Draw source { // It is important that we do not modify the blend state here, as it is set correctly by OBS + gs_set_cull_mode(GS_NEITHER); gs_enable_color(true, true, true, true); gs_enable_depth_test(false); + gs_depth_function(GS_ALWAYS); gs_enable_stencil_test(false); gs_enable_stencil_write(false); - gs_set_cull_mode(GS_NEITHER); - gs_depth_function(GS_ALWAYS); gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS); gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO); gs_effect_t* finalEffect = effect ? effect : defaultEffect; const char* technique = "Draw"; - if ((m_color_format == ColorFormat::YUV) && colorConversionEffect) { - finalEffect = colorConversionEffect->get_object(); - technique = "YUVToRGB"; - } - gs_eparam_t* param = gs_effect_get_param_by_name(finalEffect, "image"); if (!param) { P_LOG_ERROR(" Failed to set image param.", obs_source_get_name(this->m_self)); - failed = true; + obs_source_skip_video_filter(m_self); + return; } else { gs_effect_set_texture(param, m_output_texture->get_object()); } @@ -1128,9 +999,4 @@ void filter::blur::blur_instance::video_render(gs_effect_t* effect) gs_draw_sprite(m_output_texture->get_object(), 0, baseW, baseH); } } - - if (failed) { - obs_source_skip_video_filter(m_self); - return; - } } diff --git a/source/filters/filter-blur.hpp b/source/filters/filter-blur.hpp index 262dafc7..fb86330d 100644 --- a/source/filters/filter-blur.hpp +++ b/source/filters/filter-blur.hpp @@ -23,6 +23,7 @@ #include #include #include +#include "gfx/blur/gfx-blur-base.hpp" #include "gfx/gfx-source-texture.hpp" #include "obs/gs/gs-effect.hpp" #include "obs/gs/gs-helper.hpp" @@ -44,14 +45,6 @@ namespace filter { namespace blur { class blur_instance; - enum type : int64_t { - Box, - Gaussian, - Bilateral, - BoxLinear, - GaussianLinear, - }; - enum mask_type : int64_t { Region, Image, @@ -65,13 +58,9 @@ namespace filter { std::list sources; std::shared_ptr color_converter_effect; std::shared_ptr mask_effect; - std::shared_ptr blur_effect; std::map translation_map; - std::vector gaussian_widths; - std::map>> gaussian_kernels; - public: // Singleton static void initialize(); static void finalize(); @@ -84,9 +73,6 @@ namespace filter { void on_list_fill(); void on_list_empty(); - void generate_gaussian_kernels(); - void generate_kernel_textures(); - std::string const& get_translation(std::string const key); static void* create(obs_data_t* settings, obs_source_t* self); @@ -108,15 +94,9 @@ namespace filter { static void video_render(void* source, gs_effect_t* effect); public: - std::shared_ptr get_effect(filter::blur::type type); - - std::string get_technique(filter::blur::type type); - std::shared_ptr get_color_converter_effect(); std::shared_ptr get_mask_effect(); - - std::shared_ptr> get_gaussian_kernel(uint8_t size); }; class blur_instance { @@ -128,25 +108,17 @@ namespace filter { bool m_source_rendered; // Rendering - std::shared_ptr m_output_rt1; - std::shared_ptr m_output_rt2; std::shared_ptr m_output_texture; + std::shared_ptr m_output_rt; bool m_output_rendered; // Blur - std::shared_ptr m_blur_effect; - std::string m_blur_technique; - filter::blur::type m_blur_type; - uint64_t m_blur_size; - /// Bilateral Blur - double_t m_blur_bilateral_smoothing; - double_t m_blur_bilateral_sharpness; - /// Directional Blur - bool m_blur_directional; - double_t m_blur_angle; - /// Step Scaling - bool m_blur_step_scaling; - std::pair m_blur_step_scale; + std::shared_ptr<::gfx::blur::ibase> m_blur; + double_t m_blur_size; + double_t m_blur_angle; + std::pair m_blur_center; + bool m_blur_step_scaling; + std::pair m_blur_step_scale; // Masking struct { @@ -182,23 +154,19 @@ namespace filter { float_t multiplier; } m_mask; - // advanced - uint64_t m_color_format; - public: blur_instance(obs_data_t* settings, obs_source_t* self); ~blur_instance(); private: - bool apply_shared_param(gs_texture_t* input, float texelX, float texelY); - bool apply_bilateral_param(); - bool apply_gaussian_param(uint8_t width); bool apply_mask_parameters(std::shared_ptr effect, gs_texture_t* original_texture, gs_texture_t* blurred_texture); static bool modified_properties(void* ptr, obs_properties_t* props, obs_property* prop, obs_data_t* settings); + void translate_old_settings(obs_data_t*); + public: obs_properties_t* get_properties(); void update(obs_data_t*); diff --git a/source/strings.hpp b/source/strings.hpp index 66dcd128..d5418e63 100644 --- a/source/strings.hpp +++ b/source/strings.hpp @@ -29,6 +29,8 @@ #define T_FILEFILTERS_EFFECT "*.effect *.txt" #define T_FILEFILTERS_ANY "*.*" +#define S_VERSION "Version" + #define S_ADVANCED "Advanced" #define S_FILETYPE_IMAGE "FileType.Image" @@ -40,6 +42,17 @@ #define S_FILETYPE_EFFECT "FileType.Effect" #define S_FILETYPE_EFFECTS "FileType.Effects" +#define S_BLUR_TYPE_BOX "Blur.Type.Box" +#define S_BLUR_TYPE_BOX_LINEAR "Blur.Type.BoxLinear" +#define S_BLUR_TYPE_GAUSSIAN "Blur.Type.Gaussian" +#define S_BLUR_TYPE_GAUSSIAN_LINEAR "Blur.Type.GaussianLinear" +#define S_BLUR_TYPE_DUALFILTERING "Blur.Type.DualFiltering" + +#define S_BLUR_SUBTYPE_AREA "Blur.Subtype.Area" +#define S_BLUR_SUBTYPE_DIRECTIONAL "Blur.Subtype.Directional" +#define S_BLUR_SUBTYPE_ROTATIONAL "Blur.Subtype.Rotational" +#define S_BLUR_SUBTYPE_ZOOM "Blur.Subtype.Zoom" + #define S_MIPGENERATOR "MipGenerator" #define S_MIPGENERATOR_POINT "MipGenerator.Point" #define S_MIPGENERATOR_LINEAR "MipGenerator.Linear" diff --git a/source/util-math.hpp b/source/util-math.hpp index e57600f9..f728f58d 100644 --- a/source/util-math.hpp +++ b/source/util-math.hpp @@ -36,25 +36,14 @@ #endif // Constants -#define PI 3.1415926535897932384626433832795 -#define PI2 6.283185307179586476925286766559 -#define PI2_SQROOT 2.506628274631000502415765284811 +#define PI 3.1415926535897932384626433832795 // PI = pi +#define PI2 6.283185307179586476925286766559 // 2PI = 2 * pi +#define PI2_SQROOT 2.506628274631000502415765284811 // sqrt(2 * pi) -inline double_t Gaussian1D(double_t x, double_t o) -{ - double_t c = (x / o); - double_t b = exp(-0.5 * c * c); - double_t a = (1.0 / (o * PI2_SQROOT)); - return a * b; -} - -inline double_t Bilateral1D(double_t x, double_t o) -{ - double_t c = (x / 0); - double_t d = c * c; - double_t b = exp(-0.5 * d) / o; - return 0.39894 * b; // Seems to be (1.0 / (1 * PI2_SQROOT)) * b, otherwise no difference from Gaussian Blur -} +#define V_RAD 57.295779513082320876798154814105 // 180/pi +#define V_DEG 0.01745329251994329576923690768489 // pi/180 +#define DEG_TO_RAD(x) (x * V_DEG) +#define RAD_TO_DEG(x) (x * V_RAD) inline size_t GetNearestPowerOfTwoAbove(size_t v) {