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