filter-blur: Precalculate correct filter width for Gaussian

Gaussian blur used to have a hard edge which was caused by the filter width being wider than the actual sampling range, which is what is determined by the size option. In order to fix this, we have to figure out the proper filter width for a sampling range, so that we avoid any and all hard edges.

Additionally removes the old texture method entirely.

Fixes #44
This commit is contained in:
Michael Fabian 'Xaymar' Dirks 2019-02-01 08:35:19 +01:00
parent 10f7047042
commit 43136b00c4
2 changed files with 25 additions and 33 deletions

View file

@ -81,6 +81,9 @@ enum ColorFormat : uint64_t { // ToDo: Refactor into full class.
}; };
static uint8_t const max_kernel_size = 25; 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.;
// Initializer & Finalizer // Initializer & Finalizer
INITIALIZER(filterBlurFactoryInitializer) INITIALIZER(filterBlurFactoryInitializer)
@ -126,6 +129,17 @@ filter::blur::blur_factory::blur_factory()
source_info.load = load; source_info.load = load;
obs_register_source(&source_info); obs_register_source(&source_info);
P_LOG_INFO("<filter-blur> 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;
}
}
}
} }
filter::blur::blur_factory::~blur_factory() {} filter::blur::blur_factory::~blur_factory() {}
@ -170,7 +184,6 @@ void filter::blur::blur_factory::on_list_empty()
{ {
obs_enter_graphics(); obs_enter_graphics();
blur_effect.reset(); blur_effect.reset();
kernels.clear();
color_converter_effect.reset(); color_converter_effect.reset();
mask_effect.reset(); mask_effect.reset();
obs_leave_graphics(); obs_leave_graphics();
@ -180,8 +193,6 @@ void filter::blur::blur_factory::generate_gaussian_kernels()
{ {
// 2D texture, horizontal is value, vertical is kernel size. // 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))); size_t size_power_of_two = size_t(pow(2, util::math::get_power_of_two_exponent_ceil(max_kernel_size)));
std::vector<float_t> texture_data(size_power_of_two * size_power_of_two);
std::vector<float_t> math_data(size_power_of_two); std::vector<float_t> math_data(size_power_of_two);
std::shared_ptr<std::vector<float_t>> kernel_data; std::shared_ptr<std::vector<float_t>> kernel_data;
@ -192,31 +203,18 @@ void filter::blur::blur_factory::generate_gaussian_kernels()
// Calculate and normalize // Calculate and normalize
float_t sum = 0; float_t sum = 0;
for (size_t p = 0; p <= width; p++) { for (size_t p = 0; p <= width; p++) {
math_data[p] = float_t(Gaussian1D(double_t(p), double_t(width))); math_data[p] = float_t(Gaussian1D(double_t(p), double_t(gaussian_widths[width])));
sum += math_data[p] * (p > 0 ? 2 : 1); sum += math_data[p] * (p > 0 ? 2 : 1);
} }
// Normalize to Texture Buffer // Normalize to Texture Buffer
double_t inverse_sum = 1.0 / sum; double_t inverse_sum = 1.0 / sum;
for (size_t p = 0; p <= width; p++) { for (size_t p = 0; p <= width; p++) {
texture_data[v + p] = float_t(math_data[p] * inverse_sum); kernel_data->at(p) = math_data[p] * inverse_sum;
kernel_data->at(p) = texture_data[v + p];
} }
gaussian_kernels.insert({uint8_t(width), kernel_data}); gaussian_kernels.insert({uint8_t(width), kernel_data});
} }
// Create Texture
try {
auto texture_buffer = reinterpret_cast<uint8_t*>(texture_data.data());
auto unsafe_buffer = const_cast<const uint8_t**>(&texture_buffer);
kernels.insert_or_assign(filter::blur::type::Gaussian,
std::make_shared<gs::texture>(uint32_t(size_power_of_two), uint32_t(size_power_of_two),
GS_R32F, 1, unsafe_buffer, gs::texture::flags::None));
} catch (std::runtime_error ex) {
P_LOG_ERROR("<filter-blur> Failed to create gaussian kernel texture.");
}
} }
void filter::blur::blur_factory::generate_kernel_textures() void filter::blur::blur_factory::generate_kernel_textures()
@ -366,11 +364,6 @@ std::shared_ptr<gs::effect> filter::blur::blur_factory::get_mask_effect()
return mask_effect; return mask_effect;
} }
std::shared_ptr<gs::texture> filter::blur::blur_factory::get_kernel(filter::blur::type type)
{
return kernels.at(type);
}
std::shared_ptr<std::vector<float_t>> filter::blur::blur_factory::get_gaussian_kernel(uint8_t size) std::shared_ptr<std::vector<float_t>> filter::blur::blur_factory::get_gaussian_kernel(uint8_t size)
{ {
return gaussian_kernels.at(size); return gaussian_kernels.at(size);
@ -438,7 +431,8 @@ bool filter::blur::blur_instance::apply_bilateral_param()
return false; return false;
if (m_blur_effect->has_parameter("bilateralSmoothing")) { 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))); 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")) { if (m_blur_effect->has_parameter("bilateralSharpness")) {

View file

@ -65,9 +65,9 @@ namespace filter {
std::list<blur_instance*> sources; std::list<blur_instance*> sources;
std::shared_ptr<gs::effect> color_converter_effect; std::shared_ptr<gs::effect> color_converter_effect;
std::shared_ptr<gs::effect> mask_effect; std::shared_ptr<gs::effect> mask_effect;
std::shared_ptr<gs::effect> blur_effect; std::shared_ptr<gs::effect> blur_effect;
std::map<filter::blur::type, std::shared_ptr<gs::texture>> kernels;
std::vector<double_t> gaussian_widths;
std::map<uint8_t, std::shared_ptr<std::vector<float_t>>> gaussian_kernels; std::map<uint8_t, std::shared_ptr<std::vector<float_t>>> gaussian_kernels;
public: // Singleton public: // Singleton
@ -112,8 +112,6 @@ namespace filter {
std::shared_ptr<gs::effect> get_mask_effect(); std::shared_ptr<gs::effect> get_mask_effect();
std::shared_ptr<gs::texture> get_kernel(filter::blur::type type);
std::shared_ptr<std::vector<float_t>> get_gaussian_kernel(uint8_t size); std::shared_ptr<std::vector<float_t>> get_gaussian_kernel(uint8_t size);
}; };