Blender V5.0
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_types.hh"
15
17
18#include "GPU_shader.hh"
19
20#include "COM_algorithm_pad.hh"
24#include "COM_node_operation.hh"
26#include "COM_utilities.hh"
27
29
31
32static const EnumPropertyItem type_items[] = {
33 {R_FILTER_BOX, "FLAT", 0, N_("Flat"), ""},
34 {R_FILTER_TENT, "TENT", 0, N_("Tent"), ""},
35 {R_FILTER_QUAD, "QUAD", 0, N_("Quadratic"), ""},
36 {R_FILTER_CUBIC, "CUBIC", 0, N_("Cubic"), ""},
37 {R_FILTER_GAUSS, "GAUSS", 0, N_("Gaussian"), ""},
38 {R_FILTER_FAST_GAUSS, "FAST_GAUSS", 0, N_("Fast Gaussian"), ""},
39 {R_FILTER_CATROM, "CATROM", 0, N_("Catrom"), ""},
40 {R_FILTER_MITCH, "MITCH", 0, N_("Mitch"), ""},
41 {0, nullptr, 0, nullptr, nullptr},
42};
43
45{
46 b.use_custom_socket_order();
47 b.allow_any_socket_order();
48 b.add_input<decl::Color>("Image")
49 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
50 .hide_value()
51 .structure_type(StructureType::Dynamic);
52 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
53
54 b.add_input<decl::Vector>("Size")
55 .dimensions(2)
56 .default_value({0.0f, 0.0f})
57 .min(0.0f)
58 .structure_type(StructureType::Dynamic);
59 b.add_input<decl::Menu>("Type")
60 .default_value(R_FILTER_GAUSS)
61 .static_items(type_items)
63 b.add_input<decl::Bool>("Extend Bounds").default_value(false);
64 b.add_input<decl::Bool>("Separable")
65 .default_value(true)
67 "Use faster approximation by blurring along the horizontal and vertical directions "
68 "independently");
69}
70
71static void node_composit_init_blur(bNodeTree * /*ntree*/, bNode *node)
72{
73 /* Unused, but allocated for forward compatibility. */
75 node->storage = data;
76}
77
78using namespace blender::compositor;
79
81 public:
83
84 void execute() override
85 {
86 const Result &input = this->get_input("Image");
87 Result &output = this->get_result("Image");
88 if (this->is_identity()) {
89 output.share_data(input);
90 return;
91 }
92
93 const Result &size = this->get_input("Size");
94 if (this->get_extend_bounds()) {
95 Result padded_input = this->context().create_result(ResultType::Color);
96 Result padded_size = this->context().create_result(ResultType::Float2);
97
98 const int2 padding_size = this->compute_extended_boundary_size(size);
99
100 pad(this->context(), input, padded_input, padding_size, PaddingMethod::Zero);
101 pad(this->context(), size, padded_size, padding_size, PaddingMethod::Extend);
102
103 this->execute_blur(padded_input, padded_size);
104 padded_input.release();
105 padded_size.release();
106 }
107 else {
108 this->execute_blur(input, size);
109 }
110 }
111
112 /* Computes the number of pixels that the image should be extended by if Extend Bounds is
113 * enabled. */
115 {
117
118 /* For constant sized blur, the extension should just be the blur radius. */
119 if (size.is_single_value()) {
120 return int2(math::ceil(this->get_blur_size()));
121 }
122
123 /* For variable sized blur, the extension should be the maximum size. */
125 }
126
127 void execute_blur(const Result &input, const Result &size)
128 {
129 Result &output = this->get_result("Image");
130 if (!size.is_single_value()) {
131 this->execute_variable_size(input, size, output);
132 }
133 else if (this->get_type() == R_FILTER_FAST_GAUSS) {
134 recursive_gaussian_blur(this->context(), input, output, this->get_blur_size());
135 }
136 else if (use_separable_filter()) {
138 this->context(), input, output, this->get_blur_size(), this->get_type());
139 }
140 else {
141 this->execute_constant_size(input, output);
142 }
143 }
144
146 {
147 if (this->context().use_gpu()) {
148 this->execute_constant_size_gpu(input, output);
149 }
150 else {
151 this->execute_constant_size_cpu(input, output);
152 }
153 }
154
156 {
157 gpu::Shader *shader = context().get_shader("compositor_symmetric_blur");
158 GPU_shader_bind(shader);
159
160 input.bind_as_texture(shader, "input_tx");
161
162 const float2 blur_radius = this->get_blur_size();
163
165 context(), this->get_type(), blur_radius);
166 weights.bind_as_texture(shader, "weights_tx");
167
168 const Domain domain = input.domain();
169 output.allocate_texture(domain);
170 output.bind_as_image(shader, "output_img");
171
173
175 output.unbind_as_image();
176 input.unbind_as_texture();
177 weights.unbind_as_texture();
178 }
179
181 {
182 const float2 blur_radius = this->get_blur_size();
183 const Result &weights = this->context().cache_manager().symmetric_blur_weights.get(
184 this->context(), this->get_type(), blur_radius);
185
186 const Domain domain = input.domain();
187 output.allocate_texture(domain);
188
189 parallel_for(domain.size, [&](const int2 texel) {
190 float4 accumulated_color = float4(0.0f);
191
192 /* First, compute the contribution of the center pixel. */
193 float4 center_color = input.load_pixel_extended<float4>(texel);
194 accumulated_color += center_color * weights.load_pixel<float>(int2(0));
195
196 int2 weights_size = weights.domain().size;
197
198 /* Then, compute the contributions of the pixels along the x axis of the filter, noting that
199 * the weights texture only stores the weights for the positive half, but since the filter is
200 * symmetric, the same weight is used for the negative half and we add both of their
201 * contributions. */
202 for (int x = 1; x < weights_size.x; x++) {
203 float weight = weights.load_pixel<float>(int2(x, 0));
204 accumulated_color += input.load_pixel_extended<float4>(texel + int2(x, 0)) * weight;
205 accumulated_color += input.load_pixel_extended<float4>(texel + int2(-x, 0)) * weight;
206 }
207
208 /* Then, compute the contributions of the pixels along the y axis of the filter, noting that
209 * the weights texture only stores the weights for the positive half, but since the filter is
210 * symmetric, the same weight is used for the negative half and we add both of their
211 * contributions. */
212 for (int y = 1; y < weights_size.y; y++) {
213 float weight = weights.load_pixel<float>(int2(0, y));
214 accumulated_color += input.load_pixel_extended<float4>(texel + int2(0, y)) * weight;
215 accumulated_color += input.load_pixel_extended<float4>(texel + int2(0, -y)) * weight;
216 }
217
218 /* Finally, compute the contributions of the pixels in the four quadrants of the filter,
219 * noting that the weights texture only stores the weights for the upper right quadrant, but
220 * since the filter is symmetric, the same weight is used for the rest of the quadrants and
221 * we add all four of their contributions. */
222 for (int y = 1; y < weights_size.y; y++) {
223 for (int x = 1; x < weights_size.x; x++) {
224 float weight = weights.load_pixel<float>(int2(x, y));
225 accumulated_color += input.load_pixel_extended<float4>(texel + int2(x, y)) * weight;
226 accumulated_color += input.load_pixel_extended<float4>(texel + int2(-x, y)) * weight;
227 accumulated_color += input.load_pixel_extended<float4>(texel + int2(x, -y)) * weight;
228 accumulated_color += input.load_pixel_extended<float4>(texel + int2(-x, -y)) * weight;
229 }
230 }
231
232 output.store_pixel(texel, accumulated_color);
233 });
234 }
235
237 {
238 if (this->context().use_gpu()) {
240 }
241 else {
243 }
244 }
245
246 void execute_variable_size_gpu(const Result &input, const Result &size_input, Result &output)
247 {
248 const float2 blur_radius = this->compute_maximum_blur_size();
249 const Result &weights = context().cache_manager().symmetric_blur_weights.get(
250 context(), this->get_type(), blur_radius);
251
252 gpu::Shader *shader = context().get_shader("compositor_symmetric_blur_variable_size");
253 GPU_shader_bind(shader);
254
255 input.bind_as_texture(shader, "input_tx");
256 weights.bind_as_texture(shader, "weights_tx");
257 size_input.bind_as_texture(shader, "size_tx");
258
259 const Domain domain = input.domain();
260 output.allocate_texture(domain);
261 output.bind_as_image(shader, "output_img");
262
264
266 output.unbind_as_image();
267 input.unbind_as_texture();
268 weights.unbind_as_texture();
269 size_input.unbind_as_texture();
270 }
271
272 void execute_variable_size_cpu(const Result &input, const Result &size_input, Result &output)
273 {
274 const float2 blur_radius = this->compute_maximum_blur_size();
275 const Result &weights = this->context().cache_manager().symmetric_blur_weights.get(
276 this->context(), this->get_type(), blur_radius);
277
278 const Domain domain = input.domain();
279 output.allocate_texture(domain);
280
281 parallel_for(domain.size, [&](const int2 texel) {
282 float4 accumulated_color = float4(0.0f);
283 float4 accumulated_weight = float4(0.0f);
284
285 const float2 size = math::max(float2(0.0f), size_input.load_pixel_extended<float2>(texel));
286 int2 radius = int2(math::ceil(size));
287 float2 coordinates_scale = float2(1.0f) / (size + float2(1.0f));
288
289 /* First, compute the contribution of the center pixel. */
290 float4 center_color = input.load_pixel_extended<float4>(texel);
291 float center_weight = weights.load_pixel<float>(int2(0));
292 accumulated_color += center_color * center_weight;
293 accumulated_weight += center_weight;
294
295 /* Then, compute the contributions of the pixels along the x axis of the filter, noting that
296 * the weights texture only stores the weights for the positive half, but since the filter is
297 * symmetric, the same weight is used for the negative half and we add both of their
298 * contributions. */
299 for (int x = 1; x <= radius.x; x++) {
300 float weight_coordinates = (x + 0.5f) * coordinates_scale.x;
301 float weight = weights.sample_bilinear_extended(float2(weight_coordinates, 0.0f)).x;
302 accumulated_color += input.load_pixel_extended<float4>(texel + int2(x, 0)) * weight;
303 accumulated_color += input.load_pixel_extended<float4>(texel + int2(-x, 0)) * weight;
304 accumulated_weight += weight * 2.0f;
305 }
306
307 /* Then, compute the contributions of the pixels along the y axis of the filter, noting that
308 * the weights texture only stores the weights for the positive half, but since the filter is
309 * symmetric, the same weight is used for the negative half and we add both of their
310 * contributions. */
311 for (int y = 1; y <= radius.y; y++) {
312 float weight_coordinates = (y + 0.5f) * coordinates_scale.y;
313 float weight = weights.sample_bilinear_extended(float2(0.0f, weight_coordinates)).x;
314 accumulated_color += input.load_pixel_extended<float4>(texel + int2(0, y)) * weight;
315 accumulated_color += input.load_pixel_extended<float4>(texel + int2(0, -y)) * weight;
316 accumulated_weight += weight * 2.0f;
317 }
318
319 /* Finally, compute the contributions of the pixels in the four quadrants of the filter,
320 * noting that the weights texture only stores the weights for the upper right quadrant, but
321 * since the filter is symmetric, the same weight is used for the rest of the quadrants and
322 * we add all four of their contributions. */
323 for (int y = 1; y <= radius.y; y++) {
324 for (int x = 1; x <= radius.x; x++) {
325 float2 weight_coordinates = (float2(x, y) + float2(0.5f)) * coordinates_scale;
326 float weight = weights.sample_bilinear_extended(weight_coordinates).x;
327 accumulated_color += input.load_pixel_extended<float4>(texel + int2(x, y)) * weight;
328 accumulated_color += input.load_pixel_extended<float4>(texel + int2(-x, y)) * weight;
329 accumulated_color += input.load_pixel_extended<float4>(texel + int2(x, -y)) * weight;
330 accumulated_color += input.load_pixel_extended<float4>(texel + int2(-x, -y)) * weight;
331 accumulated_weight += weight * 4.0f;
332 }
333 }
334
335 accumulated_color = math::safe_divide(accumulated_color, accumulated_weight);
336
337 output.store_pixel(texel, accumulated_color);
338 });
339 }
340
342 {
343 return math::max(float2(0.0f), maximum_float2(this->context(), this->get_input("Size")));
344 }
345
347 {
348 const Result &input = this->get_input("Image");
349 if (input.is_single_value()) {
350 return true;
351 }
352
353 const Result &size = this->get_input("Size");
354 if (!size.is_single_value()) {
355 return false;
356 }
357
358 if (this->get_blur_size() == float2(0.0)) {
359 return true;
360 }
361
362 return false;
363 }
364
365 /* The blur node can operate with different filter types, evaluated on the normalized distance to
366 * the center of the filter. Some of those filters are separable and can be computed as such. If
367 * the Separable input is true, then the filter is always computed as separable even if it is not
368 * in fact separable, in which case, the used filter is a cheaper approximation to the actual
369 * filter. Otherwise, the filter is computed as separable if it is in fact separable and as a
370 * normal 2D filter otherwise. */
372 {
373 if (this->get_separable()) {
374 return true;
375 }
376
377 /* Only Gaussian filters are separable. The rest is not. */
378 switch (this->get_type()) {
379 case R_FILTER_GAUSS:
381 return true;
382 default:
383 return false;
384 }
385 }
386
388 {
389 BLI_assert(this->get_input("Size").is_single_value());
390 return math::max(float2(0.0f), this->get_input("Size").get_single_value<float2>());
391 }
392
394 {
395 return this->get_input("Separable").get_single_value_default(true);
396 }
397
399 {
400 return this->get_input("Extend Bounds").get_single_value_default(false);
401 }
402
404 {
405 const Result &input = this->get_input("Type");
406 const MenuValue default_menu_value = MenuValue(R_FILTER_GAUSS);
407 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
408 return menu_value.value;
409 }
410};
411
413{
414 return new BlurOperation(context, node);
415}
416
417} // namespace blender::nodes::node_composite_blur_cc
418
420{
421 namespace file_ns = blender::nodes::node_composite_blur_cc;
422
423 static blender::bke::bNodeType ntype;
424
425 cmp_node_type_base(&ntype, "CompositorNodeBlur", CMP_NODE_BLUR);
426 ntype.ui_name = "Blur";
427 ntype.ui_description = "Blur an image, using several blur modes";
428 ntype.enum_name_legacy = "BLUR";
430 ntype.declare = file_ns::cmp_node_blur_declare;
431 ntype.flag |= NODE_PREVIEW;
432 ntype.initfunc = file_ns::node_composit_init_blur;
435 ntype.get_compositor_operation = file_ns::get_compositor_operation;
436
438}
#define NODE_CLASS_OP_FILTER
Definition BKE_node.hh:451
#define CMP_NODE_BLUR
#define BLI_assert(a)
Definition BLI_assert.h:46
@ NODE_PREVIEW
@ R_FILTER_TENT
@ R_FILTER_GAUSS
@ R_FILTER_FAST_GAUSS
@ R_FILTER_CATROM
@ R_FILTER_MITCH
@ R_FILTER_BOX
@ R_FILTER_CUBIC
@ R_FILTER_QUAD
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_unbind()
#define NOD_REGISTER_NODE(REGISTER_FUNC)
int pad[32 - sizeof(int)]
BMesh const char void * data
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
Result create_result(ResultType type, ResultPrecision precision)
gpu::Shader * 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
void share_data(const Result &source)
Definition result.cc:523
void unbind_as_texture() const
Definition result.cc:511
void bind_as_texture(gpu::Shader *shader, const char *texture_name) const
Definition result.cc:487
T load_pixel(const int2 &texel) const
float4 sample_bilinear_extended(const float2 &coordinates) const
bool is_single_value() const
Definition result.cc:758
SymmetricBlurWeightsContainer symmetric_blur_weights
Result & get(Context &context, int type, float2 radius)
void execute_variable_size_cpu(const Result &input, const Result &size_input, Result &output)
void execute_constant_size_gpu(const Result &input, Result &output)
void execute_variable_size(const Result &input, const Result &size, Result &output)
void execute_blur(const Result &input, const Result &size)
void execute_constant_size(const Result &input, Result &output)
void execute_constant_size_cpu(const Result &input, Result &output)
void execute_variable_size_gpu(const Result &input, const Result &size_input, Result &output)
#define input
#define output
VecBase< float, 2 > float2
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
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:5414
void symmetric_separable_blur(Context &context, const Result &input, Result &output, const float2 &radius, const int filter_type=R_FILTER_GAUSS)
void compute_dispatch_threads_at_least(gpu::Shader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:196
void recursive_gaussian_blur(Context &context, const Result &input, Result &output, const float2 &radius)
float2 maximum_float2(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 const EnumPropertyItem type_items[]
static void cmp_node_blur_declare(NodeDeclarationBuilder &b)
static void node_composit_init_blur(bNodeTree *, bNode *node)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
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
#define min(a, b)
Definition sort.cc:36
void * storage
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:348
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &input_name)
#define N_(msgid)