diff --git a/data/effects/channel-mask.effect b/data/effects/channel-mask.effect index 9224c6da..99ca22c4 100644 --- a/data/effects/channel-mask.effect +++ b/data/effects/channel-mask.effect @@ -39,6 +39,13 @@ uniform float4x4 pMaskMatrix < // 0, 0, 1, 0, // 0, 0, 0, 1); >; +/* pMaskMatrix Content: + * [Red,Green,Blue,Alpha] (Red) + * [Red,Green,Blue,Alpha] (Green) + * [Red,Green,Blue,Alpha] (Blue) + * [Red,Green,Blue,Alpha] (Alpha) + */ + uniform float4 pMaskMultiplier < string name = "Channel Multiplier"; // float4 minimum = float4(-100, -100, -100, -100); @@ -86,19 +93,32 @@ VertDataOut VSDefault(VertDataIn v_in) // -------------------------------------------------------------------------------- // // Channel Masking + float4 PSChannelMask(VertDataOut v_in) : TARGET { + // Sample both inputs at current UV. float4 imageA = pMaskInputA.Sample(maskSamplerA, v_in.uv); - - // Create Mask - float4 mask = pMaskBase; float4 imageB = pMaskInputB.Sample(maskSamplerB, v_in.uv); + + // Assign the base value as the mask. + float4 mask = pMaskBase; + + // pMaskMatrix[0] contains all the "x Value from Red Input" mask += pMaskMatrix[0] * imageB.r; + + // pMaskMatrix[1] contains all the "x Value from Green Input" mask += pMaskMatrix[1] * imageB.g; + + // pMaskMatrix[2] contains all the "x Value from Blue Input" mask += pMaskMatrix[2] * imageB.b; + + // pMaskMatrix[3] contains all the "x Value from Alpha Input" mask += pMaskMatrix[3] * imageB.a; + + // Multiply the mask value by the per channel multiplier. mask *= pMaskMultiplier; + // And finally multiply the input image with the mask. return imageA * mask; } diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 83f4186a..7394f4e2 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -173,14 +173,13 @@ Filter.Displacement.Scale.Type.Description="Type of the displacement scale, with # Filter - Dynamic Mask Filter.DynamicMask="Dynamic Mask" Filter.DynamicMask.Input="Input Source" -Filter.DynamicMask.Input.Description="TODO" -Filter.DynamicMask.Channel="Channel" -Filter.DynamicMask.Channel.Description="TODO" -Filter.DynamicMask.Channel.Value="%s Channel Value" -Filter.DynamicMask.Channel.Value.Description="TODO" -Filter.DynamicMask.Channel.Multiplier="%s Channel Multiplier" +Filter.DynamicMask.Input.Description="The source to use for the complex math that is happening in the filter." +Filter.DynamicMask.Channel="%s Channel" +Filter.DynamicMask.Channel.Value="Base Value" +Filter.DynamicMask.Channel.Value.Description="Initial Value for the mask, added to the channel mask." +Filter.DynamicMask.Channel.Multiplier="Multiplier" Filter.DynamicMask.Channel.Multiplier.Description="TODO" -Filter.DynamicMask.Channel.Input="%s Channel Value from %s Input Channel" +Filter.DynamicMask.Channel.Input="%s Input Value" Filter.DynamicMask.Channel.Input.Description="TODO" # Filter - SDF Effects diff --git a/source/filters/filter-dynamic-mask.cpp b/source/filters/filter-dynamic-mask.cpp index afe3f887..12b609e8 100644 --- a/source/filters/filter-dynamic-mask.cpp +++ b/source/filters/filter-dynamic-mask.cpp @@ -39,6 +39,9 @@ #define ST_CHANNEL_MULTIPLIER "Filter.DynamicMask.Channel.Multiplier" #define ST_CHANNEL_INPUT "Filter.DynamicMask.Channel.Input" +std::shared_ptr + filter::dynamic_mask::dynamic_mask_factory::factory_instance = nullptr; + static std::pair channel_translations[] = { {filter::dynamic_mask::channel::Red, S_CHANNEL_RED}, {filter::dynamic_mask::channel::Green, S_CHANNEL_GREEN}, @@ -46,35 +49,46 @@ static std::pair channel_translation {filter::dynamic_mask::channel::Alpha, S_CHANNEL_ALPHA}, }; -static const char* get_name(void*) noexcept try { +filter::dynamic_mask::dynamic_mask_factory::dynamic_mask_factory() +{ + _info.id = "obs-stream-effects-filter-dynamic-mask"; + _info.type = OBS_SOURCE_TYPE_FILTER; + _info.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW; + + // Strip all unnecessary callbacks. + _info.get_width = nullptr; + _info.get_height = nullptr; + _info.activate = nullptr; + _info.deactivate = nullptr; + _info.show = nullptr; + _info.hide = nullptr; + _info.mouse_click = nullptr; + _info.mouse_move = nullptr; + _info.mouse_wheel = nullptr; + _info.key_click = nullptr; + _info.focus = nullptr; + _info.filter_remove = nullptr; + _info.enum_active_sources = nullptr; + _info.enum_all_sources = nullptr; + _info.transition_start = nullptr; + _info.transition_stop = nullptr; + _info.filter_audio = nullptr; + _info.filter_video = nullptr; + _info.audio_mix = nullptr; + _info.audio_render = nullptr; + + obs_register_source(&_info); +} + +filter::dynamic_mask::dynamic_mask_factory::~dynamic_mask_factory() {} + +const char* filter::dynamic_mask::dynamic_mask_factory::get_name() +{ return D_TRANSLATE(ST); -} catch (const std::exception& ex) { - P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); - return ""; -} catch (...) { - P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); - return ""; } -static void* create(obs_data_t* data, obs_source_t* source) noexcept try { - return new filter::dynamic_mask::dynamic_mask_instance(data, source); -} catch (const std::exception& ex) { - P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); - return nullptr; -} catch (...) { - P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); - return nullptr; -} - -static void destroy(void* ptr) noexcept try { - delete reinterpret_cast(ptr); -} catch (const std::exception& ex) { - P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); -} catch (...) { - P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); -} - -static void get_defaults2(void* type_data, obs_data_t* data) noexcept try { +void filter::dynamic_mask::dynamic_mask_factory::get_defaults2(obs_data_t* data) +{ obs_data_set_default_int(data, ST_CHANNEL, static_cast(filter::dynamic_mask::channel::Red)); for (auto kv : channel_translations) { obs_data_set_default_double(data, (std::string(ST_CHANNEL_VALUE) + "." + kv.second).c_str(), 1.0); @@ -84,143 +98,18 @@ static void get_defaults2(void* type_data, obs_data_t* data) noexcept try { data, (std::string(ST_CHANNEL_INPUT) + "." + kv.second + "." + kv2.second).c_str(), 0.0); } } -} catch (const std::exception& ex) { - P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); -} catch (...) { - P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); } -static obs_properties_t* get_properties2(void* ptr, void* type_data) noexcept try { - obs_properties_t* props = obs_properties_create_param(type_data, nullptr); - reinterpret_cast(ptr)->get_properties(props); - return props; -} catch (const std::exception& ex) { - P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); - return nullptr; -} catch (...) { - P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); - return nullptr; -} - -static void update(void* ptr, obs_data_t* data) noexcept try { - reinterpret_cast(ptr)->update(data); -} catch (const std::exception& ex) { - P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); -} catch (...) { - P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); -} - -static void load(void* ptr, obs_data_t* data) noexcept try { - reinterpret_cast(ptr)->load(data); -} catch (const std::exception& ex) { - P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); -} catch (...) { - P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); -} - -static void save(void* ptr, obs_data_t* data) noexcept try { - reinterpret_cast(ptr)->save(data); -} catch (const std::exception& ex) { - P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); -} catch (...) { - P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); -} - -static void video_tick(void* ptr, float time) noexcept try { - reinterpret_cast(ptr)->video_tick(time); -} catch (const std::exception& ex) { - P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); -} catch (...) { - P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); -} - -static void video_render(void* ptr, gs_effect_t* effect) noexcept try { - reinterpret_cast(ptr)->video_render(effect); -} catch (const std::exception& ex) { - P_LOG_ERROR("Unexpected exception in function '%s': %s.", __FUNCTION_NAME__, ex.what()); -} catch (...) { - P_LOG_ERROR("Unexpected exception in function '%s'.", __FUNCTION_NAME__); -} - -static std::shared_ptr factory_instance = nullptr; - -void filter::dynamic_mask::dynamic_mask_factory::initialize() +obs_properties_t* + filter::dynamic_mask::dynamic_mask_factory::get_properties2(filter::dynamic_mask::dynamic_mask_instance* data) { - factory_instance = std::make_shared(); -} + obs_properties_t* props = obs_properties_create(); + obs_property_t* p; -void filter::dynamic_mask::dynamic_mask_factory::finalize() -{ - factory_instance.reset(); -} + _translation_cache.clear(); -std::shared_ptr filter::dynamic_mask::dynamic_mask_factory::get() -{ - return factory_instance; -} - -filter::dynamic_mask::dynamic_mask_factory::dynamic_mask_factory() -{ - memset(&_source_info, 0, sizeof(obs_source_info)); - _source_info.id = "obs-stream-effects-filter-dynamic-mask"; - _source_info.type = OBS_SOURCE_TYPE_FILTER; - _source_info.output_flags = OBS_SOURCE_VIDEO; - _source_info.get_name = get_name; - _source_info.create = create; - _source_info.destroy = destroy; - _source_info.get_defaults2 = get_defaults2; - _source_info.get_properties2 = get_properties2; - _source_info.update = update; - _source_info.load = load; - _source_info.save = save; - _source_info.video_tick = video_tick; - _source_info.video_render = video_render; - - obs_register_source(&_source_info); -} - -filter::dynamic_mask::dynamic_mask_factory::~dynamic_mask_factory() {} - -filter::dynamic_mask::dynamic_mask_instance::dynamic_mask_instance(obs_data_t* data, obs_source_t* self) - : _self(self), _have_filter_texture(false), _have_input_texture(false), _have_final_texture(false), _precalc() -{ - this->update(data); - - this->_filter_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); - this->_final_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); - - { - char* file = obs_module_file("effects/channel-mask.effect"); - try { - this->_effect = gs::effect::create(file); - } catch (const std::exception& ex) { - P_LOG_ERROR("Loading channel mask _effect failed with error(s):\n%s", ex.what()); - } - assert(this->_effect != nullptr); - bfree(file); - } -} - -filter::dynamic_mask::dynamic_mask_instance::~dynamic_mask_instance() {} - -uint32_t filter::dynamic_mask::dynamic_mask_instance::get_width() -{ - return 0; -} - -uint32_t filter::dynamic_mask::dynamic_mask_instance::get_height() -{ - return 0; -} - -void filter::dynamic_mask::dynamic_mask_instance::get_properties(obs_properties_t* properties) -{ - obs_property_t* p; - - this->_translation_map.clear(); - - { - p = obs_properties_add_list(properties, ST_INPUT, D_TRANSLATE(ST_INPUT), OBS_COMBO_TYPE_LIST, + { // Input + p = obs_properties_add_list(props, ST_INPUT, D_TRANSLATE(ST_INPUT), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING); obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_INPUT))); obs_property_list_add_string(p, "", ""); @@ -242,68 +131,79 @@ void filter::dynamic_mask::dynamic_mask_instance::get_properties(obs_properties_ obs::source_tracker::filter_scenes); } - { - p = obs_properties_add_list(properties, ST_CHANNEL, D_TRANSLATE(ST_CHANNEL), OBS_COMBO_TYPE_LIST, - OBS_COMBO_FORMAT_INT); - obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_CHANNEL))); - for (auto kv : channel_translations) { - obs_property_list_add_int(p, D_TRANSLATE(kv.second), static_cast(kv.first)); + const char* pri_chs[] = {S_CHANNEL_RED, S_CHANNEL_GREEN, S_CHANNEL_BLUE, S_CHANNEL_ALPHA}; + for (auto pri_ch : pri_chs) { + auto grp = obs_properties_create(); + + { + _translation_cache.push_back(translate_string(D_TRANSLATE(ST_CHANNEL_VALUE), D_TRANSLATE(pri_ch))); + std::string buf = std::string(ST_CHANNEL_VALUE) + "." + pri_ch; + p = obs_properties_add_float_slider(grp, buf.c_str(), _translation_cache.back().c_str(), -100.0, 100.0, + 0.01); + obs_property_set_long_description(p, D_TRANSLATE(ST_CHANNEL_VALUE)); } - obs_property_set_modified_callback2(p, modified, this); - for (auto kv : channel_translations) { - std::string color = D_TRANSLATE(kv.second); + const char* sec_chs[] = {S_CHANNEL_RED, S_CHANNEL_GREEN, S_CHANNEL_BLUE, S_CHANNEL_ALPHA}; + for (auto sec_ch : sec_chs) { + _translation_cache.push_back(translate_string(D_TRANSLATE(ST_CHANNEL_INPUT), D_TRANSLATE(sec_ch))); + std::string buf = std::string(ST_CHANNEL_INPUT) + "." + pri_ch + "." + sec_ch; + p = obs_properties_add_float_slider(grp, buf.c_str(), _translation_cache.back().c_str(), -100.0, 100.0, + 0.01); + obs_property_set_long_description(p, D_TRANSLATE(ST_CHANNEL_INPUT)); + } - { - std::string _chv = D_TRANSLATE(ST_CHANNEL_VALUE); - std::vector _chv_data(_chv.size() * 2 + color.size() * 2, '\0'); - snprintf(_chv_data.data(), _chv_data.size(), _chv.c_str(), color.c_str()); - auto _chv_key = std::tuple{kv.first, channel::Invalid, std::string(ST_CHANNEL_VALUE)}; - _translation_map.emplace(_chv_key, std::string(_chv_data.begin(), _chv_data.end())); - auto chv = _translation_map.find(_chv_key); - std::string chv_key = std::string(ST_CHANNEL_VALUE) + "." + kv.second; + { + _translation_cache.push_back(translate_string(D_TRANSLATE(ST_CHANNEL_MULTIPLIER), D_TRANSLATE(pri_ch))); + std::string buf = std::string(ST_CHANNEL_MULTIPLIER) + "." + pri_ch; + p = obs_properties_add_float_slider(grp, buf.c_str(), _translation_cache.back().c_str(), -100.0, 100.0, + 0.01); + obs_property_set_long_description(p, D_TRANSLATE(ST_CHANNEL_MULTIPLIER)); + } - p = obs_properties_add_float_slider(properties, chv_key.c_str(), chv->second.c_str(), -100.0, 100.0, - 0.01); - obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_CHANNEL_VALUE))); - - std::string _chm = D_TRANSLATE(ST_CHANNEL_MULTIPLIER); - std::vector _chm_data(_chm.size() * 2 + color.size() * 2, '\0'); - snprintf(_chm_data.data(), _chm_data.size(), _chm.c_str(), color.c_str()); - auto _chm_key = std::tuple{kv.first, channel::Invalid, std::string(ST_CHANNEL_MULTIPLIER)}; - _translation_map.emplace(_chm_key, std::string(_chm_data.begin(), _chm_data.end())); - auto chm = _translation_map.find(_chm_key); - std::string chm_key = std::string(ST_CHANNEL_MULTIPLIER) + "." + kv.second; - - p = obs_properties_add_float_slider(properties, chm_key.c_str(), chm->second.c_str(), -100.0, 100.0, - 0.01); - obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_CHANNEL_MULTIPLIER))); - } + { + _translation_cache.push_back(translate_string(D_TRANSLATE(ST_CHANNEL), D_TRANSLATE(pri_ch))); + std::string buf = std::string(ST_CHANNEL) + "." + pri_ch; + obs_properties_add_group(props, buf.c_str(), _translation_cache.back().c_str(), + obs_group_type::OBS_GROUP_NORMAL, grp); } } - { - for (auto kv1 : channel_translations) { - std::string color1 = D_TRANSLATE(kv1.second); - for (auto kv2 : channel_translations) { - std::string color2 = D_TRANSLATE(kv2.second); - - std::string _chm = D_TRANSLATE(ST_CHANNEL_INPUT); - std::vector _chm_data(_chm.size() * 2 + color1.size() * 2 + color2.size() * 2, '\0'); - snprintf(_chm_data.data(), _chm_data.size(), _chm.c_str(), color1.c_str(), color2.c_str()); - auto _chm_key = std::tuple{kv1.first, kv2.first, std::string(ST_CHANNEL_INPUT)}; - _translation_map.emplace(_chm_key, std::string(_chm_data.begin(), _chm_data.end())); - auto chm = _translation_map.find(_chm_key); - std::string chm_key = std::string(ST_CHANNEL_INPUT) + "." + kv1.second + "." + kv2.second; - - p = obs_properties_add_float_slider(properties, chm_key.c_str(), chm->second.c_str(), -100.0, 100.0, - 0.01); - obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_CHANNEL_INPUT))); - } - } - } + return props; } +std::string filter::dynamic_mask::dynamic_mask_factory::translate_string(std::string format, ...) +{ + va_list vargs; + va_start(vargs, format); + std::vector buffer(2048); + size_t len = vsnprintf(buffer.data(), buffer.size(), format.c_str(), vargs); + va_end(vargs); + return std::string(buffer.data(), buffer.data() + len); +} + +filter::dynamic_mask::dynamic_mask_instance::dynamic_mask_instance(obs_data_t* settings, obs_source_t* self) + : obs::source_instance(settings, self), _have_filter_texture(false), _have_input_texture(false), + _have_final_texture(false), _precalc() +{ + this->_filter_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); + this->_final_rt = std::make_shared(GS_RGBA, GS_ZS_NONE); + + { + char* file = obs_module_file("effects/channel-mask.effect"); + try { + this->_effect = gs::effect::create(file); + } catch (const std::exception& ex) { + P_LOG_ERROR("Loading channel mask _effect failed with error(s):\n%s", ex.what()); + } + assert(this->_effect != nullptr); + bfree(file); + } + + this->update(settings); +} + +filter::dynamic_mask::dynamic_mask_instance::~dynamic_mask_instance() {} + void filter::dynamic_mask::dynamic_mask_instance::update(obs_data_t* settings) { // Update source. @@ -410,7 +310,8 @@ void filter::dynamic_mask::dynamic_mask_instance::input_renamed(obs::source*, st } bool filter::dynamic_mask::dynamic_mask_instance::modified(void*, obs_properties_t* properties, obs_property_t*, - obs_data_t* settings) noexcept try { + obs_data_t* settings) noexcept +try { channel mask = static_cast(obs_data_get_int(settings, ST_CHANNEL)); for (auto kv1 : channel_translations) { diff --git a/source/filters/filter-dynamic-mask.hpp b/source/filters/filter-dynamic-mask.hpp index 4af55ba5..90501b6d 100644 --- a/source/filters/filter-dynamic-mask.hpp +++ b/source/filters/filter-dynamic-mask.hpp @@ -18,10 +18,13 @@ */ #pragma once +#include +#include #include #include #include "gfx/gfx-source-texture.hpp" #include "obs/gs/gs-effect.hpp" +#include "obs/obs-source-factory.hpp" #include "obs/obs-source-tracker.hpp" #include "obs/obs-source.hpp" #include "plugin.hpp" @@ -41,22 +44,7 @@ namespace filter { namespace dynamic_mask { enum class channel : int8_t { Invalid = -1, Red, Green, Blue, Alpha }; - class dynamic_mask_factory { - obs_source_info _source_info; - - public: // Singleton - static void initialize(); - static void finalize(); - static std::shared_ptr get(); - - public: - dynamic_mask_factory(); - ~dynamic_mask_factory(); - }; - - class dynamic_mask_instance { - obs_source_t* _self; - + class dynamic_mask_instance : public obs::source_instance { std::map, std::string> _translation_map; std::shared_ptr _effect; @@ -89,15 +77,11 @@ namespace filter { public: dynamic_mask_instance(obs_data_t* data, obs_source_t* self); - ~dynamic_mask_instance(); + virtual ~dynamic_mask_instance(); - uint32_t get_width(); - uint32_t get_height(); - - void get_properties(obs_properties_t* properties); - void update(obs_data_t* settings); - void load(obs_data_t* settings); - void save(obs_data_t* settings); + virtual void update(obs_data_t* settings) override; + virtual void load(obs_data_t* settings) override; + virtual void save(obs_data_t* settings) override; void input_renamed(obs::source* src, std::string old_name, std::string new_name); @@ -107,5 +91,41 @@ namespace filter { void video_tick(float _time); void video_render(gs_effect_t* effect); }; + + class dynamic_mask_factory : public obs::source_factory { + static std::shared_ptr factory_instance; + + public: // Singleton + static void initialize() + { + factory_instance = std::make_shared(); + } + + static void finalize() + { + factory_instance.reset(); + } + + static std::shared_ptr get() + { + return factory_instance; + } + + private: + std::list _translation_cache; + + public: + dynamic_mask_factory(); + virtual ~dynamic_mask_factory() override; + + virtual const char* get_name() override; + + virtual void get_defaults2(obs_data_t* data) override; + + virtual obs_properties_t* get_properties2(filter::dynamic_mask::dynamic_mask_instance* data) override; + + std::string translate_string(std::string format, ...); + }; } // namespace dynamic_mask } // namespace filter