Blender V5.0
node_composite_directionalblur.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_math_base.hh"
10#include "BLI_math_matrix.hh"
11
12#include "UI_resources.hh"
13
14#include "GPU_shader.hh"
15
16#include "COM_node_operation.hh"
17#include "COM_utilities.hh"
18
20
22
24{
25 b.use_custom_socket_order();
26 b.allow_any_socket_order();
27
28 b.add_input<decl::Color>("Image")
29 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
30 .hide_value()
31 .structure_type(StructureType::Dynamic);
32 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
33
34 b.add_input<decl::Int>("Samples").default_value(1).min(1).max(32).description(
35 "The number of samples used to compute the blur. The more samples the smoother the "
36 "result, but at the expense of more compute time. The actual number of samples is two "
37 "to the power of this input, so it increases exponentially");
38 b.add_input<decl::Vector>("Center")
39 .subtype(PROP_FACTOR)
40 .dimensions(2)
41 .default_value({0.5f, 0.5f})
42 .min(0.0f)
43 .max(1.0f)
44 .description(
45 "The position at which the transformations pivot around. Defined in normalized "
46 "coordinates, so 0 means lower left corner and 1 means upper right corner of the image");
47
48 b.add_input<decl::Float>("Rotation")
49 .default_value(0.0f)
51 .description("The amount of rotation that the blur spans");
52 b.add_input<decl::Float>("Scale").default_value(1.0f).min(0.0f).description(
53 "The amount of scaling that the blur spans");
54
55 PanelDeclarationBuilder &translation_panel = b.add_panel("Translation").default_closed(false);
56 translation_panel.add_input<decl::Float>("Amount", "Translation Amount")
57 .default_value(0.0f)
59 .min(-1.0f)
60 .max(1.0f)
61 .description(
62 "The amount of translation that the blur spans in the specified direction relative to "
63 "the size of the image. Negative values indicate translation in the opposite direction");
64 translation_panel.add_input<decl::Float>("Direction", "Translation Direction")
65 .default_value(0.0f)
67 .description("The angle that defines the direction of the translation");
68}
69
70using namespace blender::compositor;
71
73 public:
75
76 void execute() override
77 {
78 if (this->is_identity()) {
79 const Result &input = this->get_input("Image");
80 Result &output = this->get_result("Image");
81 output.share_data(input);
82 return;
83 }
84
85 if (this->context().use_gpu()) {
86 this->execute_gpu();
87 }
88 else {
89 this->execute_cpu();
90 }
91 }
92
94 {
95 gpu::Shader *shader = context().get_shader("compositor_directional_blur");
96 GPU_shader_bind(shader);
97
98 /* The number of iterations does not cover the original image, that is, the image with no
99 * transformation. So add an extra iteration for the original image and put that into
100 * consideration in the shader. */
101 GPU_shader_uniform_1i(shader, "iterations", this->get_iterations() + 1);
102 GPU_shader_uniform_2fv(shader, "origin", this->get_origin());
103 GPU_shader_uniform_2fv(shader, "delta_translation", this->get_delta_translation());
104 GPU_shader_uniform_1f(shader, "delta_rotation_sin", math::sin(this->get_delta_rotation()));
105 GPU_shader_uniform_1f(shader, "delta_rotation_cos", math::cos(this->get_delta_rotation()));
106 GPU_shader_uniform_1f(shader, "delta_scale", this->get_delta_scale());
107
108 const Result &input_image = get_input("Image");
109 GPU_texture_filter_mode(input_image, true);
111 input_image.bind_as_texture(shader, "input_tx");
112
113 const Domain domain = compute_domain();
114 Result &output_image = get_result("Image");
115 output_image.allocate_texture(domain);
116 output_image.bind_as_image(shader, "output_img");
117
119
121 output_image.unbind_as_image();
122 input_image.unbind_as_texture();
123 }
124
126 {
127 /* The number of iterations does not cover the original image, that is, the image with no
128 * transformation. So add an extra iteration for the original image and put that into
129 * consideration in the code. */
130 const int iterations = this->get_iterations() + 1;
131 const float2 origin = this->get_origin();
132 const float2 delta_translation = this->get_delta_translation();
133 const float delta_rotation_sin = math::sin(this->get_delta_rotation());
134 const float delta_rotation_cos = math::cos(this->get_delta_rotation());
135 const float delta_scale = this->get_delta_scale();
136
137 const Result &input = get_input("Image");
138
139 const Domain domain = compute_domain();
140 Result &output = get_result("Image");
141 output.allocate_texture(domain);
142
143 const int2 size = domain.size;
144 parallel_for(size, [&](const int2 texel) {
145 float2 coordinates = float2(texel) + float2(0.5f);
146
147 float current_sin = 0.0f;
148 float current_cos = 1.0f;
149 float current_scale = 1.0f;
150 float2 current_translation = float2(0.0f);
151
152 /* For each iteration, accumulate the input at the transformed coordinates, then increment
153 * the transformations for the next iteration. */
154 float4 accumulated_color = float4(0.0f);
155 for (int i = 0; i < iterations; i++) {
156 /* Transform the coordinates by first offsetting the origin, scaling, translating,
157 * rotating, then finally restoring the origin. Notice that we do the inverse of each of
158 * the transforms, since we are transforming the coordinates, not the image. */
159 float2 transformed_coordinates = coordinates;
160 transformed_coordinates -= origin;
161 transformed_coordinates /= current_scale;
162 transformed_coordinates -= current_translation;
163 transformed_coordinates = transformed_coordinates *
164 float2x2(float2(current_cos, current_sin),
165 float2(-current_sin, current_cos));
166 transformed_coordinates += origin;
167
168 accumulated_color += input.sample_bilinear_zero(transformed_coordinates / float2(size));
169
170 current_scale += delta_scale;
171 current_translation += delta_translation;
172
173 /* Those are the sine and cosine addition identities. Used to avoid computing sine and
174 * cosine at each iteration. */
175 float new_sin = current_sin * delta_rotation_cos + current_cos * delta_rotation_sin;
176 current_cos = current_cos * delta_rotation_cos - current_sin * delta_rotation_sin;
177 current_sin = new_sin;
178 }
179
180 output.store_pixel(texel, accumulated_color / iterations);
181 });
182 }
183
184 /* Get the delta that will be added to the translation on each iteration. The translation is in
185 * the negative x direction rotated in the clock-wise direction, hence the negative sign for the
186 * rotation and translation vector. */
188 {
189 const float2 input_size = float2(get_input("Image").domain().size);
190 const float diagonal_length = math::length(input_size);
191 const float translation_amount = diagonal_length * this->get_translation_amount();
194 const float2 translation = rotation *
195 float2(-translation_amount / this->get_iterations(), 0.0f);
196 return translation;
197 }
198
199 /* Get the delta that will be added to the rotation on each iteration. */
201 {
202 return this->get_rotation() / this->get_iterations();
203 }
204
205 /* Get the delta that will be added to the scale on each iteration. */
207 {
208 return (this->get_scale() - 1.0f) / this->get_iterations();
209 }
210
212 {
213 const float2 input_size = float2(get_input("Image").domain().size);
214 return this->get_center() * input_size;
215 }
216
217 /* The actual number of iterations is 2 to the power of the user supplied iterations. The power
218 * is implemented using a bit shift. But also make sure it doesn't exceed the upper limit which
219 * is the number of diagonal pixels. */
221 {
222 const int iterations = 2 << (this->get_samples() - 1);
223 const int upper_limit = math::ceil(math::length(float2(get_input("Image").domain().size)));
224 return math::min(iterations, upper_limit);
225 }
226
228 {
229 const Result &input = this->get_input("Image");
230 if (input.is_single_value()) {
231 return true;
232 }
233
234 if (this->get_translation_amount() != 0.0f) {
235 return false;
236 }
237
238 if (this->get_rotation() != 0.0f) {
239 return false;
240 }
241
242 if (this->get_scale() != 1.0f) {
243 return false;
244 }
245
246 return true;
247 }
248
250 {
251 return math::clamp(this->get_input("Samples").get_single_value_default(1), 1, 32);
252 }
253
255 {
256 return math::clamp(this->get_input("Center").get_single_value_default(float2(0.5f)),
257 float2(0.0f),
258 float2(1.0f));
259 }
260
262 {
263 return math::clamp(
264 this->get_input("Translation Amount").get_single_value_default(0.0f), -1.0f, 1.0f);
265 }
266
268 {
269 return this->get_input("Translation Direction").get_single_value_default(0.0f);
270 }
271
273 {
274 return this->get_input("Rotation").get_single_value_default(0.0f);
275 }
276
277 float get_scale()
278 {
279 return math::max(10e-6f, this->get_input("Scale").get_single_value_default(1.0f));
280 }
281};
282
284{
285 return new DirectionalBlurOperation(context, node);
286}
287
288} // namespace blender::nodes::node_composite_directionalblur_cc
289
291{
293
294 static blender::bke::bNodeType ntype;
295
296 cmp_node_type_base(&ntype, "CompositorNodeDBlur", CMP_NODE_DBLUR);
297 ntype.ui_name = "Directional Blur";
298 ntype.ui_description = "Blur an image along a direction";
299 ntype.enum_name_legacy = "DBLUR";
301 ntype.declare = file_ns::cmp_node_directional_blur_declare;
302 ntype.get_compositor_operation = file_ns::get_compositor_operation;
303
305}
#define NODE_CLASS_OP_FILTER
Definition BKE_node.hh:451
#define CMP_NODE_DBLUR
void GPU_shader_uniform_1f(blender::gpu::Shader *sh, const char *name, float value)
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_uniform_1i(blender::gpu::Shader *sh, const char *name, int value)
void GPU_shader_uniform_2fv(blender::gpu::Shader *sh, const char *name, const float data[2])
void GPU_shader_unbind()
void GPU_texture_extend_mode(blender::gpu::Texture *texture, GPUSamplerExtendMode extend_mode)
@ GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER
void GPU_texture_filter_mode(blender::gpu::Texture *texture, bool use_filter)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_ANGLE
Definition RNA_types.hh:252
@ PROP_FACTOR
Definition RNA_types.hh:251
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
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
virtual Domain compute_domain()
Definition operation.cc:56
void share_data(const Result &source)
Definition result.cc:523
T get_single_value_default(const T &default_value) const
void allocate_texture(const Domain domain, const bool from_pool=true, const std::optional< ResultStorageType > storage_type=std::nullopt)
Definition result.cc:389
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
void unbind_as_image() const
Definition result.cc:517
void bind_as_image(gpu::Shader *shader, const char *image_name, bool read=false) const
Definition result.cc:498
bool is_single_value() const
Definition result.cc:758
DeclType::Builder & add_input(StringRef name, StringRef identifier="")
#define input
#define output
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void compute_dispatch_threads_at_least(gpu::Shader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:196
void parallel_for(const int2 range, const Function &function)
AngleRadianBase< float > AngleRadian
T cos(const AngleRadianBase< T > &a)
T clamp(const T &a, const T &min, const T &max)
T length(const VecBase< T, Size > &a)
T min(const T &a, const T &b)
T ceil(const T &a)
T sin(const AngleRadianBase< T > &a)
T max(const T &a, const T &b)
MatT from_rotation(const RotationT &rotation)
static void cmp_node_directional_blur_declare(NodeDeclarationBuilder &b)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
MatBase< float, 2, 2 > float2x2
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
static void register_node_type_cmp_dblur()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
#define min(a, b)
Definition sort.cc:36
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
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362
i
Definition text_draw.cc:230