1 /*
2  * Modern effects for a modern Streamer
3  * Copyright (C) 2017-2018 Michael Fabian Dirks
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 
20 #include "filter-blur.hpp"
21 #include "strings.hpp"
22 #include <cfloat>
23 #include <cinttypes>
24 #include <cmath>
25 #include <map>
26 #include <stdexcept>
27 #include "gfx/blur/gfx-blur-box-linear.hpp"
28 #include "gfx/blur/gfx-blur-box.hpp"
29 #include "gfx/blur/gfx-blur-dual-filtering.hpp"
30 #include "gfx/blur/gfx-blur-gaussian-linear.hpp"
31 #include "gfx/blur/gfx-blur-gaussian.hpp"
32 #include "obs/gs/gs-helper.hpp"
33 #include "obs/obs-source-tracker.hpp"
34 
35 // OBS
36 #ifdef _MSC_VER
37 #pragma warning(push)
38 #pragma warning(disable : 4201)
39 #endif
40 #include <callback/signal.h>
41 #include <graphics/graphics.h>
42 #include <graphics/matrix4.h>
43 #include <util/platform.h>
44 #ifdef _MSC_VER
45 #pragma warning(pop)
46 #endif
47 
48 // Translation Strings
49 #define ST "Filter.Blur"
50 
51 #define ST_TYPE "Filter.Blur.Type"
52 #define ST_SUBTYPE "Filter.Blur.SubType"
53 #define ST_SIZE "Filter.Blur.Size"
54 #define ST_ANGLE "Filter.Blur.Angle"
55 #define ST_CENTER "Filter.Blur.Center"
56 #define ST_CENTER_X "Filter.Blur.Center.X"
57 #define ST_CENTER_Y "Filter.Blur.Center.Y"
58 #define ST_STEPSCALE "Filter.Blur.StepScale"
59 #define ST_STEPSCALE_X "Filter.Blur.StepScale.X"
60 #define ST_STEPSCALE_Y "Filter.Blur.StepScale.Y"
61 #define ST_MASK "Filter.Blur.Mask"
62 #define ST_MASK_TYPE "Filter.Blur.Mask.Type"
63 #define ST_MASK_TYPE_REGION "Filter.Blur.Mask.Type.Region"
64 #define ST_MASK_TYPE_IMAGE "Filter.Blur.Mask.Type.Image"
65 #define ST_MASK_TYPE_SOURCE "Filter.Blur.Mask.Type.Source"
66 #define ST_MASK_REGION_LEFT "Filter.Blur.Mask.Region.Left"
67 #define ST_MASK_REGION_RIGHT "Filter.Blur.Mask.Region.Right"
68 #define ST_MASK_REGION_TOP "Filter.Blur.Mask.Region.Top"
69 #define ST_MASK_REGION_BOTTOM "Filter.Blur.Mask.Region.Bottom"
70 #define ST_MASK_REGION_FEATHER "Filter.Blur.Mask.Region.Feather"
71 #define ST_MASK_REGION_FEATHER_SHIFT "Filter.Blur.Mask.Region.Feather.Shift"
72 #define ST_MASK_REGION_INVERT "Filter.Blur.Mask.Region.Invert"
73 #define ST_MASK_IMAGE "Filter.Blur.Mask.Image"
74 #define ST_MASK_SOURCE "Filter.Blur.Mask.Source"
75 #define ST_MASK_COLOR "Filter.Blur.Mask.Color"
76 #define ST_MASK_ALPHA "Filter.Blur.Mask.Alpha"
77 #define ST_MASK_MULTIPLIER "Filter.Blur.Mask.Multiplier"
78 
79 using namespace streamfx::filter::blur;
80 
81 struct local_blur_type_t {
82 	std::function<::gfx::blur::ifactory&()> fn;
83 	const char*                             name;
84 };
85 struct local_blur_subtype_t {
86 	::gfx::blur::type type;
87 	const char*       name;
88 };
89 
90 static std::map<std::string, local_blur_type_t> list_of_types = {
91 	{"box", {&::gfx::blur::box_factory::get, S_BLUR_TYPE_BOX}},
92 	{"box_linear", {&::gfx::blur::box_linear_factory::get, S_BLUR_TYPE_BOX_LINEAR}},
93 	{"gaussian", {&::gfx::blur::gaussian_factory::get, S_BLUR_TYPE_GAUSSIAN}},
94 	{"gaussian_linear", {&::gfx::blur::gaussian_linear_factory::get, S_BLUR_TYPE_GAUSSIAN_LINEAR}},
95 	{"dual_filtering", {&::gfx::blur::dual_filtering_factory::get, S_BLUR_TYPE_DUALFILTERING}},
96 };
97 static std::map<std::string, local_blur_subtype_t> list_of_subtypes = {
98 	{"area", {::gfx::blur::type::Area, S_BLUR_SUBTYPE_AREA}},
99 	{"directional", {::gfx::blur::type::Directional, S_BLUR_SUBTYPE_DIRECTIONAL}},
100 	{"rotational", {::gfx::blur::type::Rotational, S_BLUR_SUBTYPE_ROTATIONAL}},
101 	{"zoom", {::gfx::blur::type::Zoom, S_BLUR_SUBTYPE_ZOOM}},
102 };
103 
blur_instance(obs_data_t * settings,obs_source_t * self)104 blur_instance::blur_instance(obs_data_t* settings, obs_source_t* self)
105 	: obs::source_instance(settings, self), _source_rendered(false), _output_rendered(false)
106 {
107 	{
108 		auto gctx = gs::context();
109 
110 		// Create RenderTargets
111 		this->_source_rt = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
112 		this->_output_rt = std::make_shared<gs::rendertarget>(GS_RGBA, GS_ZS_NONE);
113 
114 		// Load Effects
115 		{
116 			auto file = streamfx::data_file_path("effects/mask.effect").string();
117 			try {
118 				_effect_mask = gs::effect::create(file);
119 			} catch (std::runtime_error& ex) {
120 				DLOG_ERROR("<filter-blur> Loading effect '%s' failed with error(s): %s", file.c_str(), ex.what());
121 			}
122 		}
123 	}
124 
125 	update(settings);
126 }
127 
~blur_instance()128 blur_instance::~blur_instance() {}
129 
apply_mask_parameters(gs::effect effect,gs_texture_t * original_texture,gs_texture_t * blurred_texture)130 bool blur_instance::apply_mask_parameters(gs::effect effect, gs_texture_t* original_texture,
131 										  gs_texture_t* blurred_texture)
132 {
133 	if (effect.has_parameter("image_orig")) {
134 		effect.get_parameter("image_orig").set_texture(original_texture);
135 	}
136 	if (effect.has_parameter("image_blur")) {
137 		effect.get_parameter("image_blur").set_texture(blurred_texture);
138 	}
139 
140 	// Region
141 	if (_mask.type == mask_type::Region) {
142 		if (effect.has_parameter("mask_region_left")) {
143 			effect.get_parameter("mask_region_left").set_float(_mask.region.left);
144 		}
145 		if (effect.has_parameter("mask_region_right")) {
146 			effect.get_parameter("mask_region_right").set_float(_mask.region.right);
147 		}
148 		if (effect.has_parameter("mask_region_top")) {
149 			effect.get_parameter("mask_region_top").set_float(_mask.region.top);
150 		}
151 		if (effect.has_parameter("mask_region_bottom")) {
152 			effect.get_parameter("mask_region_bottom").set_float(_mask.region.bottom);
153 		}
154 		if (effect.has_parameter("mask_region_feather")) {
155 			effect.get_parameter("mask_region_feather").set_float(_mask.region.feather);
156 		}
157 		if (effect.has_parameter("mask_region_feather_shift")) {
158 			effect.get_parameter("mask_region_feather_shift").set_float(_mask.region.feather_shift);
159 		}
160 	}
161 
162 	// Image
163 	if (_mask.type == mask_type::Image) {
164 		if (effect.has_parameter("mask_image")) {
165 			if (_mask.image.texture) {
166 				effect.get_parameter("mask_image").set_texture(_mask.image.texture);
167 			} else {
168 				effect.get_parameter("mask_image").set_texture(nullptr);
169 			}
170 		}
171 	}
172 
173 	// Source
174 	if (_mask.type == mask_type::Source) {
175 		if (effect.has_parameter("mask_image")) {
176 			if (_mask.source.texture) {
177 				effect.get_parameter("mask_image").set_texture(_mask.source.texture);
178 			} else {
179 				effect.get_parameter("mask_image").set_texture(nullptr);
180 			}
181 		}
182 	}
183 
184 	// Shared
185 	if (effect.has_parameter("mask_color")) {
186 		effect.get_parameter("mask_color").set_float4(_mask.color.r, _mask.color.g, _mask.color.b, _mask.color.a);
187 	}
188 	if (effect.has_parameter("mask_multiplier")) {
189 		effect.get_parameter("mask_multiplier").set_float(_mask.multiplier);
190 	}
191 
192 	return true;
193 }
194 
load(obs_data_t * settings)195 void blur_instance::load(obs_data_t* settings)
196 {
197 	update(settings);
198 }
199 
migrate(obs_data_t * settings,uint64_t version)200 void blur_instance::migrate(obs_data_t* settings, uint64_t version)
201 {
202 	// Now we use a fall-through switch to gradually upgrade each known version change.
203 	switch (version) {
204 	case 0:
205 		/// Blur Type
206 		int64_t old_blur = obs_data_get_int(settings, "Filter.Blur.Type");
207 		if (old_blur == 0) { // Box
208 			obs_data_set_string(settings, ST_TYPE, "box");
209 		} else if (old_blur == 1) { // Gaussian
210 			obs_data_set_string(settings, ST_TYPE, "gaussian");
211 		} else if (old_blur == 2) { // Bilateral, no longer included.
212 			obs_data_set_string(settings, ST_TYPE, "box");
213 		} else if (old_blur == 3) { // Box Linear
214 			obs_data_set_string(settings, ST_TYPE, "box_linear");
215 		} else if (old_blur == 4) { // Gaussian Linear
216 			obs_data_set_string(settings, ST_TYPE, "gaussian_linear");
217 		} else {
218 			obs_data_set_string(settings, ST_TYPE, "box");
219 		}
220 		obs_data_unset_user_value(settings, "Filter.Blur.Type");
221 
222 		/// Directional Blur
223 		bool directional = obs_data_get_bool(settings, "Filter.Blur.Directional");
224 		if (directional) {
225 			obs_data_set_string(settings, ST_SUBTYPE, "directional");
226 		} else {
227 			obs_data_set_string(settings, ST_SUBTYPE, "area");
228 		}
229 		obs_data_unset_user_value(settings, "Filter.Blur.Directional");
230 
231 		/// Directional Blur Angle
232 		double_t angle = obs_data_get_double(settings, "Filter.Blur.Directional.Angle");
233 		obs_data_set_double(settings, ST_ANGLE, angle);
234 		obs_data_unset_user_value(settings, "Filter.Blur.Directional.Angle");
235 	}
236 }
237 
update(obs_data_t * settings)238 void blur_instance::update(obs_data_t* settings)
239 {
240 	{ // Blur Type
241 		const char* blur_type      = obs_data_get_string(settings, ST_TYPE);
242 		const char* blur_subtype   = obs_data_get_string(settings, ST_SUBTYPE);
243 		const char* last_blur_type = obs_data_get_string(settings, ST_TYPE ".last");
244 
245 		auto type_found = list_of_types.find(blur_type);
246 		if (type_found != list_of_types.end()) {
247 			auto subtype_found = list_of_subtypes.find(blur_subtype);
248 			if (subtype_found != list_of_subtypes.end()) {
249 				if ((strcmp(last_blur_type, blur_type) != 0) || (_blur->get_type() != subtype_found->second.type)) {
250 					if (type_found->second.fn().is_type_supported(subtype_found->second.type)) {
251 						_blur = type_found->second.fn().create(subtype_found->second.type);
252 					}
253 				}
254 			}
255 		}
256 	}
257 
258 	{ // Blur Parameters
259 		this->_blur_size          = obs_data_get_double(settings, ST_SIZE);
260 		this->_blur_angle         = obs_data_get_double(settings, ST_ANGLE);
261 		this->_blur_center.first  = obs_data_get_double(settings, ST_CENTER_X) / 100.0;
262 		this->_blur_center.second = obs_data_get_double(settings, ST_CENTER_Y) / 100.0;
263 
264 		// Scaling
265 		this->_blur_step_scaling      = obs_data_get_bool(settings, ST_STEPSCALE);
266 		this->_blur_step_scale.first  = obs_data_get_double(settings, ST_STEPSCALE_X) / 100.0;
267 		this->_blur_step_scale.second = obs_data_get_double(settings, ST_STEPSCALE_Y) / 100.0;
268 	}
269 
270 	{ // Masking
271 		_mask.enabled = obs_data_get_bool(settings, ST_MASK);
272 		if (_mask.enabled) {
273 			_mask.type = static_cast<mask_type>(obs_data_get_int(settings, ST_MASK_TYPE));
274 			switch (_mask.type) {
275 			case mask_type::Region:
276 				_mask.region.left    = float_t(obs_data_get_double(settings, ST_MASK_REGION_LEFT) / 100.0);
277 				_mask.region.top     = float_t(obs_data_get_double(settings, ST_MASK_REGION_TOP) / 100.0);
278 				_mask.region.right   = 1.0f - float_t(obs_data_get_double(settings, ST_MASK_REGION_RIGHT) / 100.0);
279 				_mask.region.bottom  = 1.0f - float_t(obs_data_get_double(settings, ST_MASK_REGION_BOTTOM) / 100.0);
280 				_mask.region.feather = float_t(obs_data_get_double(settings, ST_MASK_REGION_FEATHER) / 100.0);
281 				_mask.region.feather_shift =
282 					float_t(obs_data_get_double(settings, ST_MASK_REGION_FEATHER_SHIFT) / 100.0);
283 				_mask.region.invert = obs_data_get_bool(settings, ST_MASK_REGION_INVERT);
284 				break;
285 			case mask_type::Image:
286 				_mask.image.path = obs_data_get_string(settings, ST_MASK_IMAGE);
287 				break;
288 			case mask_type::Source:
289 				_mask.source.name = obs_data_get_string(settings, ST_MASK_SOURCE);
290 				break;
291 			}
292 			if ((_mask.type == mask_type::Image) || (_mask.type == mask_type::Source)) {
293 				uint32_t color   = static_cast<uint32_t>(obs_data_get_int(settings, ST_MASK_COLOR));
294 				_mask.color.r    = ((color >> 0) & 0xFF) / 255.0f;
295 				_mask.color.g    = ((color >> 8) & 0xFF) / 255.0f;
296 				_mask.color.b    = ((color >> 16) & 0xFF) / 255.0f;
297 				_mask.color.a    = static_cast<float_t>(obs_data_get_double(settings, ST_MASK_ALPHA));
298 				_mask.multiplier = float_t(obs_data_get_double(settings, ST_MASK_MULTIPLIER));
299 			}
300 		}
301 	}
302 }
303 
video_tick(float)304 void blur_instance::video_tick(float)
305 {
306 	// Blur
307 	if (_blur) {
308 		_blur->set_size(_blur_size);
309 		if (_blur_step_scaling) {
310 			_blur->set_step_scale(_blur_step_scale.first, _blur_step_scale.second);
311 		} else {
312 			_blur->set_step_scale(1.0, 1.0);
313 		}
314 		if ((_blur->get_type() == ::gfx::blur::type::Directional)
315 			|| (_blur->get_type() == ::gfx::blur::type::Rotational)) {
316 			auto obj = std::dynamic_pointer_cast<::gfx::blur::base_angle>(_blur);
317 			obj->set_angle(_blur_angle);
318 		}
319 		if ((_blur->get_type() == ::gfx::blur::type::Zoom) || (_blur->get_type() == ::gfx::blur::type::Rotational)) {
320 			auto obj = std::dynamic_pointer_cast<::gfx::blur::base_center>(_blur);
321 			obj->set_center(_blur_center.first, _blur_center.second);
322 		}
323 	}
324 
325 	// Load Mask
326 	if (_mask.type == mask_type::Image) {
327 		if (_mask.image.path_old != _mask.image.path) {
328 			try {
329 				_mask.image.texture  = std::make_shared<gs::texture>(_mask.image.path);
330 				_mask.image.path_old = _mask.image.path;
331 			} catch (...) {
332 				DLOG_ERROR("<filter-blur> Instance '%s' failed to load image '%s'.", obs_source_get_name(_self),
333 						   _mask.image.path.c_str());
334 			}
335 		}
336 	} else if (_mask.type == mask_type::Source) {
337 		if (_mask.source.name_old != _mask.source.name) {
338 			try {
339 				_mask.source.source_texture = std::make_shared<gfx::source_texture>(_mask.source.name, _self);
340 				_mask.source.is_scene = (obs_scene_from_source(_mask.source.source_texture->get_object()) != nullptr);
341 				_mask.source.name_old = _mask.source.name;
342 			} catch (...) {
343 				DLOG_ERROR("<filter-blur> Instance '%s' failed to grab source '%s'.", obs_source_get_name(_self),
344 						   _mask.source.name.c_str());
345 			}
346 		}
347 	}
348 
349 	_source_rendered = false;
350 	_output_rendered = false;
351 }
352 
video_render(gs_effect_t * effect)353 void blur_instance::video_render(gs_effect_t* effect)
354 {
355 	obs_source_t* parent        = obs_filter_get_parent(this->_self);
356 	obs_source_t* target        = obs_filter_get_target(this->_self);
357 	gs_effect_t*  defaultEffect = obs_get_base_effect(obs_base_effect::OBS_EFFECT_DEFAULT);
358 	uint32_t      baseW         = obs_source_get_base_width(target);
359 	uint32_t      baseH         = obs_source_get_base_height(target);
360 
361 	// Verify that we can actually run first.
362 	if (!target || !parent || !this->_self || !this->_blur || (baseW == 0) || (baseH == 0)) {
363 		obs_source_skip_video_filter(this->_self);
364 		return;
365 	}
366 
367 #ifdef ENABLE_PROFILING
368 	gs::debug_marker gdmp{gs::debug_color_source, "Blur '%s'", obs_source_get_name(_self)};
369 #endif
370 
371 	if (!_source_rendered) {
372 		// Source To Texture
373 		{
374 #ifdef ENABLE_PROFILING
375 			gs::debug_marker gdm{gs::debug_color_cache, "Cache"};
376 #endif
377 
378 			if (obs_source_process_filter_begin(this->_self, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING)) {
379 				{
380 					auto op = this->_source_rt->render(baseW, baseH);
381 
382 					gs_blend_state_push();
383 					gs_reset_blend_state();
384 					gs_enable_blending(false);
385 					gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
386 
387 					gs_set_cull_mode(GS_NEITHER);
388 					gs_enable_color(true, true, true, true);
389 
390 					gs_enable_depth_test(false);
391 					gs_depth_function(GS_ALWAYS);
392 
393 					gs_enable_stencil_test(false);
394 					gs_enable_stencil_write(false);
395 					gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS);
396 					gs_stencil_op(GS_STENCIL_BOTH, GS_KEEP, GS_KEEP, GS_KEEP);
397 
398 					// Orthographic Camera and clear RenderTarget.
399 					gs_ortho(0, static_cast<float>(baseW), 0, static_cast<float>(baseH), -1., 1.);
400 					//gs_clear(GS_CLEAR_COLOR | GS_CLEAR_DEPTH, &black, 0, 0);
401 
402 					// Render
403 					obs_source_process_filter_end(this->_self, defaultEffect, baseW, baseH);
404 
405 					gs_blend_state_pop();
406 				}
407 
408 				_source_texture = this->_source_rt->get_texture();
409 				if (!_source_texture) {
410 					obs_source_skip_video_filter(this->_self);
411 					return;
412 				}
413 			} else {
414 				obs_source_skip_video_filter(this->_self);
415 				return;
416 			}
417 		}
418 
419 		_source_rendered = true;
420 	}
421 
422 	if (!_output_rendered) {
423 		{
424 #ifdef ENABLE_PROFILING
425 			gs::debug_marker gdm{gs::debug_color_convert, "Blur"};
426 #endif
427 
428 			_blur->set_input(_source_texture);
429 			_output_texture = _blur->render();
430 		}
431 
432 		// Mask
433 		if (_mask.enabled) {
434 #ifdef ENABLE_PROFILING
435 			gs::debug_marker gdm{gs::debug_color_convert, "Mask"};
436 #endif
437 
438 			gs_blend_state_push();
439 			gs_reset_blend_state();
440 			gs_enable_color(true, true, true, true);
441 			gs_enable_blending(false);
442 			gs_enable_depth_test(false);
443 			gs_enable_stencil_test(false);
444 			gs_enable_stencil_write(false);
445 			gs_set_cull_mode(GS_NEITHER);
446 			gs_depth_function(GS_ALWAYS);
447 			gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
448 			gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS);
449 			gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO);
450 
451 			std::string technique = "";
452 			switch (this->_mask.type) {
453 			case mask_type::Region:
454 				if (this->_mask.region.feather > std::numeric_limits<float_t>::epsilon()) {
455 					if (this->_mask.region.invert) {
456 						technique = "RegionFeatherInverted";
457 					} else {
458 						technique = "RegionFeather";
459 					}
460 				} else {
461 					if (this->_mask.region.invert) {
462 						technique = "RegionInverted";
463 					} else {
464 						technique = "Region";
465 					}
466 				}
467 				break;
468 			case mask_type::Image:
469 			case mask_type::Source:
470 				technique = "Image";
471 				break;
472 			}
473 
474 			if (_mask.source.source_texture) {
475 				uint32_t source_width  = obs_source_get_width(this->_mask.source.source_texture->get_object());
476 				uint32_t source_height = obs_source_get_height(this->_mask.source.source_texture->get_object());
477 
478 				if (source_width == 0) {
479 					source_width = baseW;
480 				}
481 				if (source_height == 0) {
482 					source_height = baseH;
483 				}
484 				if (this->_mask.source.is_scene) {
485 					obs_video_info ovi;
486 					if (obs_get_video_info(&ovi)) {
487 						source_width  = ovi.base_width;
488 						source_height = ovi.base_height;
489 					}
490 				}
491 
492 #ifdef ENABLE_PROFILING
493 				gs::debug_marker gdm{gs::debug_color_capture, "Capture '%s'",
494 									 obs_source_get_name(_mask.source.source_texture->get_object())};
495 #endif
496 
497 				this->_mask.source.texture = this->_mask.source.source_texture->render(source_width, source_height);
498 			}
499 
500 			apply_mask_parameters(_effect_mask, _source_texture->get_object(), _output_texture->get_object());
501 
502 			try {
503 				auto op = this->_output_rt->render(baseW, baseH);
504 				gs_ortho(0, 1, 0, 1, -1, 1);
505 
506 				// Render
507 				while (gs_effect_loop(_effect_mask.get_object(), technique.c_str())) {
508 					streamfx::gs_draw_fullscreen_tri();
509 				}
510 			} catch (const std::exception&) {
511 				gs_blend_state_pop();
512 				obs_source_skip_video_filter(this->_self);
513 				return;
514 			}
515 			gs_blend_state_pop();
516 
517 			if (!(_output_texture = this->_output_rt->get_texture())) {
518 				obs_source_skip_video_filter(this->_self);
519 				return;
520 			}
521 		}
522 
523 		_output_rendered = true;
524 	}
525 
526 	// Draw source
527 	{
528 #ifdef ENABLE_PROFILING
529 		gs::debug_marker gdm{gs::debug_color_render, "Render"};
530 #endif
531 
532 		// It is important that we do not modify the blend state here, as it is set correctly by OBS
533 		gs_set_cull_mode(GS_NEITHER);
534 		gs_enable_color(true, true, true, true);
535 		gs_enable_depth_test(false);
536 		gs_depth_function(GS_ALWAYS);
537 		gs_enable_stencil_test(false);
538 		gs_enable_stencil_write(false);
539 		gs_stencil_function(GS_STENCIL_BOTH, GS_ALWAYS);
540 		gs_stencil_op(GS_STENCIL_BOTH, GS_ZERO, GS_ZERO, GS_ZERO);
541 
542 		gs_effect_t* finalEffect = effect ? effect : defaultEffect;
543 		const char*  technique   = "Draw";
544 
545 		gs_eparam_t* param = gs_effect_get_param_by_name(finalEffect, "image");
546 		if (!param) {
547 			DLOG_ERROR("<filter-blur:%s> Failed to set image param.", obs_source_get_name(this->_self));
548 			obs_source_skip_video_filter(_self);
549 			return;
550 		} else {
551 			gs_effect_set_texture(param, _output_texture->get_object());
552 		}
553 		while (gs_effect_loop(finalEffect, technique)) {
554 			gs_draw_sprite(_output_texture->get_object(), 0, baseW, baseH);
555 		}
556 	}
557 }
558 
blur_factory()559 blur_factory::blur_factory()
560 {
561 	_info.id           = PREFIX "filter-blur";
562 	_info.type         = OBS_SOURCE_TYPE_FILTER;
563 	_info.output_flags = OBS_SOURCE_VIDEO;
564 
565 	set_resolution_enabled(false);
566 	finish_setup();
567 	register_proxy("obs-stream-effects-filter-blur");
568 }
569 
~blur_factory()570 blur_factory::~blur_factory() {}
571 
get_name()572 const char* blur_factory::get_name()
573 {
574 	return D_TRANSLATE(ST);
575 }
576 
get_defaults2(obs_data_t * settings)577 void blur_factory::get_defaults2(obs_data_t* settings)
578 {
579 	// Type, Subtype
580 	obs_data_set_default_string(settings, ST_TYPE, "box");
581 	obs_data_set_default_string(settings, ST_SUBTYPE, "area");
582 
583 	// Parameters
584 	obs_data_set_default_int(settings, ST_SIZE, 5);
585 	obs_data_set_default_double(settings, ST_ANGLE, 0.);
586 	obs_data_set_default_double(settings, ST_CENTER_X, 50.);
587 	obs_data_set_default_double(settings, ST_CENTER_Y, 50.);
588 	obs_data_set_default_bool(settings, ST_STEPSCALE, false);
589 	obs_data_set_default_double(settings, ST_STEPSCALE_X, 1.);
590 	obs_data_set_default_double(settings, ST_STEPSCALE_Y, 1.);
591 
592 	// Masking
593 	obs_data_set_default_bool(settings, ST_MASK, false);
594 	obs_data_set_default_int(settings, ST_MASK_TYPE, static_cast<int64_t>(mask_type::Region));
595 	obs_data_set_default_double(settings, ST_MASK_REGION_LEFT, 0.0);
596 	obs_data_set_default_double(settings, ST_MASK_REGION_RIGHT, 0.0);
597 	obs_data_set_default_double(settings, ST_MASK_REGION_TOP, 0.0);
598 	obs_data_set_default_double(settings, ST_MASK_REGION_BOTTOM, 0.0);
599 	obs_data_set_default_double(settings, ST_MASK_REGION_FEATHER, 0.0);
600 	obs_data_set_default_double(settings, ST_MASK_REGION_FEATHER_SHIFT, 0.0);
601 	obs_data_set_default_bool(settings, ST_MASK_REGION_INVERT, false);
602 	obs_data_set_default_string(settings, ST_MASK_IMAGE, streamfx::data_file_path("white.png").u8string().c_str());
603 	obs_data_set_default_string(settings, ST_MASK_SOURCE, "");
604 	obs_data_set_default_int(settings, ST_MASK_COLOR, 0xFFFFFFFFull);
605 	obs_data_set_default_double(settings, ST_MASK_MULTIPLIER, 1.0);
606 }
607 
modified_properties(void *,obs_properties_t * props,obs_property * prop,obs_data_t * settings)608 bool modified_properties(void*, obs_properties_t* props, obs_property* prop, obs_data_t* settings) noexcept
609 try {
610 	obs_property_t* p;
611 	const char*     propname = obs_property_name(prop);
612 	const char*     vtype    = obs_data_get_string(settings, ST_TYPE);
613 	const char*     vsubtype = obs_data_get_string(settings, ST_SUBTYPE);
614 
615 	// Find new Type
616 	auto type_found = list_of_types.find(vtype);
617 	if (type_found == list_of_types.end()) {
618 		return false;
619 	}
620 
621 	// Find new Subtype
622 	auto subtype_found = list_of_subtypes.find(vsubtype);
623 	if (subtype_found == list_of_subtypes.end()) {
624 		return false;
625 	}
626 
627 	// Blur Type
628 	if (strcmp(propname, ST_TYPE) == 0) {
629 		obs_property_t* prop_subtype = obs_properties_get(props, ST_SUBTYPE);
630 
631 		/// Disable unsupported items.
632 		std::size_t subvalue_idx = 0;
633 		for (std::size_t idx = 0, edx = obs_property_list_item_count(prop_subtype); idx < edx; idx++) {
634 			const char* subtype  = obs_property_list_item_string(prop_subtype, idx);
635 			bool        disabled = false;
636 
637 			auto subtype_found_idx = list_of_subtypes.find(subtype);
638 			if (subtype_found_idx != list_of_subtypes.end()) {
639 				disabled = !type_found->second.fn().is_type_supported(subtype_found_idx->second.type);
640 			} else {
641 				disabled = true;
642 			}
643 
644 			obs_property_list_item_disable(prop_subtype, idx, disabled);
645 			if (strcmp(subtype, vsubtype) == 0) {
646 				subvalue_idx = idx;
647 			}
648 		}
649 
650 		/// Ensure that there is a valid item selected.
651 		if (obs_property_list_item_disabled(prop_subtype, subvalue_idx)) {
652 			for (std::size_t idx = 0, edx = obs_property_list_item_count(prop_subtype); idx < edx; idx++) {
653 				if (!obs_property_list_item_disabled(prop_subtype, idx)) {
654 					obs_data_set_string(settings, ST_SUBTYPE, obs_property_list_item_string(prop_subtype, idx));
655 
656 					// Find new Subtype
657 					auto subtype_found2 = list_of_subtypes.find(vsubtype);
658 					if (subtype_found2 == list_of_subtypes.end()) {
659 						subtype_found = list_of_subtypes.end();
660 					} else {
661 						subtype_found = subtype_found2;
662 					}
663 
664 					break;
665 				}
666 			}
667 		}
668 	}
669 
670 	// Update hover text with new descriptions.
671 	if (type_found != list_of_types.end()) {
672 		if (type_found->first == "box") {
673 			obs_property_set_long_description(obs_properties_get(props, ST_TYPE), D_TRANSLATE(D_DESC(S_BLUR_TYPE_BOX)));
674 		} else if (type_found->first == "box_linear") {
675 			obs_property_set_long_description(obs_properties_get(props, ST_TYPE),
676 											  D_TRANSLATE(D_DESC(S_BLUR_TYPE_BOX_LINEAR)));
677 		} else if (type_found->first == "gaussian") {
678 			obs_property_set_long_description(obs_properties_get(props, ST_TYPE),
679 											  D_TRANSLATE(D_DESC(S_BLUR_TYPE_GAUSSIAN)));
680 		} else if (type_found->first == "gaussian_linear") {
681 			obs_property_set_long_description(obs_properties_get(props, ST_TYPE),
682 											  D_TRANSLATE(D_DESC(S_BLUR_TYPE_GAUSSIAN_LINEAR)));
683 		}
684 	} else {
685 		obs_property_set_long_description(obs_properties_get(props, ST_TYPE), D_TRANSLATE(D_DESC(ST_TYPE)));
686 	}
687 	if (subtype_found != list_of_subtypes.end()) {
688 		if (subtype_found->first == "area") {
689 			obs_property_set_long_description(obs_properties_get(props, ST_SUBTYPE),
690 											  D_TRANSLATE(D_DESC(S_BLUR_SUBTYPE_AREA)));
691 		} else if (subtype_found->first == "directional") {
692 			obs_property_set_long_description(obs_properties_get(props, ST_SUBTYPE),
693 											  D_TRANSLATE(D_DESC(S_BLUR_SUBTYPE_DIRECTIONAL)));
694 		} else if (subtype_found->first == "rotational") {
695 			obs_property_set_long_description(obs_properties_get(props, ST_SUBTYPE),
696 											  D_TRANSLATE(D_DESC(S_BLUR_SUBTYPE_ROTATIONAL)));
697 		} else if (subtype_found->first == "zoom") {
698 			obs_property_set_long_description(obs_properties_get(props, ST_SUBTYPE),
699 											  D_TRANSLATE(D_DESC(S_BLUR_SUBTYPE_ZOOM)));
700 		}
701 	} else {
702 		obs_property_set_long_description(obs_properties_get(props, ST_SUBTYPE), D_TRANSLATE(D_DESC(ST_SUBTYPE)));
703 	}
704 
705 	// Blur Sub-Type
706 	{
707 		bool has_angle_support = (subtype_found->second.type == ::gfx::blur::type::Directional)
708 								 || (subtype_found->second.type == ::gfx::blur::type::Rotational);
709 		bool has_center_support = (subtype_found->second.type == ::gfx::blur::type::Rotational)
710 								  || (subtype_found->second.type == ::gfx::blur::type::Zoom);
711 		bool has_stepscale_support = type_found->second.fn().is_step_scale_supported(subtype_found->second.type);
712 		bool show_scaling          = obs_data_get_bool(settings, ST_STEPSCALE) && has_stepscale_support;
713 
714 		/// Size
715 		p = obs_properties_get(props, ST_SIZE);
716 		obs_property_float_set_limits(p, type_found->second.fn().get_min_size(subtype_found->second.type),
717 									  type_found->second.fn().get_max_size(subtype_found->second.type),
718 									  type_found->second.fn().get_step_size(subtype_found->second.type));
719 
720 		/// Angle
721 		p = obs_properties_get(props, ST_ANGLE);
722 		obs_property_set_visible(p, has_angle_support);
723 		obs_property_float_set_limits(p, type_found->second.fn().get_min_angle(subtype_found->second.type),
724 									  type_found->second.fn().get_max_angle(subtype_found->second.type),
725 									  type_found->second.fn().get_step_angle(subtype_found->second.type));
726 
727 		/// Center, Radius
728 		obs_property_set_visible(obs_properties_get(props, ST_CENTER_X), has_center_support);
729 		obs_property_set_visible(obs_properties_get(props, ST_CENTER_Y), has_center_support);
730 
731 		/// Step Scaling
732 		obs_property_set_visible(obs_properties_get(props, ST_STEPSCALE), has_stepscale_support);
733 		p = obs_properties_get(props, ST_STEPSCALE_X);
734 		obs_property_set_visible(p, show_scaling);
735 		obs_property_float_set_limits(p, type_found->second.fn().get_min_step_scale_x(subtype_found->second.type),
736 									  type_found->second.fn().get_max_step_scale_x(subtype_found->second.type),
737 									  type_found->second.fn().get_step_step_scale_x(subtype_found->second.type));
738 		p = obs_properties_get(props, ST_STEPSCALE_Y);
739 		obs_property_set_visible(p, show_scaling);
740 		obs_property_float_set_limits(p, type_found->second.fn().get_min_step_scale_x(subtype_found->second.type),
741 									  type_found->second.fn().get_max_step_scale_x(subtype_found->second.type),
742 									  type_found->second.fn().get_step_step_scale_x(subtype_found->second.type));
743 	}
744 
745 	{ // Masking
746 		using namespace ::gfx::blur;
747 		bool      show_mask   = obs_data_get_bool(settings, ST_MASK);
748 		mask_type mtype       = static_cast<mask_type>(obs_data_get_int(settings, ST_MASK_TYPE));
749 		bool      show_region = (mtype == mask_type::Region) && show_mask;
750 		bool      show_image  = (mtype == mask_type::Image) && show_mask;
751 		bool      show_source = (mtype == mask_type::Source) && show_mask;
752 		obs_property_set_visible(obs_properties_get(props, ST_MASK_TYPE), show_mask);
753 		obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_LEFT), show_region);
754 		obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_TOP), show_region);
755 		obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_RIGHT), show_region);
756 		obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_BOTTOM), show_region);
757 		obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_FEATHER), show_region);
758 		obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_FEATHER_SHIFT), show_region);
759 		obs_property_set_visible(obs_properties_get(props, ST_MASK_REGION_INVERT), show_region);
760 		obs_property_set_visible(obs_properties_get(props, ST_MASK_IMAGE), show_image);
761 		obs_property_set_visible(obs_properties_get(props, ST_MASK_SOURCE), show_source);
762 		obs_property_set_visible(obs_properties_get(props, ST_MASK_COLOR), show_image || show_source);
763 		obs_property_set_visible(obs_properties_get(props, ST_MASK_ALPHA), show_image || show_source);
764 		obs_property_set_visible(obs_properties_get(props, ST_MASK_MULTIPLIER), show_image || show_source);
765 	}
766 
767 	return true;
768 } catch (...) {
769 	DLOG_ERROR("Unexpected exception in modified_properties callback.");
770 	return false;
771 }
772 
get_properties2(blur_instance * data)773 obs_properties_t* blur_factory::get_properties2(blur_instance* data)
774 {
775 	obs_properties_t* pr = obs_properties_create();
776 	obs_property_t*   p  = NULL;
777 
778 	// Blur Type and Sub-Type
779 	{
780 		p = obs_properties_add_list(pr, ST_TYPE, D_TRANSLATE(ST_TYPE), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
781 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_TYPE)));
782 		obs_property_set_modified_callback2(p, modified_properties, this);
783 		obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_TYPE_BOX), "box");
784 		obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_TYPE_BOX_LINEAR), "box_linear");
785 		obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_TYPE_GAUSSIAN), "gaussian");
786 		obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_TYPE_GAUSSIAN_LINEAR), "gaussian_linear");
787 		obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_TYPE_DUALFILTERING), "dual_filtering");
788 
789 		p = obs_properties_add_list(pr, ST_SUBTYPE, D_TRANSLATE(ST_SUBTYPE), OBS_COMBO_TYPE_LIST,
790 									OBS_COMBO_FORMAT_STRING);
791 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_SUBTYPE)));
792 		obs_property_set_modified_callback2(p, modified_properties, this);
793 		obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_SUBTYPE_AREA), "area");
794 		obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_SUBTYPE_DIRECTIONAL), "directional");
795 		obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_SUBTYPE_ROTATIONAL), "rotational");
796 		obs_property_list_add_string(p, D_TRANSLATE(S_BLUR_SUBTYPE_ZOOM), "zoom");
797 	}
798 
799 	// Blur Parameters
800 	{
801 		p = obs_properties_add_float_slider(pr, ST_SIZE, D_TRANSLATE(ST_SIZE), 1, 32767, 1);
802 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_SIZE)));
803 
804 		p = obs_properties_add_float_slider(pr, ST_ANGLE, D_TRANSLATE(ST_ANGLE), -180.0, 180.0, 0.01);
805 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_ANGLE)));
806 
807 		p = obs_properties_add_float_slider(pr, ST_CENTER_X, D_TRANSLATE(ST_CENTER_X), 0.00, 100.0, 0.01);
808 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_CENTER_X)));
809 		p = obs_properties_add_float_slider(pr, ST_CENTER_Y, D_TRANSLATE(ST_CENTER_Y), 0.00, 100.0, 0.01);
810 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_CENTER_Y)));
811 
812 		p = obs_properties_add_bool(pr, ST_STEPSCALE, D_TRANSLATE(ST_STEPSCALE));
813 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_STEPSCALE)));
814 		obs_property_set_modified_callback2(p, modified_properties, this);
815 		p = obs_properties_add_float_slider(pr, ST_STEPSCALE_X, D_TRANSLATE(ST_STEPSCALE_X), 0.0, 1000.0, 0.01);
816 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_STEPSCALE_X)));
817 		p = obs_properties_add_float_slider(pr, ST_STEPSCALE_Y, D_TRANSLATE(ST_STEPSCALE_Y), 0.0, 1000.0, 0.01);
818 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_STEPSCALE_Y)));
819 	}
820 
821 	// Masking
822 	{
823 		p = obs_properties_add_bool(pr, ST_MASK, D_TRANSLATE(ST_MASK));
824 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK)));
825 		obs_property_set_modified_callback2(p, modified_properties, this);
826 		p = obs_properties_add_list(pr, ST_MASK_TYPE, D_TRANSLATE(ST_MASK_TYPE), OBS_COMBO_TYPE_LIST,
827 									OBS_COMBO_FORMAT_INT);
828 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_TYPE)));
829 		obs_property_set_modified_callback2(p, modified_properties, this);
830 		obs_property_list_add_int(p, D_TRANSLATE(ST_MASK_TYPE_REGION), static_cast<int64_t>(mask_type::Region));
831 		obs_property_list_add_int(p, D_TRANSLATE(ST_MASK_TYPE_IMAGE), static_cast<int64_t>(mask_type::Image));
832 		obs_property_list_add_int(p, D_TRANSLATE(ST_MASK_TYPE_SOURCE), static_cast<int64_t>(mask_type::Source));
833 		/// Region
834 		p = obs_properties_add_float_slider(pr, ST_MASK_REGION_LEFT, D_TRANSLATE(ST_MASK_REGION_LEFT), 0.0, 100.0,
835 											0.01);
836 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_LEFT)));
837 		p = obs_properties_add_float_slider(pr, ST_MASK_REGION_TOP, D_TRANSLATE(ST_MASK_REGION_TOP), 0.0, 100.0, 0.01);
838 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_TOP)));
839 		p = obs_properties_add_float_slider(pr, ST_MASK_REGION_RIGHT, D_TRANSLATE(ST_MASK_REGION_RIGHT), 0.0, 100.0,
840 											0.01);
841 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_RIGHT)));
842 		p = obs_properties_add_float_slider(pr, ST_MASK_REGION_BOTTOM, D_TRANSLATE(ST_MASK_REGION_BOTTOM), 0.0, 100.0,
843 											0.01);
844 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_BOTTOM)));
845 		p = obs_properties_add_float_slider(pr, ST_MASK_REGION_FEATHER, D_TRANSLATE(ST_MASK_REGION_FEATHER), 0.0, 50.0,
846 											0.01);
847 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_FEATHER)));
848 		p = obs_properties_add_float_slider(pr, ST_MASK_REGION_FEATHER_SHIFT, D_TRANSLATE(ST_MASK_REGION_FEATHER_SHIFT),
849 											-100.0, 100.0, 0.01);
850 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_FEATHER_SHIFT)));
851 		p = obs_properties_add_bool(pr, ST_MASK_REGION_INVERT, D_TRANSLATE(ST_MASK_REGION_INVERT));
852 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_REGION_INVERT)));
853 		/// Image
854 		{
855 			std::string filter =
856 				translate_string("%s (%s);;* (*.*)", D_TRANSLATE(S_FILETYPE_IMAGES), S_FILEFILTERS_TEXTURE);
857 			_translation_cache.push_back(filter);
858 			p = obs_properties_add_path(pr, ST_MASK_IMAGE, D_TRANSLATE(ST_MASK_IMAGE), OBS_PATH_FILE,
859 										_translation_cache.back().c_str(), nullptr);
860 			obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_IMAGE)));
861 		}
862 		/// Source
863 		p = obs_properties_add_list(pr, ST_MASK_SOURCE, D_TRANSLATE(ST_MASK_SOURCE), OBS_COMBO_TYPE_LIST,
864 									OBS_COMBO_FORMAT_STRING);
865 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_SOURCE)));
866 		obs_property_list_add_string(p, "", "");
867 		obs::source_tracker::get()->enumerate(
868 			[&p](std::string name, obs_source_t*) {
869 				obs_property_list_add_string(p, std::string(name + " (Source)").c_str(), name.c_str());
870 				return false;
871 			},
872 			obs::source_tracker::filter_video_sources);
873 		obs::source_tracker::get()->enumerate(
874 			[&p](std::string name, obs_source_t*) {
875 				obs_property_list_add_string(p, std::string(name + " (Scene)").c_str(), name.c_str());
876 				return false;
877 			},
878 			obs::source_tracker::filter_scenes);
879 
880 		/// Shared
881 		p = obs_properties_add_color(pr, ST_MASK_COLOR, D_TRANSLATE(ST_MASK_COLOR));
882 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_COLOR)));
883 		p = obs_properties_add_float_slider(pr, ST_MASK_ALPHA, D_TRANSLATE(ST_MASK_ALPHA), 0.0, 100.0, 0.1);
884 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_ALPHA)));
885 		p = obs_properties_add_float_slider(pr, ST_MASK_MULTIPLIER, D_TRANSLATE(ST_MASK_MULTIPLIER), 0.0, 10.0, 0.01);
886 		obs_property_set_long_description(p, D_TRANSLATE(D_DESC(ST_MASK_MULTIPLIER)));
887 	}
888 
889 	return pr;
890 }
891 
translate_string(const char * format,...)892 std::string blur_factory::translate_string(const char* format, ...)
893 {
894 	va_list vargs;
895 	va_start(vargs, format);
896 	std::vector<char> buffer(2048);
897 	std::size_t       len = static_cast<size_t>(vsnprintf(buffer.data(), buffer.size(), format, vargs));
898 	va_end(vargs);
899 	return std::string(buffer.data(), buffer.data() + len);
900 }
901 
902 std::shared_ptr<blur_factory> _filter_blur_factory_instance = nullptr;
903 
initialize()904 void streamfx::filter::blur::blur_factory::initialize()
905 {
906 	if (!_filter_blur_factory_instance)
907 		_filter_blur_factory_instance = std::make_shared<blur_factory>();
908 }
909 
finalize()910 void streamfx::filter::blur::blur_factory::finalize()
911 {
912 	_filter_blur_factory_instance.reset();
913 }
914 
get()915 std::shared_ptr<blur_factory> streamfx::filter::blur::blur_factory::get()
916 {
917 	return _filter_blur_factory_instance;
918 }
919