Blender V4.5
node_composite_blur.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2006 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_assert.h"
10#include "BLI_math_base.hh"
11#include "BLI_math_vector.hh"
13
14#include "RNA_access.hh"
15
16#include "UI_interface.hh"
17#include "UI_resources.hh"
18
19#include "GPU_shader.hh"
20
24#include "COM_node_operation.hh"
26#include "COM_utilities.hh"
27
29
30/* **************** BLUR ******************** */
31
33
35
37{
38 b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f});
39 b.add_input<decl::Vector>("Size").dimensions(2).default_value({0.0f, 0.0f}).min(0.0f);
40 b.add_input<decl::Bool>("Extend Bounds").default_value(false).compositor_expects_single_value();
41 b.add_input<decl::Bool>("Separable")
42 .default_value(true)
43 .compositor_expects_single_value()
44 .description(
45 "Use faster approximation by blurring along the horizontal and vertical directions "
46 "independently");
47
48 b.add_output<decl::Color>("Image");
49}
50
51static void node_composit_init_blur(bNodeTree * /*ntree*/, bNode *node)
52{
54 data->filtertype = R_FILTER_GAUSS;
55 node->storage = data;
56}
57
59{
60 layout->prop(ptr, "filter_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
61}
62
63using namespace blender::compositor;
64
66 public:
68
69 void execute() override
70 {
71 const Result &input = this->get_input("Image");
72 Result &output = this->get_result("Image");
73 if (this->is_identity()) {
74 output.share_data(input);
75 return;
76 }
77
78 if (!this->get_input("Size").is_single_value()) {
79 this->execute_variable_size(input, output);
80 }
81 else if (node_storage(bnode()).filtertype == R_FILTER_FAST_GAUSS) {
83 }
84 else if (use_separable_filter()) {
86 input,
87 output,
88 this->get_blur_size(),
89 node_storage(bnode()).filtertype,
91 }
92 else {
93 this->execute_constant_size(input, output);
94 }
95 }
96
98 {
99 if (this->context().use_gpu()) {
100 this->execute_constant_size_gpu(input, output);
101 }
102 else {
103 this->execute_constant_size_cpu(input, output);
104 }
105 }
106
108 {
109 GPUShader *shader = context().get_shader("compositor_symmetric_blur");
110 GPU_shader_bind(shader);
111
112 GPU_shader_uniform_1b(shader, "extend_bounds", get_extend_bounds());
113
114 input.bind_as_texture(shader, "input_tx");
115
116 const float2 blur_radius = this->get_blur_size();
117
119 context(), node_storage(bnode()).filtertype, blur_radius);
120 weights.bind_as_texture(shader, "weights_tx");
121
122 Domain domain = compute_domain();
123 if (get_extend_bounds()) {
124 /* Add a radius amount of pixels in both sides of the image, hence the multiply by 2. */
125 domain.size += int2(math::ceil(blur_radius)) * 2;
126 }
127
128 output.allocate_texture(domain);
129 output.bind_as_image(shader, "output_img");
130
132
134 output.unbind_as_image();
135 input.unbind_as_texture();
136 weights.unbind_as_texture();
137 }
138
140 {
141 const float2 blur_radius = this->get_blur_size();
142 const Result &weights = this->context().cache_manager().symmetric_blur_weights.get(
143 this->context(), node_storage(this->bnode()).filtertype, blur_radius);
144
145 Domain domain = this->compute_domain();
146 const bool extend_bounds = this->get_extend_bounds();
147 if (extend_bounds) {
148 /* Add a radius amount of pixels in both sides of the image, hence the multiply by 2. */
149 domain.size += int2(math::ceil(blur_radius)) * 2;
150 }
151
152 output.allocate_texture(domain);
153
154 auto load_input = [&](const int2 texel) {
155 return this->load_input(input, weights, texel, extend_bounds);
156 };
157
158 parallel_for(domain.size, [&](const int2 texel) {
159 float4 accumulated_color = float4(0.0f);
160
161 /* First, compute the contribution of the center pixel. */
162 float4 center_color = load_input(texel);
163 accumulated_color += center_color * weights.load_pixel<float>(int2(0));
164
165 int2 weights_size = weights.domain().size;
166
167 /* Then, compute the contributions of the pixels along the x axis of the filter, noting that
168 * the weights texture only stores the weights for the positive half, but since the filter is
169 * symmetric, the same weight is used for the negative half and we add both of their
170 * contributions. */
171 for (int x = 1; x < weights_size.x; x++) {
172 float weight = weights.load_pixel<float>(int2(x, 0));
173 accumulated_color += load_input(texel + int2(x, 0)) * weight;
174 accumulated_color += load_input(texel + int2(-x, 0)) * weight;
175 }
176
177 /* Then, compute the contributions of the pixels along the y axis of the filter, noting that
178 * the weights texture only stores the weights for the positive half, but since the filter is
179 * symmetric, the same weight is used for the negative half and we add both of their
180 * contributions. */
181 for (int y = 1; y < weights_size.y; y++) {
182 float weight = weights.load_pixel<float>(int2(0, y));
183 accumulated_color += load_input(texel + int2(0, y)) * weight;
184 accumulated_color += load_input(texel + int2(0, -y)) * weight;
185 }
186
187 /* Finally, compute the contributions of the pixels in the four quadrants of the filter,
188 * noting that the weights texture only stores the weights for the upper right quadrant, but
189 * since the filter is symmetric, the same weight is used for the rest of the quadrants and
190 * we add all four of their contributions. */
191 for (int y = 1; y < weights_size.y; y++) {
192 for (int x = 1; x < weights_size.x; x++) {
193 float weight = weights.load_pixel<float>(int2(x, y));
194 accumulated_color += load_input(texel + int2(x, y)) * weight;
195 accumulated_color += load_input(texel + int2(-x, y)) * weight;
196 accumulated_color += load_input(texel + int2(x, -y)) * weight;
197 accumulated_color += load_input(texel + int2(-x, -y)) * weight;
198 }
199 }
200
201 output.store_pixel(texel, accumulated_color);
202 });
203 }
204
206 {
207 if (this->context().use_gpu()) {
209 }
210 else {
212 }
213 }
214
216 {
217 const float2 blur_radius = this->compute_maximum_blur_size();
218 const Result &weights = context().cache_manager().symmetric_blur_weights.get(
219 context(), node_storage(bnode()).filtertype, blur_radius);
220
221 GPUShader *shader = context().get_shader("compositor_symmetric_blur_variable_size");
222 GPU_shader_bind(shader);
223
224 GPU_shader_uniform_1b(shader, "extend_bounds", get_extend_bounds());
225
226 input.bind_as_texture(shader, "input_tx");
227
228 weights.bind_as_texture(shader, "weights_tx");
229
230 const Result &input_size = get_input("Size");
231 input_size.bind_as_texture(shader, "size_tx");
232
233 Domain domain = compute_domain();
234 if (get_extend_bounds()) {
235 /* Add a radius amount of pixels in both sides of the image, hence the multiply by 2. */
236 domain.size += int2(math::ceil(blur_radius)) * 2;
237 }
238
239 output.allocate_texture(domain);
240 output.bind_as_image(shader, "output_img");
241
243
245 output.unbind_as_image();
246 input.unbind_as_texture();
247 weights.unbind_as_texture();
248 input_size.unbind_as_texture();
249 }
250
252 {
253 const float2 blur_radius = this->compute_maximum_blur_size();
254 const Result &weights = this->context().cache_manager().symmetric_blur_weights.get(
255 this->context(), node_storage(this->bnode()).filtertype, blur_radius);
256
257 Domain domain = this->compute_domain();
258 const bool extend_bounds = this->get_extend_bounds();
259 if (extend_bounds) {
260 /* Add a radius amount of pixels in both sides of the image, hence the multiply by 2. */
261 domain.size += int2(math::ceil(blur_radius)) * 2;
262 }
263
264 output.allocate_texture(domain);
265
266 auto load_input = [&](const int2 texel) {
267 return this->load_input(input, weights, texel, extend_bounds);
268 };
269
270 const Result &size = get_input("Size");
271 /* Similar to load_input but loads the size instead and clamps to borders instead of returning
272 * zero for out of bound access. See load_input for more information. */
273 auto load_size = [&](const int2 texel) {
274 int2 blur_radius = weights.domain().size - 1;
275 int2 offset = extend_bounds ? blur_radius : int2(0);
276 return math::max(float2(0.0f), size.load_pixel_extended<float3>(texel - offset).xy());
277 };
278
279 parallel_for(domain.size, [&](const int2 texel) {
280 float4 accumulated_color = float4(0.0f);
281 float4 accumulated_weight = float4(0.0f);
282
283 const float2 size = load_size(texel);
284 int2 radius = int2(math::ceil(size));
285 float2 coordinates_scale = float2(1.0f) / (size + float2(1.0f));
286
287 /* First, compute the contribution of the center pixel. */
288 float4 center_color = load_input(texel);
289 float center_weight = weights.load_pixel<float>(int2(0));
290 accumulated_color += center_color * center_weight;
291 accumulated_weight += center_weight;
292
293 /* Then, compute the contributions of the pixels along the x axis of the filter, noting that
294 * the weights texture only stores the weights for the positive half, but since the filter is
295 * symmetric, the same weight is used for the negative half and we add both of their
296 * contributions. */
297 for (int x = 1; x <= radius.x; x++) {
298 float weight_coordinates = (x + 0.5f) * coordinates_scale.x;
299 float weight = weights.sample_bilinear_extended(float2(weight_coordinates, 0.0f)).x;
300 accumulated_color += load_input(texel + int2(x, 0)) * weight;
301 accumulated_color += load_input(texel + int2(-x, 0)) * weight;
302 accumulated_weight += weight * 2.0f;
303 }
304
305 /* Then, compute the contributions of the pixels along the y axis of the filter, noting that
306 * the weights texture only stores the weights for the positive half, but since the filter is
307 * symmetric, the same weight is used for the negative half and we add both of their
308 * contributions. */
309 for (int y = 1; y <= radius.y; y++) {
310 float weight_coordinates = (y + 0.5f) * coordinates_scale.y;
311 float weight = weights.sample_bilinear_extended(float2(0.0f, weight_coordinates)).x;
312 accumulated_color += load_input(texel + int2(0, y)) * weight;
313 accumulated_color += load_input(texel + int2(0, -y)) * weight;
314 accumulated_weight += weight * 2.0f;
315 }
316
317 /* Finally, compute the contributions of the pixels in the four quadrants of the filter,
318 * noting that the weights texture only stores the weights for the upper right quadrant, but
319 * since the filter is symmetric, the same weight is used for the rest of the quadrants and
320 * we add all four of their contributions. */
321 for (int y = 1; y <= radius.y; y++) {
322 for (int x = 1; x <= radius.x; x++) {
323 float2 weight_coordinates = (float2(x, y) + float2(0.5f)) * coordinates_scale;
324 float weight = weights.sample_bilinear_extended(weight_coordinates).x;
325 accumulated_color += load_input(texel + int2(x, y)) * weight;
326 accumulated_color += load_input(texel + int2(-x, y)) * weight;
327 accumulated_color += load_input(texel + int2(x, -y)) * weight;
328 accumulated_color += load_input(texel + int2(-x, -y)) * weight;
329 accumulated_weight += weight * 4.0f;
330 }
331 }
332
333 accumulated_color = math::safe_divide(accumulated_color, accumulated_weight);
334
335 output.store_pixel(texel, accumulated_color);
336 });
337 }
338
340 {
341 return maximum_float3(this->context(), this->get_input("Size")).xy();
342 }
343
344 /* Loads the input color of the pixel at the given texel. If bounds are extended, then the input
345 * is treated as padded by a blur size amount of pixels of zero color, and the given texel is
346 * assumed to be in the space of the image after padding. So we offset the texel by the blur
347 * radius amount and fall back to a zero color if it is out of bounds. For instance, if the input
348 * is padded by 5 pixels to the left of the image, the first 5 pixels should be out of bounds and
349 * thus zero, hence the introduced offset. */
351 const Result &weights,
352 const int2 texel,
353 const bool extend_bounds)
354 {
356 if (extend_bounds) {
357 /* Notice that we subtract 1 because the weights result have an extra center weight, see the
358 * SymmetricBlurWeights class for more information. */
359 int2 blur_radius = weights.domain().size - 1;
360 color = input.load_pixel_zero<float4>(texel - blur_radius);
361 }
362 else {
363 color = input.load_pixel_extended<float4>(texel);
364 }
365
366 return color;
367 }
368
370 {
371 const Result &input = this->get_input("Image");
372 if (input.is_single_value()) {
373 return true;
374 }
375
376 const Result &size = this->get_input("Size");
377 if (!size.is_single_value()) {
378 return false;
379 }
380
381 if (this->get_blur_size() == float2(0.0)) {
382 return true;
383 }
384
385 return false;
386 }
387
388 /* The blur node can operate with different filter types, evaluated on the normalized distance to
389 * the center of the filter. Some of those filters are separable and can be computed as such. If
390 * the Separable input is true, then the filter is always computed as separable even if it is not
391 * in fact separable, in which case, the used filter is a cheaper approximation to the actual
392 * filter. Otherwise, the filter is computed as separable if it is in fact separable and as a
393 * normal 2D filter otherwise. */
395 {
396 if (this->get_separable()) {
397 return true;
398 }
399
400 /* Only Gaussian filters are separable. The rest is not. */
401 switch (node_storage(bnode()).filtertype) {
402 case R_FILTER_GAUSS:
404 return true;
405 default:
406 return false;
407 }
408 }
409
411 {
412 BLI_assert(this->get_input("Size").is_single_value());
413 return math::max(float2(0.0f), this->get_input("Size").get_single_value<float3>().xy());
414 }
415
417 {
418 return this->get_input("Separable").get_single_value_default(true);
419 }
420
422 {
423 return this->get_input("Extend Bounds").get_single_value_default(false);
424 }
425};
426
428{
429 return new BlurOperation(context, node);
430}
431
432} // namespace blender::nodes::node_composite_blur_cc
433
435{
436 namespace file_ns = blender::nodes::node_composite_blur_cc;
437
438 static blender::bke::bNodeType ntype;
439
440 cmp_node_type_base(&ntype, "CompositorNodeBlur", CMP_NODE_BLUR);
441 ntype.ui_name = "Blur";
442 ntype.ui_description = "Blur an image, using several blur modes";
443 ntype.enum_name_legacy = "BLUR";
445 ntype.declare = file_ns::cmp_node_blur_declare;
446 ntype.draw_buttons = file_ns::node_composit_buts_blur;
447 ntype.flag |= NODE_PREVIEW;
448 ntype.initfunc = file_ns::node_composit_init_blur;
451 ntype.get_compositor_operation = file_ns::get_compositor_operation;
452
454}
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1215
#define NODE_CLASS_OP_FILTER
Definition BKE_node.hh:437
#define CMP_NODE_BLUR
#define BLI_assert(a)
Definition BLI_assert.h:46
@ NODE_PREVIEW
@ R_FILTER_GAUSS
@ R_FILTER_FAST_GAUSS
void GPU_shader_bind(GPUShader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_uniform_1b(GPUShader *sh, const char *name, bool value)
void GPU_shader_unbind()
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ UI_ITEM_R_SPLIT_EMPTY_NAME
for(;discarded_id_iter !=nullptr;discarded_id_iter=static_cast< ID * >(discarded_id_iter->next))
Definition blendfile.cc:634
BMesh const char void * data
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
GPUShader * get_shader(const char *info_name, ResultPrecision precision)
NodeOperation(Context &context, DNode node)
Result & get_result(StringRef identifier)
Definition operation.cc:39
Result & get_input(StringRef identifier) const
Definition operation.cc:138
virtual Domain compute_domain()
Definition operation.cc:56
void share_data(const Result &source)
Definition result.cc:401
void unbind_as_texture() const
Definition result.cc:389
void bind_as_texture(GPUShader *shader, const char *texture_name) const
Definition result.cc:365
const Domain & domain() const
T load_pixel(const int2 &texel) const
float4 sample_bilinear_extended(const float2 &coordinates) const
bool is_single_value() const
Definition result.cc:625
SymmetricBlurWeightsContainer symmetric_blur_weights
Result & get(Context &context, int type, float2 radius)
void execute_constant_size_gpu(const Result &input, Result &output)
void execute_constant_size(const Result &input, Result &output)
void execute_variable_size_gpu(const Result &input, Result &output)
float4 load_input(const Result &input, const Result &weights, const int2 texel, const bool extend_bounds)
void execute_variable_size_cpu(const Result &input, Result &output)
void execute_constant_size_cpu(const Result &input, Result &output)
void execute_variable_size(const Result &input, Result &output)
#define input
VecBase< float, 2 > float2
#define output
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void node_register_type(bNodeType &ntype)
Definition node.cc:2748
void node_type_storage(bNodeType &ntype, std::optional< StringRefNull > storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:5603
void recursive_gaussian_blur(Context &context, const Result &input, Result &output, const float2 &radius)
void compute_dispatch_threads_at_least(GPUShader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:170
void symmetric_separable_blur(Context &context, const Result &input, Result &output, const float2 &radius, const int filter_type=R_FILTER_GAUSS, const bool extend_bounds=false)
float3 maximum_float3(Context &context, const Result &result)
void parallel_for(const int2 range, const Function &function)
T safe_divide(const T &a, const T &b)
T ceil(const T &a)
T max(const T &a, const T &b)
static void cmp_node_blur_declare(NodeDeclarationBuilder &b)
static void node_composit_init_blur(bNodeTree *, bNode *node)
static void node_composit_buts_blur(uiLayout *layout, bContext *, PointerRNA *ptr)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
VecBase< float, 3 > float3
static void register_node_type_cmp_blur()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:42
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:54
void * storage
Defines a node type.
Definition BKE_node.hh:226
std::string ui_description
Definition BKE_node.hh:232
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:336
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:277
const char * enum_name_legacy
Definition BKE_node.hh:235
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:355
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &input_name)
int xy[2]
Definition wm_draw.cc:174
PointerRNA * ptr
Definition wm_files.cc:4227