/* * Modern effects for a modern Streamer * Copyright (C) 2017-2018 Michael Fabian Dirks * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "filter-blur.hpp" #include #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/gs/gs-helper.hpp" #include "obs/obs-source-tracker.hpp" #include "strings.hpp" #include "util-math.hpp" // OBS #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4201) #endif #include #include #include #include #ifdef _MSC_VER #pragma warning(pop) #endif // Translation Strings #define ST "Filter.Blur" #define ST_TYPE "Filter.Blur.Type" #define ST_SUBTYPE "Filter.Blur.SubType" #define ST_SIZE "Filter.Blur.Size" #define ST_ANGLE "Filter.Blur.Angle" #define ST_CENTER "Filter.Blur.Center" #define ST_CENTER_X "Filter.Blur.Center.X" #define ST_CENTER_Y "Filter.Blur.Center.Y" #define ST_STEPSCALE "Filter.Blur.StepScale" #define ST_STEPSCALE_X "Filter.Blur.StepScale.X" #define ST_STEPSCALE_Y "Filter.Blur.StepScale.Y" #define ST_MASK "Filter.Blur.Mask" #define ST_MASK_TYPE "Filter.Blur.Mask.Type" #define ST_MASK_TYPE_REGION "Filter.Blur.Mask.Type.Region" #define ST_MASK_TYPE_IMAGE "Filter.Blur.Mask.Type.Image" #define ST_MASK_TYPE_SOURCE "Filter.Blur.Mask.Type.Source" #define ST_MASK_REGION_LEFT "Filter.Blur.Mask.Region.Left" #define ST_MASK_REGION_RIGHT "Filter.Blur.Mask.Region.Right" #define ST_MASK_REGION_TOP "Filter.Blur.Mask.Region.Top" #define ST_MASK_REGION_BOTTOM "Filter.Blur.Mask.Region.Bottom" #define ST_MASK_REGION_FEATHER "Filter.Blur.Mask.Region.Feather" #define ST_MASK_REGION_FEATHER_SHIFT "Filter.Blur.Mask.Region.Feather.Shift" #define ST_MASK_REGION_INVERT "Filter.Blur.Mask.Region.Invert" #define ST_MASK_IMAGE "Filter.Blur.Mask.Image" #define ST_MASK_SOURCE "Filter.Blur.Mask.Source" #define ST_MASK_COLOR "Filter.Blur.Mask.Color" #define ST_MASK_ALPHA "Filter.Blur.Mask.Alpha" #define ST_MASK_MULTIPLIER "Filter.Blur.Mask.Multiplier" 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 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}}, }; static 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 P_INITIALIZER(filterBlurFactoryInitializer) { initializer_functions.push_back([] { filter::blur::blur_factory::initialize(); }); finalizer_functions.push_back([] { filter::blur::blur_factory::finalize(); }); } static std::shared_ptr factory_instance = nullptr; void filter::blur::blur_factory::initialize() { factory_instance = std::make_shared(); } void filter::blur::blur_factory::finalize() { factory_instance.reset(); } std::shared_ptr filter::blur::blur_factory::get() { return factory_instance; } filter::blur::blur_factory::blur_factory() { memset(&_source_info, 0, sizeof(obs_source_info)); _source_info.id = "obs-stream-effects-filter-blur"; _source_info.type = OBS_SOURCE_TYPE_FILTER; _source_info.output_flags = OBS_SOURCE_VIDEO; _source_info.get_name = get_name; _source_info.get_defaults = get_defaults; _source_info.get_properties = get_properties; _source_info.create = create; _source_info.destroy = destroy; _source_info.update = update; _source_info.activate = activate; _source_info.deactivate = deactivate; _source_info.video_tick = video_tick; _source_info.video_render = video_render; _source_info.load = load; obs_register_source(&_source_info); // Translation Cache /// File Filter for Images _translation_map.insert({std::string("image-filter"), std::string(D_TRANSLATE(S_FILETYPE_IMAGES)) + std::string(" (" S_FILEFILTERS_IMAGE ");;") + std::string("* (*.*)")}); } filter::blur::blur_factory::~blur_factory() {} void filter::blur::blur_factory::on_list_fill() { auto gctx = gs::context(); { char* file = obs_module_file("effects/color-conversion._effect"); try { _color_converter_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/mask._effect"); try { _mask_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); } } void filter::blur::blur_factory::on_list_empty() { auto gctx = gs::context(); _color_converter_effect.reset(); _mask_effect.reset(); } std::string const& filter::blur::blur_factory::get_translation(std::string const key) { static std::string none(""); auto found = _translation_map.find(key); if (found != _translation_map.end()) { return found->second; } return none; } void* filter::blur::blur_factory::create(obs_data_t* data, obs_source_t* parent) { if (get()->_sources.empty()) { get()->on_list_fill(); } filter::blur::blur_instance* ptr = new filter::blur::blur_instance(data, parent); get()->_sources.push_back(ptr); return ptr; } void filter::blur::blur_factory::destroy(void* inptr) { filter::blur::blur_instance* ptr = reinterpret_cast(inptr); get()->_sources.remove(ptr); if (get()->_sources.empty()) { get()->on_list_empty(); } delete ptr; } void filter::blur::blur_factory::get_defaults(obs_data_t* data) { // Type, Subtype obs_data_set_default_string(data, ST_TYPE, "box"); obs_data_set_default_string(data, ST_SUBTYPE, "area"); // Parameters obs_data_set_default_int(data, ST_SIZE, 5); obs_data_set_default_double(data, ST_ANGLE, 0.); obs_data_set_default_double(data, ST_CENTER_X, 50.); obs_data_set_default_double(data, ST_CENTER_Y, 50.); obs_data_set_default_bool(data, ST_STEPSCALE, false); obs_data_set_default_double(data, ST_STEPSCALE_X, 1.); obs_data_set_default_double(data, ST_STEPSCALE_Y, 1.); // Masking obs_data_set_default_bool(data, ST_MASK, false); obs_data_set_default_int(data, ST_MASK_TYPE, mask_type::Region); obs_data_set_default_double(data, ST_MASK_REGION_LEFT, 0.0); obs_data_set_default_double(data, ST_MASK_REGION_RIGHT, 0.0); obs_data_set_default_double(data, ST_MASK_REGION_TOP, 0.0); obs_data_set_default_double(data, ST_MASK_REGION_BOTTOM, 0.0); obs_data_set_default_double(data, ST_MASK_REGION_FEATHER, 0.0); obs_data_set_default_double(data, ST_MASK_REGION_FEATHER_SHIFT, 0.0); obs_data_set_default_bool(data, ST_MASK_REGION_INVERT, false); { char* default_file = obs_module_file("white.png"); obs_data_set_default_string(data, ST_MASK_IMAGE, default_file); bfree(default_file); } obs_data_set_default_string(data, ST_MASK_SOURCE, ""); obs_data_set_default_int(data, ST_MASK_COLOR, 0xFFFFFFFFull); obs_data_set_default_double(data, ST_MASK_MULTIPLIER, 1.0); } obs_properties_t* filter::blur::blur_factory::get_properties(void* inptr) { return reinterpret_cast(inptr)->get_properties(); } void filter::blur::blur_factory::update(void* inptr, obs_data_t* settings) { reinterpret_cast(inptr)->update(settings); } void filter::blur::blur_factory::load(void* inptr, obs_data_t* settings) { reinterpret_cast(inptr)->load(settings); } const char* filter::blur::blur_factory::get_name(void*) { return D_TRANSLATE(ST); } uint32_t filter::blur::blur_factory::get_width(void* inptr) { return reinterpret_cast(inptr)->get_width(); } uint32_t filter::blur::blur_factory::get_height(void* inptr) { return reinterpret_cast(inptr)->get_height(); } void filter::blur::blur_factory::activate(void* inptr) { reinterpret_cast(inptr)->activate(); } void filter::blur::blur_factory::deactivate(void* inptr) { reinterpret_cast(inptr)->deactivate(); } void filter::blur::blur_factory::video_tick(void* inptr, float delta) { reinterpret_cast(inptr)->video_tick(delta); } 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_color_converter_effect() { return _color_converter_effect; } std::shared_ptr filter::blur::blur_factory::get_mask_effect() { return _mask_effect; } filter::blur::blur_instance::blur_instance(obs_data_t* settings, obs_source_t* parent) : _self(parent), _source_rendered(false), _output_rendered(false) { _self = parent; // Create RenderTargets try { this->_source_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); this->_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(_self), ex.what()); } update(settings); } filter::blur::blur_instance::~blur_instance() { this->_mask.source.source_texture.reset(); this->_source_rt.reset(); this->_output_texture.reset(); } bool filter::blur::blur_instance::apply_mask_parameters(std::shared_ptr effect, gs_texture_t* original_texture, gs_texture_t* blurred_texture) { if (effect->has_parameter("image_orig")) { effect->get_parameter("image_orig").set_texture(original_texture); } if (effect->has_parameter("image_blur")) { effect->get_parameter("image_blur").set_texture(blurred_texture); } // Region if (_mask.type == mask_type::Region) { if (effect->has_parameter("mask_region_left")) { effect->get_parameter("mask_region_left").set_float(_mask.region.left); } if (effect->has_parameter("mask_region_right")) { effect->get_parameter("mask_region_right").set_float(_mask.region.right); } if (effect->has_parameter("mask_region_top")) { effect->get_parameter("mask_region_top").set_float(_mask.region.top); } if (effect->has_parameter("mask_region_bottom")) { effect->get_parameter("mask_region_bottom").set_float(_mask.region.bottom); } if (effect->has_parameter("mask_region_feather")) { effect->get_parameter("mask_region_feather").set_float(_mask.region.feather); } if (effect->has_parameter("mask_region_feather_shift")) { effect->get_parameter("mask_region_feather_shift").set_float(_mask.region.feather_shift); } } // Image if (_mask.type == mask_type::Image) { if (effect->has_parameter("mask_image")) { if (_mask.image.texture) { effect->get_parameter("mask_image").set_texture(_mask.image.texture); } else { effect->get_parameter("mask_image").set_texture(nullptr); } } } // Source if (_mask.type == mask_type::Source) { if (effect->has_parameter("mask_image")) { if (_mask.source.texture) { effect->get_parameter("mask_image").set_texture(_mask.source.texture); } else { effect->get_parameter("mask_image").set_texture(nullptr); } } } // Shared if (effect->has_parameter("mask_color")) { effect->get_parameter("mask_color").set_float4(_mask.color.r, _mask.color.g, _mask.color.b, _mask.color.a); } if (effect->has_parameter("mask_multiplier")) { effect->get_parameter("mask_multiplier").set_float(_mask.multiplier); } return true; } bool filter::blur::blur_instance::modified_properties(void*, obs_properties_t* props, obs_property* prop, obs_data_t* settings) { obs_property_t* p; const char* propname = obs_property_name(prop); const char* vtype = obs_data_get_string(settings, ST_TYPE); const char* vsubtype = obs_data_get_string(settings, ST_SUBTYPE); // Find new Type auto type_found = list_of_types.find(vtype); if (type_found == list_of_types.end()) { return false; } // Find new Subtype auto subtype_found = list_of_subtypes.find(vsubtype); if (subtype_found == list_of_subtypes.end()) { return false; } // Blur Type if (strcmp(propname, ST_TYPE) == 0) { obs_property_t* prop_subtype = obs_properties_get(props, ST_SUBTYPE); /// 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 subtype_found_idx = list_of_subtypes.find(subtype); if (subtype_found_idx != list_of_subtypes.end()) { disabled = !type_found->second.fn().is_type_supported(subtype_found_idx->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, ST_SUBTYPE, obs_property_list_item_string(prop_subtype, idx)); // Find new Subtype auto subtype_found2 = list_of_subtypes.find(vsubtype); if (subtype_found2 == list_of_subtypes.end()) { subtype_found = list_of_subtypes.end(); } else { subtype_found = subtype_found2; } break; } } } } // Update hover text with new descriptions. if (type_found != list_of_types.end()) { if (type_found->first == "box") { obs_property_set_long_description(obs_properties_get(props, ST_TYPE), D_TRANSLATE(D_DESC(S_BLUR_TYPE_BOX))); } else if (type_found->first == "box_linear") { obs_property_set_long_description(obs_properties_get(props, ST_TYPE), D_TRANSLATE(D_DESC(S_BLUR_TYPE_BOX_LINEAR))); } else if (type_found->first == "gaussian") { obs_property_set_long_description(obs_properties_get(props, ST_TYPE), D_TRANSLATE(D_DESC(S_BLUR_TYPE_GAUSSIAN))); } else if (type_found->first == "gaussian_linear") { obs_property_set_long_description(obs_properties_get(props, ST_TYPE), D_TRANSLATE(D_DESC(S_BLUR_TYPE_GAUSSIAN_LINEAR))); } } else { obs_property_set_long_description(obs_properties_get(props, ST_TYPE), D_TRANSLATE(D_DESC(ST_TYPE))); } if (subtype_found != list_of_subtypes.end()) { if (subtype_found->first == "area") { obs_property_set_long_description(obs_properties_get(props, ST_SUBTYPE), D_TRANSLATE(D_DESC(S_BLUR_SUBTYPE_AREA))); } else if (subtype_found->first == "directional") { obs_property_set_long_description(obs_properties_get(props, ST_SUBTYPE), D_TRANSLATE(D_DESC(S_BLUR_SUBTYPE_DIRECTIONAL))); } else if (subtype_found->first == "rotational") { obs_property_set_long_description(obs_properties_get(props, ST_SUBTYPE), D_TRANSLATE(D_DESC(S_BLUR_SUBTYPE_ROTATIONAL))); } else if (subtype_found->first == "zoom") { obs_property_set_long_description(obs_properties_get(props, ST_SUBTYPE), D_TRANSLATE(D_DESC(S_BLUR_SUBTYPE_ZOOM))); } } else { obs_property_set_long_description(obs_properties_get(props, ST_SUBTYPE), D_TRANSLATE(D_DESC(ST_SUBTYPE))); } // Blur Sub-Type { bool has_angle_support = (subtype_found->second.type == ::gfx::blur::type::Directional) || (subtype_found->second.type == ::gfx::blur::type::Rotational); bool has_center_support = (subtype_found->second.type == ::gfx::blur::type::Rotational) || (subtype_found->second.type == ::gfx::blur::type::Zoom); bool has_stepscale_support = type_found->second.fn().is_step_scale_supported(subtype_found->second.type); bool show_scaling = obs_data_get_bool(settings, ST_STEPSCALE) && has_stepscale_support; /// Size p = obs_properties_get(props, ST_SIZE); obs_property_float_set_limits(p, type_found->second.fn().get_min_size(subtype_found->second.type), type_found->second.fn().get_max_size(subtype_found->second.type), type_found->second.fn().get_step_size(subtype_found->second.type)); /// Angle p = obs_properties_get(props, ST_ANGLE); obs_property_set_visible(p, has_angle_support); obs_property_float_set_limits(p, type_found->second.fn().get_min_angle(subtype_found->second.type), type_found->second.fn().get_max_angle(subtype_found->second.type), type_found->second.fn().get_step_angle(subtype_found->second.type)); /// Center, Radius obs_property_set_visible(obs_properties_get(props, ST_CENTER_X), has_center_support); obs_property_set_visible(obs_properties_get(props, ST_CENTER_Y), has_center_support); /// Step Scaling obs_property_set_visible(obs_properties_get(props, ST_STEPSCALE), has_stepscale_support); p = obs_properties_get(props, ST_STEPSCALE_X); obs_property_set_visible(p, show_scaling); obs_property_float_set_limits(p, type_found->second.fn().get_min_step_scale_x(subtype_found->second.type), type_found->second.fn().get_max_step_scale_x(subtype_found->second.type), type_found->second.fn().get_step_step_scale_x(subtype_found->second.type)); p = obs_properties_get(props, ST_STEPSCALE_Y); obs_property_set_visible(p, show_scaling); obs_property_float_set_limits(p, type_found->second.fn().get_min_step_scale_x(subtype_found->second.type), type_found->second.fn().get_max_step_scale_x(subtype_found->second.type), type_found->second.fn().get_step_step_scale_x(subtype_found->second.type)); } { // Masking bool show_mask = obs_data_get_bool(settings, ST_MASK); mask_type mtype = static_cast(obs_data_get_int(settings, ST_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, ST_MASK_TYPE), show_mask); obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_LEFT), show_region); obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_TOP), show_region); obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_RIGHT), show_region); obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_BOTTOM), show_region); obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_FEATHER), show_region); obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_FEATHER_SHIFT), show_region); obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_INVERT), show_region); obs_property_set_visible(obs_properties_get(props, ST_MASK_IMAGE), show_image); obs_property_set_visible(obs_properties_get(props, ST_MASK_SOURCE), show_source); obs_property_set_visible(obs_properties_get(props, ST_MASK_COLOR), show_image || show_source); obs_property_set_visible(obs_properties_get(props, ST_MASK_ALPHA), show_image || show_source); obs_property_set_visible(obs_properties_get(props, ST_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, ST_TYPE, "box"); } else if (old_blur == 1) { // Gaussian obs_data_set_string(settings, ST_TYPE, "gaussian"); } else if (old_blur == 2) { // Bilateral, no longer included. obs_data_set_string(settings, ST_TYPE, "box"); } else if (old_blur == 3) { // Box Linear obs_data_set_string(settings, ST_TYPE, "box_linear"); } else if (old_blur == 4) { // Gaussian Linear obs_data_set_string(settings, ST_TYPE, "gaussian_linear"); } else { obs_data_set_string(settings, ST_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, ST_SUBTYPE, "directional"); } else { obs_data_set_string(settings, ST_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, ST_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; // Blur Type and Sub-Type { p = obs_properties_add_list(pr, ST_TYPE, D_TRANSLATE(ST_TYPE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_TYPE))); obs_property_set_modified_callback2(p, modified_properties, this); obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_TYPE_BOX), "box"); obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_TYPE_BOX_LINEAR), "box_linear"); obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_TYPE_GAUSSIAN), "gaussian"); obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_TYPE_GAUSSIAN_LINEAR), "gaussian_linear"); obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_TYPE_DUALFILTERING), "dual_filtering"); p = obs_properties_add_list(pr, ST_SUBTYPE, D_TRANSLATE(ST_SUBTYPE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_SUBTYPE))); obs_property_set_modified_callback2(p, modified_properties, this); obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_SUBTYPE_AREA), "area"); obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_SUBTYPE_DIRECTIONAL), "directional"); obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_SUBTYPE_ROTATIONAL), "rotational"); obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_SUBTYPE_ZOOM), "zoom"); } // Blur Parameters { p = obs_properties_add_float_slider(pr, ST_SIZE, D_TRANSLATE(ST_SIZE), 1, 32767, 1); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_SIZE))); p = obs_properties_add_float_slider(pr, ST_ANGLE, D_TRANSLATE(ST_ANGLE), -180.0, 180.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_ANGLE))); p = obs_properties_add_float_slider(pr, ST_CENTER_X, D_TRANSLATE(ST_CENTER_X), 0.00, 100.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_CENTER_X))); p = obs_properties_add_float_slider(pr, ST_CENTER_Y, D_TRANSLATE(ST_CENTER_Y), 0.00, 100.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_CENTER_Y))); p = obs_properties_add_bool(pr, ST_STEPSCALE, D_TRANSLATE(ST_STEPSCALE)); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_STEPSCALE))); obs_property_set_modified_callback2(p, modified_properties, this); p = obs_properties_add_float_slider(pr, ST_STEPSCALE_X, D_TRANSLATE(ST_STEPSCALE_X), 0.0, 1000.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_STEPSCALE_X))); p = obs_properties_add_float_slider(pr, ST_STEPSCALE_Y, D_TRANSLATE(ST_STEPSCALE_Y), 0.0, 1000.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_STEPSCALE_Y))); } // Masking { p = obs_properties_add_bool(pr, ST_MASK, D_TRANSLATE(ST_MASK)); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK))); obs_property_set_modified_callback2(p, modified_properties, this); p = obs_properties_add_list(pr, ST_MASK_TYPE, D_TRANSLATE(ST_MASK_TYPE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_TYPE))); obs_property_set_modified_callback2(p, modified_properties, this); obs_property_list_add_int(p, D_TRANSLATE(ST_MASK_TYPE_REGION), mask_type::Region); obs_property_list_add_int(p, D_TRANSLATE(ST_MASK_TYPE_IMAGE), mask_type::Image); obs_property_list_add_int(p, D_TRANSLATE(ST_MASK_TYPE_SOURCE), mask_type::Source); /// Region p = obs_properties_add_float_slider(pr, ST_MASK_REGION_LEFT, D_TRANSLATE(ST_MASK_REGION_LEFT), 0.0, 100.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_LEFT))); p = obs_properties_add_float_slider(pr, ST_MASK_REGION_TOP, D_TRANSLATE(ST_MASK_REGION_TOP), 0.0, 100.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_TOP))); p = obs_properties_add_float_slider(pr, ST_MASK_REGION_RIGHT, D_TRANSLATE(ST_MASK_REGION_RIGHT), 0.0, 100.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_RIGHT))); p = obs_properties_add_float_slider(pr, ST_MASK_REGION_BOTTOM, D_TRANSLATE(ST_MASK_REGION_BOTTOM), 0.0, 100.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_BOTTOM))); p = obs_properties_add_float_slider(pr, ST_MASK_REGION_FEATHER, D_TRANSLATE(ST_MASK_REGION_FEATHER), 0.0, 50.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_FEATHER))); p = obs_properties_add_float_slider(pr, ST_MASK_REGION_FEATHER_SHIFT, D_TRANSLATE(ST_MASK_REGION_FEATHER_SHIFT), -100.0, 100.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_FEATHER_SHIFT))); p = obs_properties_add_bool(pr, ST_MASK_REGION_INVERT, D_TRANSLATE(ST_MASK_REGION_INVERT)); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_INVERT))); /// Image p = obs_properties_add_path(pr, ST_MASK_IMAGE, D_TRANSLATE(ST_MASK_IMAGE), OBS_PATH_FILE, filter::blur::blur_factory::get()->get_translation("image-filter").c_str(), nullptr); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_IMAGE))); /// Source p = obs_properties_add_list(pr, ST_MASK_SOURCE, D_TRANSLATE(ST_MASK_SOURCE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_SOURCE))); obs_property_list_add_string(p, "", ""); obs::source_tracker::get()->enumerate( [&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( [&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); /// Shared p = obs_properties_add_color(pr, ST_MASK_COLOR, D_TRANSLATE(ST_MASK_COLOR)); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_COLOR))); p = obs_properties_add_float_slider(pr, ST_MASK_ALPHA, D_TRANSLATE(ST_MASK_ALPHA), 0.0, 100.0, 0.1); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_ALPHA))); p = obs_properties_add_float_slider(pr, ST_MASK_MULTIPLIER, D_TRANSLATE(ST_MASK_MULTIPLIER), 0.0, 10.0, 0.01); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_MULTIPLIER))); } return pr; } void filter::blur::blur_instance::update(obs_data_t* settings) { // Ensure backwards compatibility. this->translate_old_settings(settings); { // Blur Type const char* blur_type = obs_data_get_string(settings, ST_TYPE); const char* blur_subtype = obs_data_get_string(settings, ST_SUBTYPE); const char* last_blur_type = obs_data_get_string(settings, ST_TYPE ".last"); 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) || (_blur->get_type() != subtype_found->second.type)) { if (type_found->second.fn().is_type_supported(subtype_found->second.type)) { _blur = type_found->second.fn().create(subtype_found->second.type); } } } } } { // Blur Parameters this->_blur_size = obs_data_get_double(settings, ST_SIZE); this->_blur_angle = obs_data_get_double(settings, ST_ANGLE); this->_blur_center.first = obs_data_get_double(settings, ST_CENTER_X) / 100.0; this->_blur_center.second = obs_data_get_double(settings, ST_CENTER_Y) / 100.0; // Scaling this->_blur_step_scaling = obs_data_get_bool(settings, ST_STEPSCALE); this->_blur_step_scale.first = obs_data_get_double(settings, ST_STEPSCALE_X) / 100.0; this->_blur_step_scale.second = obs_data_get_double(settings, ST_STEPSCALE_Y) / 100.0; } { // Masking _mask.enabled = obs_data_get_bool(settings, ST_MASK); if (_mask.enabled) { _mask.type = static_cast(obs_data_get_int(settings, ST_MASK_TYPE)); switch (_mask.type) { case mask_type::Region: _mask.region.left = float_t(obs_data_get_double(settings, ST_MASK_REGION_LEFT) / 100.0); _mask.region.top = float_t(obs_data_get_double(settings, ST_MASK_REGION_TOP) / 100.0); _mask.region.right = 1.0f - float_t(obs_data_get_double(settings, ST_MASK_REGION_RIGHT) / 100.0); _mask.region.bottom = 1.0f - float_t(obs_data_get_double(settings, ST_MASK_REGION_BOTTOM) / 100.0); _mask.region.feather = float_t(obs_data_get_double(settings, ST_MASK_REGION_FEATHER) / 100.0); _mask.region.feather_shift = float_t(obs_data_get_double(settings, ST_MASK_REGION_FEATHER_SHIFT) / 100.0); _mask.region.invert = obs_data_get_bool(settings, ST_MASK_REGION_INVERT); break; case mask_type::Image: _mask.image.path = obs_data_get_string(settings, ST_MASK_IMAGE); break; case mask_type::Source: _mask.source.name = obs_data_get_string(settings, ST_MASK_SOURCE); break; } if ((_mask.type == mask_type::Image) || (_mask.type == mask_type::Source)) { uint32_t color = static_cast(obs_data_get_int(settings, ST_MASK_COLOR)); _mask.color.r = ((color >> 0) & 0xFF) / 255.0f; _mask.color.g = ((color >> 8) & 0xFF) / 255.0f; _mask.color.b = ((color >> 16) & 0xFF) / 255.0f; _mask.color.a = static_cast(obs_data_get_double(settings, ST_MASK_ALPHA)); _mask.multiplier = float_t(obs_data_get_double(settings, ST_MASK_MULTIPLIER)); } } } } void filter::blur::blur_instance::load(obs_data_t* settings) { update(settings); } uint32_t filter::blur::blur_instance::get_width() { return uint32_t(0); } uint32_t filter::blur::blur_instance::get_height() { return uint32_t(0); } void filter::blur::blur_instance::activate() {} void filter::blur::blur_instance::deactivate() {} void filter::blur::blur_instance::video_tick(float) { // Blur if (_blur) { _blur->set_size(_blur_size); if (_blur_step_scaling) { _blur->set_step_scale(_blur_step_scale.first, _blur_step_scale.second); } else { _blur->set_step_scale(1.0, 1.0); } if ((_blur->get_type() == ::gfx::blur::type::Directional) || (_blur->get_type() == ::gfx::blur::type::Rotational)) { auto obj = std::dynamic_pointer_cast<::gfx::blur::base_angle>(_blur); obj->set_angle(_blur_angle); } if ((_blur->get_type() == ::gfx::blur::type::Zoom) || (_blur->get_type() == ::gfx::blur::type::Rotational)) { auto obj = std::dynamic_pointer_cast<::gfx::blur::base_center>(_blur); obj->set_center(_blur_center.first, _blur_center.second); } } // Load Mask if (_mask.type == mask_type::Image) { if (_mask.image.path_old != _mask.image.path) { try { _mask.image.texture = std::make_shared(_mask.image.path); _mask.image.path_old = _mask.image.path; } catch (...) { P_LOG_ERROR(" Instance '%s' failed to load image '%s'.", obs_source_get_name(_self), _mask.image.path.c_str()); } } } else if (_mask.type == mask_type::Source) { if (_mask.source.name_old != _mask.source.name) { try { _mask.source.source_texture = std::make_shared(_mask.source.name, _self); _mask.source.is_scene = (obs_scene_from_source(_mask.source.source_texture->get_object()) != nullptr); _mask.source.name_old = _mask.source.name; } catch (...) { P_LOG_ERROR(" Instance '%s' failed to grab source '%s'.", obs_source_get_name(_self), _mask.source.name.c_str()); } } } _source_rendered = false; _output_rendered = false; } void filter::blur::blur_instance::video_render(gs_effect_t* effect) { obs_source_t* parent = obs_filter_get_parent(this->_self); obs_source_t* target = obs_filter_get_target(this->_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(); vec4 black; vec4_set(&black, 0, 0, 0, 0); // Verify that we can actually run first. if (!target || !parent || !this->_self || !this->_blur || (baseW == 0) || (baseH == 0)) { obs_source_skip_video_filter(this->_self); return; } if (!_source_rendered) { // Source To Texture { if (obs_source_process_filter_begin(this->_self, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING)) { { auto op = this->_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); 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_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->_self, defaultEffect, baseW, baseH); gs_blend_state_pop(); } _source_texture = this->_source_rt->get_texture(); if (!_source_texture) { obs_source_skip_video_filter(this->_self); return; } } else { obs_source_skip_video_filter(this->_self); return; } } _source_rendered = true; } if (!_output_rendered) { _blur->set_input(_source_texture); _output_texture = _blur->render(); // Mask if (_mask.enabled) { 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::string technique = ""; switch (this->_mask.type) { case Region: if (this->_mask.region.feather > std::numeric_limits::epsilon()) { if (this->_mask.region.invert) { technique = "RegionFeatherInverted"; } else { technique = "RegionFeather"; } } else { if (this->_mask.region.invert) { technique = "RegionInverted"; } else { technique = "Region"; } } break; case Image: case Source: technique = "Image"; break; } if (_mask.source.source_texture) { uint32_t source_width = obs_source_get_width(this->_mask.source.source_texture->get_object()); uint32_t source_height = obs_source_get_height(this->_mask.source.source_texture->get_object()); if (source_width == 0) { source_width = baseW; } if (source_height == 0) { source_height = baseH; } if (this->_mask.source.is_scene) { obs_video_info ovi; if (obs_get_video_info(&ovi)) { source_width = ovi.base_width; source_height = ovi.base_height; } } this->_mask.source.texture = this->_mask.source.source_texture->render(source_width, source_height); } std::shared_ptr mask_effect = blur_factory::get()->get_mask_effect(); apply_mask_parameters(mask_effect, _source_texture->get_object(), _output_texture->get_object()); try { auto op = this->_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(_output_texture->get_object(), 0, baseW, baseH); } } catch (const std::exception&) { gs_blend_state_pop(); obs_source_skip_video_filter(this->_self); return; } gs_blend_state_pop(); if (!(_output_texture = this->_output_rt->get_texture())) { obs_source_skip_video_filter(this->_self); return; } } _output_rendered = true; } // 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_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"; 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->_self)); obs_source_skip_video_filter(_self); return; } else { gs_effect_set_texture(param, _output_texture->get_object()); } while (gs_effect_loop(finalEffect, technique)) { gs_draw_sprite(_output_texture->get_object(), 0, baseW, baseH); } } }