diff --git a/data/effects/blur/gaussian.effect b/data/effects/blur/gaussian.effect new file mode 100644 index 0000000..6e9c76d --- /dev/null +++ b/data/effects/blur/gaussian.effect @@ -0,0 +1,146 @@ +// Parameters: +/// OBS Default +uniform float4x4 ViewProj; +/// Texture +uniform texture2d pImage; +uniform float2 pImageTexel; +/// Blur +uniform float pSize; +uniform float pAngle; +uniform float2 pCenter; +uniform float2 pStepScale; +/// Gaussian +uniform float4 pKernel[32]; + +#define MAX_BLUR_SIZE 128 + +// Sampler +sampler_state linearSampler { + Filter = Linear; + AddressU = Clamp; + AddressV = Clamp; + MinLOD = 0; + MaxLOD = 0; +}; + +// Default Vertex Shader and Data +struct VertDataIn { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +struct VertDataOut { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertDataOut VSDefault(VertDataIn vtx) { + VertDataOut vert_out; + vert_out.pos = mul(float4(vtx.pos.xyz, 1.0), ViewProj); + vert_out.uv = vtx.uv; + return vert_out; +} + +// Functions +float GetKernelAt(int i) { + return ((float[4])(pKernel[floor(i/4)]))[i%4]; +} + +// Blur 1 Dimensional +float4 PSBlur1D(VertDataOut vtx) : TARGET { + float4 final = pImage.Sample(linearSampler, vtx.uv) + * GetKernelAt(0); + + // Loop unrolling is only possible with a fixed known maximum. + // Some compilers may unroll up to x iterations, but most will not. + for (int n = 1; n <= MAX_BLUR_SIZE; n++) { + float2 nstep = (pImageTexel * pStepScale) * n; + float kernel = GetKernelAt(n); + final += pImage.Sample(linearSampler, vtx.uv + nstep) * kernel; + final += pImage.Sample(linearSampler, vtx.uv - nstep) * kernel; + + if (n >= pSize) { + break; + } + } + + return final; +} + +technique Draw { + pass { + vertex_shader = VSDefault(vtx); + pixel_shader = PSBlur1D(vtx); + } +} + +// Blur Rotation +float2 rotate(float2 pt, float angle) { + float cp = cos(angle); + float sp = sin(angle); + float sn = -sp; + return float2((pt.x * cp) + (pt.y * sn), (pt.x * sp) + (pt.y * cp)); +} + +float2 rotateAround(float2 pt, float2 cpt, float angle) { + return rotate(pt - cpt, angle) + cpt; +} + +float4 PSRotate(VertDataOut vtx) : TARGET { + float4 final = pImage.Sample(linearSampler, vtx.uv) + * GetKernelAt(0); + + float angstep = pAngle * pStepScale.x; + + // Loop unrolling is only possible with a fixed known maximum. + // Some compilers may unroll up to x iterations, but most will not. + for (int n = 1; n <= MAX_BLUR_SIZE; n++) { + float kernel = GetKernelAt(n); + final += pImage.Sample(linearSampler, rotateAround(vtx.uv, pCenter, angstep * n)) * kernel; + final += pImage.Sample(linearSampler, rotateAround(vtx.uv, pCenter, angstep * -n)) * kernel; + + if (n >= pSize) { + break; + } + } + + return final; +} + +technique Rotate { + pass { + vertex_shader = VSDefault(vtx); + pixel_shader = PSRotate(vtx); + } +} + +// Blur Zoom +float4 PSZoom(VertDataOut vtx) : TARGET { + float4 final = pImage.Sample(linearSampler, vtx.uv) + * GetKernelAt(0); + + // step is calculated from the direction relative to the center + float2 dir = normalize(vtx.uv - pCenter) * pStepScale * pImageTexel; + float dist = distance(vtx.uv, pCenter); + + // Loop unrolling is only possible with a fixed known maximum. + // Some compilers may unroll up to x iterations, but most will not. + for (int n = 1; n <= MAX_BLUR_SIZE; n++) { + float kernel = GetKernelAt(n); + final += pImage.Sample(linearSampler, vtx.uv + (dir * n) * dist) * kernel; + final += pImage.Sample(linearSampler, vtx.uv - (dir * n) * dist) * kernel; + + if (n >= pSize) { + break; + } + } + + return final; +} + +technique Zoom { + pass { + vertex_shader = VSDefault(vtx); + pixel_shader = PSZoom(vtx); + } +} diff --git a/source/gfx/blur/gfx-blur-gaussian.cpp b/source/gfx/blur/gfx-blur-gaussian.cpp new file mode 100644 index 0000000..14a02de --- /dev/null +++ b/source/gfx/blur/gfx-blur-gaussian.cpp @@ -0,0 +1,577 @@ +// Modern effects for a modern Streamer +// Copyright (C) 2019 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 "gfx-blur-gaussian.hpp" +#include "plugin.hpp" +#include "util-math.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4201) +#endif +#include +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// FIXME: This breaks when MAX_KERNEL_SIZE is changed, due to the way the Gaussian +// function first goes up at the point, and then once we pass the critical point +// will go down again and it is not handled well. This is a pretty basic +// approximation anyway at the moment. +#define MAX_KERNEL_SIZE 128 +#define MAX_BLUR_SIZE (MAX_KERNEL_SIZE - 1) +#define SEARCH_DENSITY double_t(1. / 500.) +#define SEARCH_THRESHOLD double_t(1. / (MAX_KERNEL_SIZE * 5)) +#define SEARCH_EXTENSION 1 +#define SEARCH_RANGE MAX_KERNEL_SIZE * 2 + +gfx::blur::gaussian_data::gaussian_data() +{ + { + char* file = obs_module_file("effects/blur/gaussian.effect"); + m_effect = std::make_shared(file); + bfree(file); + } + + // Precalculate Kernels + for (size_t kernel_size = 1; kernel_size <= MAX_BLUR_SIZE; kernel_size++) { + std::vector kernel_math(MAX_KERNEL_SIZE); + std::vector kernel_data(MAX_KERNEL_SIZE); + double_t actual_width = 1.; + + // Find actual kernel width. + for (double_t h = SEARCH_DENSITY; h < SEARCH_RANGE; h += SEARCH_DENSITY) { + if (util::math::gaussian(double_t(kernel_size + SEARCH_EXTENSION), h) > SEARCH_THRESHOLD) { + actual_width = h; + break; + } + } + + // Calculate and normalize + double_t sum = 0; + for (size_t p = 0; p <= kernel_size; p++) { + kernel_math[p] = util::math::gaussian(double_t(p), actual_width); + sum += kernel_math[p] * (p > 0 ? 2 : 1); + } + + // Normalize to fill the entire 0..1 range over the width. + double_t inverse_sum = 1.0 / sum; + for (size_t p = 0; p <= kernel_size; p++) { + kernel_data.at(p) = float_t(kernel_math[p] * inverse_sum); + } + + m_kernels.push_back(std::move(kernel_data)); + } +} + +gfx::blur::gaussian_data::~gaussian_data() +{ + m_effect.reset(); +} + +std::shared_ptr<::gs::effect> gfx::blur::gaussian_data::get_effect() +{ + return m_effect; +} + +std::vector const& gfx::blur::gaussian_data::get_kernel(size_t width) +{ + if (width < 1) + width = 1; + if (width > MAX_BLUR_SIZE) + width = MAX_BLUR_SIZE; + width -= 1; + return m_kernels[width]; +} + +gfx::blur::gaussian_factory::gaussian_factory() {} + +gfx::blur::gaussian_factory::~gaussian_factory() {} + +bool gfx::blur::gaussian_factory::is_type_supported(::gfx::blur::type v) +{ + switch (v) { + case ::gfx::blur::type::Area: + return true; + case ::gfx::blur::type::Directional: + return true; + case ::gfx::blur::type::Rotational: + return true; + case ::gfx::blur::type::Zoom: + return true; + default: + return false; + } +} + +std::shared_ptr<::gfx::blur::ibase> gfx::blur::gaussian_factory::create(::gfx::blur::type v) +{ + switch (v) { + case ::gfx::blur::type::Area: + return std::make_shared<::gfx::blur::gaussian>(); + case ::gfx::blur::type::Directional: + return std::static_pointer_cast<::gfx::blur::gaussian>(std::make_shared<::gfx::blur::gaussian_directional>()); + case ::gfx::blur::type::Rotational: + return std::make_shared<::gfx::blur::gaussian_rotational>(); + case ::gfx::blur::type::Zoom: + return std::make_shared<::gfx::blur::gaussian_zoom>(); + default: + throw std::runtime_error("Invalid type."); + } +} + +double_t gfx::blur::gaussian_factory::get_min_size(::gfx::blur::type) +{ + return double_t(1.0); +} + +double_t gfx::blur::gaussian_factory::get_step_size(::gfx::blur::type) +{ + return double_t(1.0); +} + +double_t gfx::blur::gaussian_factory::get_max_size(::gfx::blur::type) +{ + return double_t(MAX_BLUR_SIZE); +} + +double_t gfx::blur::gaussian_factory::get_min_angle(::gfx::blur::type v) +{ + switch (v) { + case ::gfx::blur::type::Directional: + case ::gfx::blur::type::Rotational: + return -180.0; + default: + return 0; + } +} + +double_t gfx::blur::gaussian_factory::get_step_angle(::gfx::blur::type) +{ + return double_t(0.01); +} + +double_t gfx::blur::gaussian_factory::get_max_angle(::gfx::blur::type v) +{ + switch (v) { + case ::gfx::blur::type::Directional: + case ::gfx::blur::type::Rotational: + return 180.0; + default: + return 0; + } +} + +bool gfx::blur::gaussian_factory::is_step_scale_supported(::gfx::blur::type v) +{ + switch (v) { + case ::gfx::blur::type::Area: + case ::gfx::blur::type::Zoom: + case ::gfx::blur::type::Directional: + return true; + default: + return false; + } +} + +double_t gfx::blur::gaussian_factory::get_min_step_scale_x(::gfx::blur::type) +{ + return double_t(0.01); +} + +double_t gfx::blur::gaussian_factory::get_step_step_scale_x(::gfx::blur::type) +{ + return double_t(0.01); +} + +double_t gfx::blur::gaussian_factory::get_max_step_scale_x(::gfx::blur::type) +{ + return double_t(1000.0); +} + +double_t gfx::blur::gaussian_factory::get_min_step_scale_y(::gfx::blur::type) +{ + return double_t(0.01); +} + +double_t gfx::blur::gaussian_factory::get_step_step_scale_y(::gfx::blur::type) +{ + return double_t(0.01); +} + +double_t gfx::blur::gaussian_factory::get_max_step_scale_y(::gfx::blur::type) +{ + return double_t(1000.0); +} + +std::shared_ptr<::gfx::blur::gaussian_data> gfx::blur::gaussian_factory::data() +{ + std::unique_lock ulock(m_data_lock); + std::shared_ptr<::gfx::blur::gaussian_data> data = m_data.lock(); + if (!data) { + data = std::make_shared<::gfx::blur::gaussian_data>(); + m_data = data; + } + return data; +} + +::gfx::blur::gaussian_factory& gfx::blur::gaussian_factory::get() +{ + static ::gfx::blur::gaussian_factory instance; + return instance; +} + +gfx::blur::gaussian::gaussian() + : m_size(1.), m_step_scale({1., 1.}), m_data(::gfx::blur::gaussian_factory::get().data()) +{ + m_rendertarget = std::make_shared(GS_RGBA, GS_ZS_NONE); + m_rendertarget2 = std::make_shared(GS_RGBA, GS_ZS_NONE); +} + +gfx::blur::gaussian::~gaussian() {} + +void gfx::blur::gaussian::set_input(std::shared_ptr<::gs::texture> texture) +{ + m_input_texture = texture; +} + +::gfx::blur::type gfx::blur::gaussian::get_type() +{ + return ::gfx::blur::type::Area; +} + +double_t gfx::blur::gaussian::get_size() +{ + return m_size; +} + +void gfx::blur::gaussian::set_size(double_t width) +{ + if (width < 1.) + width = 1.; + if (width > MAX_BLUR_SIZE) + width = MAX_BLUR_SIZE; + m_size = width; +} + +void gfx::blur::gaussian::set_step_scale(double_t x, double_t y) +{ + m_step_scale.first = x; + m_step_scale.second = y; +} + +void gfx::blur::gaussian::get_step_scale(double_t& x, double_t& y) +{ + x = m_step_scale.first; + y = m_step_scale.second; +} + +double_t gfx::blur::gaussian::get_step_scale_x() +{ + return m_step_scale.first; +} + +double_t gfx::blur::gaussian::get_step_scale_y() +{ + return m_step_scale.second; +} + +std::shared_ptr<::gs::texture> gfx::blur::gaussian::render() +{ + std::shared_ptr<::gs::effect> effect = m_data->get_effect(); + auto kernel = m_data->get_kernel(size_t(m_size)); + + if (!effect || ((m_step_scale.first + m_step_scale.second) < FLT_EPSILON)) { + return m_input_texture; + } + + float_t width = float_t(m_input_texture->get_width()); + float_t height = float_t(m_input_texture->get_height()); + + // Setup + obs_enter_graphics(); + gs_set_cull_mode(GS_NEITHER); + gs_enable_color(true, true, true, true); + gs_enable_depth_test(false); + gs_depth_function(GS_ALWAYS); + gs_blend_state_push(); + gs_reset_blend_state(); + gs_enable_blending(false); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); + 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); + + effect->get_parameter("pImage").set_texture(m_input_texture); + effect->get_parameter("pStepScale").set_float2(float_t(m_step_scale.first), float_t(m_step_scale.second)); + effect->get_parameter("pSize").set_float(float_t(m_size)); + effect->get_parameter("pKernel").set_float_array(kernel.data(), MAX_KERNEL_SIZE); + + // First Pass + if (m_step_scale.first > FLT_EPSILON) { + effect->get_parameter("pImageTexel").set_float2(float_t(1. / width), 0.); + + { + auto op = m_rendertarget2->render(uint32_t(width), uint32_t(height)); + gs_ortho(0, 1., 0, 1., 0, 1.); + while (gs_effect_loop(effect->get_object(), "Draw")) { + gs_draw_sprite(0, 0, 1, 1); + } + } + + std::swap(m_rendertarget, m_rendertarget2); + effect->get_parameter("pImage").set_texture(m_rendertarget->get_texture()); + } + + // Second Pass + if (m_step_scale.second > FLT_EPSILON) { + effect->get_parameter("pImageTexel").set_float2(0., float_t(1. / height)); + + { + auto op = m_rendertarget2->render(uint32_t(width), uint32_t(height)); + gs_ortho(0, 1., 0, 1., 0, 1.); + while (gs_effect_loop(effect->get_object(), "Draw")) { + gs_draw_sprite(0, 0, 1, 1); + } + } + + std::swap(m_rendertarget, m_rendertarget2); + } + + gs_blend_state_pop(); + obs_leave_graphics(); + + return this->get(); +} + +std::shared_ptr<::gs::texture> gfx::blur::gaussian::get() +{ + return m_rendertarget->get_texture(); +} + +gfx::blur::gaussian_directional::gaussian_directional() : m_angle(0.) {} + +gfx::blur::gaussian_directional::~gaussian_directional() {} + +::gfx::blur::type gfx::blur::gaussian_directional::get_type() +{ + return ::gfx::blur::type::Directional; +} + +double_t gfx::blur::gaussian_directional::get_angle() +{ + return RAD_TO_DEG(m_angle); +} + +void gfx::blur::gaussian_directional::set_angle(double_t angle) +{ + m_angle = DEG_TO_RAD(angle); +} + +std::shared_ptr<::gs::texture> gfx::blur::gaussian_directional::render() +{ + std::shared_ptr<::gs::effect> effect = m_data->get_effect(); + auto kernel = m_data->get_kernel(size_t(m_size)); + + if (!effect || ((m_step_scale.first + m_step_scale.second) < FLT_EPSILON)) { + return m_input_texture; + } + + float_t width = float_t(m_input_texture->get_width()); + float_t height = float_t(m_input_texture->get_height()); + + // Setup + obs_enter_graphics(); + gs_set_cull_mode(GS_NEITHER); + gs_enable_color(true, true, true, true); + gs_enable_depth_test(false); + gs_depth_function(GS_ALWAYS); + gs_blend_state_push(); + gs_reset_blend_state(); + gs_enable_blending(false); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); + 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); + + effect->get_parameter("pImage").set_texture(m_input_texture); + effect->get_parameter("pImageTexel") + .set_float2(float_t(1. / width * cos(m_angle)), float_t(1. / height * sin(m_angle))); + effect->get_parameter("pStepScale").set_float2(float_t(m_step_scale.first), float_t(m_step_scale.second)); + effect->get_parameter("pSize").set_float(float_t(m_size)); + effect->get_parameter("pKernel").set_float_array(kernel.data(), MAX_KERNEL_SIZE); + + // First Pass + { + auto op = m_rendertarget->render(uint32_t(width), uint32_t(height)); + gs_ortho(0, 1., 0, 1., 0, 1.); + while (gs_effect_loop(effect->get_object(), "Draw")) { + gs_draw_sprite(0, 0, 1, 1); + } + } + + gs_blend_state_pop(); + obs_leave_graphics(); + + return this->get(); +} + +::gfx::blur::type gfx::blur::gaussian_rotational::get_type() +{ + return ::gfx::blur::type::Rotational; +} + +std::shared_ptr<::gs::texture> gfx::blur::gaussian_rotational::render() +{ + std::shared_ptr<::gs::effect> effect = m_data->get_effect(); + auto kernel = m_data->get_kernel(size_t(m_size)); + + if (!effect || ((m_step_scale.first + m_step_scale.second) < FLT_EPSILON)) { + return m_input_texture; + } + + float_t width = float_t(m_input_texture->get_width()); + float_t height = float_t(m_input_texture->get_height()); + + // Setup + obs_enter_graphics(); + gs_set_cull_mode(GS_NEITHER); + gs_enable_color(true, true, true, true); + gs_enable_depth_test(false); + gs_depth_function(GS_ALWAYS); + gs_blend_state_push(); + gs_reset_blend_state(); + gs_enable_blending(false); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); + 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); + + effect->get_parameter("pImage").set_texture(m_input_texture); + effect->get_parameter("pImageTexel").set_float2(float_t(1. / width), float_t(1. / height)); + effect->get_parameter("pStepScale").set_float2(float_t(m_step_scale.first), float_t(m_step_scale.second)); + effect->get_parameter("pSize").set_float(float_t(m_size)); + effect->get_parameter("pAngle").set_float(float_t(m_angle / m_size)); + effect->get_parameter("pCenter").set_float2(float_t(m_center.first), float_t(m_center.second)); + effect->get_parameter("pKernel").set_float_array(kernel.data(), MAX_KERNEL_SIZE); + + // First Pass + { + auto op = m_rendertarget->render(uint32_t(width), uint32_t(height)); + gs_ortho(0, 1., 0, 1., 0, 1.); + while (gs_effect_loop(effect->get_object(), "Rotate")) { + gs_draw_sprite(0, 0, 1, 1); + } + } + + gs_blend_state_pop(); + obs_leave_graphics(); + + return this->get(); +} + +void gfx::blur::gaussian_rotational::set_center(double_t x, double_t y) +{ + m_center.first = x; + m_center.second = y; +} + +void gfx::blur::gaussian_rotational::get_center(double_t& x, double_t& y) +{ + x = m_center.first; + y = m_center.second; +} + +double_t gfx::blur::gaussian_rotational::get_angle() +{ + return double_t(RAD_TO_DEG(m_angle)); +} + +void gfx::blur::gaussian_rotational::set_angle(double_t angle) +{ + m_angle = DEG_TO_RAD(angle); +} + +::gfx::blur::type gfx::blur::gaussian_zoom::get_type() +{ + return ::gfx::blur::type::Zoom; +} + +std::shared_ptr<::gs::texture> gfx::blur::gaussian_zoom::render() +{ + std::shared_ptr<::gs::effect> effect = m_data->get_effect(); + auto kernel = m_data->get_kernel(size_t(m_size)); + + if (!effect || ((m_step_scale.first + m_step_scale.second) < FLT_EPSILON)) { + return m_input_texture; + } + + float_t width = float_t(m_input_texture->get_width()); + float_t height = float_t(m_input_texture->get_height()); + + // Setup + obs_enter_graphics(); + gs_set_cull_mode(GS_NEITHER); + gs_enable_color(true, true, true, true); + gs_enable_depth_test(false); + gs_depth_function(GS_ALWAYS); + gs_blend_state_push(); + gs_reset_blend_state(); + gs_enable_blending(false); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); + 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); + + effect->get_parameter("pImage").set_texture(m_input_texture); + effect->get_parameter("pImageTexel").set_float2(float_t(1. / width), float_t(1. / height)); + effect->get_parameter("pStepScale").set_float2(float_t(m_step_scale.first), float_t(m_step_scale.second)); + effect->get_parameter("pSize").set_float(float_t(m_size)); + effect->get_parameter("pCenter").set_float2(float_t(m_center.first), float_t(m_center.second)); + effect->get_parameter("pKernel").set_float_array(kernel.data(), MAX_KERNEL_SIZE); + + // First Pass + { + auto op = m_rendertarget->render(uint32_t(width), uint32_t(height)); + gs_ortho(0, 1., 0, 1., 0, 1.); + while (gs_effect_loop(effect->get_object(), "Zoom")) { + gs_draw_sprite(0, 0, 1, 1); + } + } + + gs_blend_state_pop(); + obs_leave_graphics(); + + return this->get(); +} + +void gfx::blur::gaussian_zoom::set_center(double_t x, double_t y) +{ + m_center.first = x; + m_center.second = y; +} + +void gfx::blur::gaussian_zoom::get_center(double_t& x, double_t& y) +{ + x = m_center.first; + y = m_center.second; +} diff --git a/source/gfx/blur/gfx-blur-gaussian.hpp b/source/gfx/blur/gfx-blur-gaussian.hpp new file mode 100644 index 0000000..01e33ba --- /dev/null +++ b/source/gfx/blur/gfx-blur-gaussian.hpp @@ -0,0 +1,167 @@ +// Modern effects for a modern Streamer +// Copyright (C) 2019 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 + +#pragma once +#include +#include +#include "gfx-blur-base.hpp" +#include "obs/gs/gs-effect.hpp" +#include "obs/gs/gs-rendertarget.hpp" +#include "obs/gs/gs-texture.hpp" + +namespace gfx { + namespace blur { + class gaussian_data { + std::shared_ptr<::gs::effect> m_effect; + std::vector> m_kernels; + + public: + gaussian_data(); + ~gaussian_data(); + + std::shared_ptr<::gs::effect> get_effect(); + + std::vector const& get_kernel(size_t width); + }; + + class gaussian_factory : public ::gfx::blur::ifactory { + std::mutex m_data_lock; + std::weak_ptr<::gfx::blur::gaussian_data> m_data; + + public: + gaussian_factory(); + virtual ~gaussian_factory(); + + virtual bool is_type_supported(::gfx::blur::type type) override; + + virtual std::shared_ptr<::gfx::blur::ibase> create(::gfx::blur::type type) override; + + virtual double_t get_min_size(::gfx::blur::type type) override; + + virtual double_t get_step_size(::gfx::blur::type type) override; + + virtual double_t get_max_size(::gfx::blur::type type) override; + + virtual double_t get_min_angle(::gfx::blur::type type) override; + + virtual double_t get_step_angle(::gfx::blur::type type) override; + + virtual double_t get_max_angle(::gfx::blur::type type) override; + + virtual bool is_step_scale_supported(::gfx::blur::type type) override; + + virtual double_t get_min_step_scale_x(::gfx::blur::type type) override; + + virtual double_t get_step_step_scale_x(::gfx::blur::type type) override; + + virtual double_t get_max_step_scale_x(::gfx::blur::type type) override; + + virtual double_t get_min_step_scale_y(::gfx::blur::type type) override; + + virtual double_t get_step_step_scale_y(::gfx::blur::type type) override; + + virtual double_t get_max_step_scale_y(::gfx::blur::type type) override; + + std::shared_ptr<::gfx::blur::gaussian_data> data(); + + public: // Singleton + static ::gfx::blur::gaussian_factory& get(); + }; + + class gaussian : public ::gfx::blur::ibase { + protected: + std::shared_ptr<::gfx::blur::gaussian_data> m_data; + + double_t m_size; + std::pair m_step_scale; + std::shared_ptr<::gs::texture> m_input_texture; + std::shared_ptr<::gs::rendertarget> m_rendertarget; + + private: + std::shared_ptr<::gs::rendertarget> m_rendertarget2; + + public: + gaussian(); + virtual ~gaussian(); + + virtual void set_input(std::shared_ptr<::gs::texture> texture) override; + + virtual ::gfx::blur::type get_type() override; + + virtual double_t get_size() override; + + virtual void set_size(double_t width) override; + + virtual void set_step_scale(double_t x, double_t y) override; + + virtual void get_step_scale(double_t& x, double_t& y) override; + + virtual double_t get_step_scale_x() override; + + virtual double_t get_step_scale_y() override; + + virtual std::shared_ptr<::gs::texture> render() override; + + virtual std::shared_ptr<::gs::texture> get() override; + }; + + class gaussian_directional : public ::gfx::blur::gaussian, public ::gfx::blur::ibase_angle { + double_t m_angle; + + public: + gaussian_directional(); + virtual ~gaussian_directional(); + + virtual ::gfx::blur::type get_type() override; + + virtual double_t get_angle() override; + virtual void set_angle(double_t angle) override; + + virtual std::shared_ptr<::gs::texture> render() override; + }; + + class gaussian_rotational : public ::gfx::blur::gaussian, + public ::gfx::blur::ibase_angle, + public ::gfx::blur::ibase_center { + std::pair m_center; + double_t m_angle; + + public: + virtual ::gfx::blur::type get_type() override; + + virtual void set_center(double_t x, double_t y) override; + virtual void get_center(double_t& x, double_t& y) override; + + virtual double_t get_angle() override; + virtual void set_angle(double_t angle) override; + + virtual std::shared_ptr<::gs::texture> render() override; + }; + + class gaussian_zoom : public ::gfx::blur::gaussian, public ::gfx::blur::ibase_center { + std::pair m_center; + + public: + virtual ::gfx::blur::type get_type() override; + + virtual void set_center(double_t x, double_t y) override; + virtual void get_center(double_t& x, double_t& y) override; + + virtual std::shared_ptr<::gs::texture> render() override; + }; + } // namespace blur +} // namespace gfx