Blender V5.0
node_composite_cornerpin.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2013 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_math_geom.h"
12
13#include "DNA_node_types.h"
14
15#include "RNA_enum_types.hh"
16
17#include "GPU_shader.hh"
18#include "GPU_texture.hh"
19
20#include "BKE_node.hh"
21#include "BKE_tracking.h"
22
23#include "MEM_guardedalloc.h"
24
25#include "COM_algorithm_smaa.hh"
26#include "COM_domain.hh"
27#include "COM_node_operation.hh"
28#include "COM_utilities.hh"
29
31
33
35{
36 b.use_custom_socket_order();
37 b.allow_any_socket_order();
38
39 b.add_input<decl::Color>("Image")
40 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
41 .hide_value()
42 .structure_type(StructureType::Dynamic);
43 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
44 b.add_output<decl::Float>("Plane").structure_type(StructureType::Dynamic);
45
46 b.add_input<decl::Vector>("Upper Left")
47 .subtype(PROP_FACTOR)
48 .dimensions(2)
49 .default_value({0.0f, 1.0f})
50 .min(0.0f)
51 .max(1.0f);
52 b.add_input<decl::Vector>("Upper Right")
53 .subtype(PROP_FACTOR)
54 .dimensions(2)
55 .default_value({1.0f, 1.0f})
56 .min(0.0f)
57 .max(1.0f);
58 b.add_input<decl::Vector>("Lower Left")
59 .subtype(PROP_FACTOR)
60 .dimensions(2)
61 .default_value({0.0f, 0.0f})
62 .min(0.0f)
63 .max(1.0f);
64 b.add_input<decl::Vector>("Lower Right")
65 .subtype(PROP_FACTOR)
66 .dimensions(2)
67 .default_value({1.0f, 0.0f})
68 .min(0.0f)
69 .max(1.0f);
70
71 PanelDeclarationBuilder &sampling_panel = b.add_panel("Sampling").default_closed(true);
72 sampling_panel.add_input<decl::Menu>("Interpolation")
75 .description("Interpolation method")
76 .optional_label();
77 sampling_panel.add_input<decl::Menu>("Extension X")
78 .default_value(CMP_NODE_EXTENSION_MODE_CLIP)
80 .description("The extension mode applied to the X axis")
81 .optional_label();
82 sampling_panel.add_input<decl::Menu>("Extension Y")
83 .default_value(CMP_NODE_EXTENSION_MODE_CLIP)
85 .description("The extension mode applied to the Y axis")
86 .optional_label();
87}
88
89static void node_composit_init_cornerpin(bNodeTree * /*ntree*/, bNode *node)
90{
91 /* Unused, kept for forward compatibility. */
93 node->storage = data;
94}
95
96using namespace blender::compositor;
97
99 public:
101
102 void execute() override
103 {
104 const float3x3 homography_matrix = compute_homography_matrix();
105
106 const Result &input_image = this->get_input("Image");
107 Result &output_image = this->get_result("Image");
108 Result &output_mask = this->get_result("Plane");
109 if (input_image.is_single_value() || homography_matrix == float3x3::identity()) {
110 if (output_image.should_compute()) {
111 output_image.share_data(input_image);
112 }
113 if (output_mask.should_compute()) {
114 output_mask.allocate_single_value();
115 output_mask.set_single_value(1.0f);
116 }
117 return;
118 }
119
120 /* Only compute the mask if extension modes are not set to clip and if it is not used as an
121 * output. */
122 if (this->should_compute_mask()) {
123 Result plane_mask = compute_plane_mask(homography_matrix);
124 Result anti_aliased_plane_mask = context().create_result(ResultType::Float);
125 smaa(context(), plane_mask, anti_aliased_plane_mask);
126 plane_mask.release();
127
128 if (output_image.should_compute()) {
129 this->compute_plane(homography_matrix, &anti_aliased_plane_mask);
130 }
131
132 if (output_mask.should_compute()) {
133 output_mask.steal_data(anti_aliased_plane_mask);
134 }
135 else {
136 anti_aliased_plane_mask.release();
137 }
138 }
139 else {
140 if (output_image.should_compute()) {
141 this->compute_plane(homography_matrix, nullptr);
142 }
143 }
144 }
145
146 void compute_plane(const float3x3 &homography_matrix, Result *plane_mask)
147 {
148 if (this->context().use_gpu()) {
149 this->compute_plane_gpu(homography_matrix, plane_mask);
150 }
151 else {
152 this->compute_plane_cpu(homography_matrix, plane_mask);
153 }
154 }
155
156 void compute_plane_gpu(const float3x3 &homography_matrix, Result *plane_mask)
157 {
158 gpu::Shader *shader = this->context().get_shader(this->get_shader_name());
159 GPU_shader_bind(shader);
160
161 GPU_shader_uniform_mat3_as_mat4(shader, "homography_matrix", homography_matrix.ptr());
162
163 Result &input_image = get_input("Image");
164 GPU_texture_mipmap_mode(input_image, true, true);
165 /* The texture sampler should use bilinear interpolation for both the bilinear and bicubic
166 * cases, as the logic used by the bicubic realization shader expects textures to use
167 * bilinear interpolation. */
168 const Interpolation interpolation = this->get_interpolation();
169 const ExtensionMode extension_mode_x = this->get_extension_mode_x();
170 const ExtensionMode extension_mode_y = this->get_extension_mode_y();
171
172 const bool use_bilinear = ELEM(interpolation, Interpolation::Bicubic, Interpolation::Bilinear);
173 const bool use_anisotropic = interpolation == Interpolation::Anisotropic;
174 GPU_texture_filter_mode(input_image, use_bilinear);
175 GPU_texture_anisotropic_filter(input_image, use_anisotropic);
176 GPU_texture_extend_mode_x(input_image, map_extension_mode_to_extend_mode(extension_mode_x));
177 GPU_texture_extend_mode_y(input_image, map_extension_mode_to_extend_mode(extension_mode_y));
178 input_image.bind_as_texture(shader, "input_tx");
179 if (plane_mask) {
180 plane_mask->bind_as_texture(shader, "mask_tx");
181 }
182
183 const Domain domain = compute_domain();
184 Result &output_image = get_result("Image");
185 output_image.allocate_texture(domain);
186 output_image.bind_as_image(shader, "output_img");
187
189
190 input_image.unbind_as_texture();
191 if (plane_mask) {
192 plane_mask->unbind_as_texture();
193 }
194
195 output_image.unbind_as_image();
197 }
198
199 void compute_plane_cpu(const float3x3 &homography_matrix, Result *plane_mask)
200 {
201 Result &input = get_input("Image");
202
203 const Domain domain = compute_domain();
204 Result &output = get_result("Image");
205 output.allocate_texture(domain);
206 const Interpolation interpolation = this->get_interpolation();
207 const ExtensionMode extension_mode_x = this->get_extension_mode_x();
208 const ExtensionMode extension_mode_y = this->get_extension_mode_y();
209
210 const int2 size = domain.size;
211 parallel_for(size, [&](const int2 texel) {
212 float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
213
214 float3 transformed_coordinates = float3x3(homography_matrix) * float3(coordinates, 1.0f);
215 /* Point is at infinity and will be zero when sampled, so early exit. */
216 if (transformed_coordinates.z == 0.0f) {
217 output.store_pixel(texel, float4(0.0f));
218 return;
219 }
220
221 float2 projected_coordinates = transformed_coordinates.xy() / transformed_coordinates.z;
222 float4 sampled_color;
223
224 if (interpolation != Interpolation::Anisotropic) {
225 sampled_color = input.sample(
226 projected_coordinates, interpolation, extension_mode_x, extension_mode_y);
227 }
228 else {
229 /* The derivatives of the projected coordinates with respect to x and y are the first and
230 * second columns respectively, divided by the z projection factor as can be shown by
231 * differentiating the above matrix multiplication with respect to x and y. Divide by the
232 * output size since sample_ewa assumes derivatives with respect to texel coordinates. */
233 float2 x_gradient = (homography_matrix[0].xy() / transformed_coordinates.z) / size.x;
234 float2 y_gradient = (homography_matrix[1].xy() / transformed_coordinates.z) / size.y;
235 sampled_color = input.sample_ewa_extended(projected_coordinates, x_gradient, y_gradient);
236 }
237
238 float4 plane_color = plane_mask ? sampled_color * plane_mask->load_pixel<float>(texel) :
239 sampled_color;
240
241 output.store_pixel(texel, plane_color);
242 });
243 }
244
245 Result compute_plane_mask(const float3x3 &homography_matrix)
246 {
247 if (this->context().use_gpu()) {
248 return this->compute_plane_mask_gpu(homography_matrix);
249 }
250
251 return this->compute_plane_mask_cpu(homography_matrix);
252 }
253
254 Result compute_plane_mask_gpu(const float3x3 &homography_matrix)
255 {
256 const bool is_x_clipped = this->get_extension_mode_x() == ExtensionMode::Clip;
257 const bool is_y_clipped = this->get_extension_mode_y() == ExtensionMode::Clip;
258
259 gpu::Shader *shader = context().get_shader("compositor_plane_deform_mask");
260 GPU_shader_bind(shader);
261
262 GPU_shader_uniform_mat3_as_mat4(shader, "homography_matrix", homography_matrix.ptr());
263 GPU_shader_uniform_1b(shader, "is_x_clipped", is_x_clipped);
264 GPU_shader_uniform_1b(shader, "is_y_clipped", is_y_clipped);
265
266 const Domain domain = compute_domain();
268 plane_mask.allocate_texture(domain);
269 plane_mask.bind_as_image(shader, "mask_img");
270
272
273 plane_mask.unbind_as_image();
275
276 return plane_mask;
277 }
278
279 Result compute_plane_mask_cpu(const float3x3 &homography_matrix)
280 {
281 const bool is_x_clipped = this->get_extension_mode_x() == ExtensionMode::Clip;
282 const bool is_y_clipped = this->get_extension_mode_y() == ExtensionMode::Clip;
283 const Domain domain = compute_domain();
285 plane_mask.allocate_texture(domain);
286
287 const int2 size = domain.size;
288 parallel_for(size, [&](const int2 texel) {
289 float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
290
291 float3 transformed_coordinates = float3x3(homography_matrix) * float3(coordinates, 1.0f);
292 /* Point is at infinity and will be zero when sampled, so early exit. */
293 if (transformed_coordinates.z == 0.0f) {
294 plane_mask.store_pixel(texel, 0.0f);
295 return;
296 }
297 float2 projected_coordinates = transformed_coordinates.xy() / transformed_coordinates.z;
298 bool is_inside_plane_x = projected_coordinates.x >= 0.0f && projected_coordinates.x <= 1.0f;
299 bool is_inside_plane_y = projected_coordinates.y >= 0.0f && projected_coordinates.y <= 1.0f;
300
301 /* If not inside the plane and not clipped, use extend or repeat extension mode for the
302 * mask. */
303 bool is_x_masked = is_inside_plane_x || !is_x_clipped;
304 bool is_y_masked = is_inside_plane_y || !is_y_clipped;
305 float mask_value = is_x_masked && is_y_masked ? 1.0f : 0.0f;
306
307 plane_mask.store_pixel(texel, mask_value);
308 });
309
310 return plane_mask;
311 }
312
314 {
315 float2 lower_left = get_input("Lower Left").get_single_value_default(float2(0.0f));
316 float2 lower_right = get_input("Lower Right").get_single_value_default(float2(0.0f));
317 float2 upper_right = get_input("Upper Right").get_single_value_default(float2(0.0f));
318 float2 upper_left = get_input("Upper Left").get_single_value_default(float2(0.0f));
319
320 /* The inputs are invalid because the plane is not convex, fall back to an identity operation
321 * in that case. */
322 if (!is_quad_convex_v2(lower_left, lower_right, upper_right, upper_left)) {
323 return float3x3::identity();
324 }
325
326 /* Compute a 2D projection matrix that projects from the corners of the image in normalized
327 * coordinates into the corners of the input plane. */
328 float3x3 homography_matrix;
329 float corners[4][2] = {{lower_left.x, lower_left.y},
330 {lower_right.x, lower_right.y},
331 {upper_right.x, upper_right.y},
332 {upper_left.x, upper_left.y}};
333 float identity_corners[4][2] = {{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}};
334 BKE_tracking_homography_between_two_quads(corners, identity_corners, homography_matrix.ptr());
335 return homography_matrix;
336 }
337
339 {
340 const Result &input = this->get_input("Interpolation");
341 const MenuValue default_menu_value = MenuValue(CMP_NODE_INTERPOLATION_BILINEAR);
342 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
343 const CMPNodeInterpolation interpolation = static_cast<CMPNodeInterpolation>(menu_value.value);
344 switch (interpolation) {
353 }
354
356 }
357
359 {
361 return ExtensionMode::Clip;
362 }
363
364 const Result &input = this->get_input("Extension X");
365 const MenuValue default_menu_value = MenuValue(CMP_NODE_EXTENSION_MODE_CLIP);
366 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
367 const CMPExtensionMode extension_x = static_cast<CMPExtensionMode>(menu_value.value);
368 switch (extension_x) {
370 return ExtensionMode::Clip;
375 }
376
377 return ExtensionMode::Clip;
378 }
379
381 {
383 return ExtensionMode::Clip;
384 }
385
386 const Result &input = this->get_input("Extension Y");
387 const MenuValue default_menu_value = MenuValue(CMP_NODE_EXTENSION_MODE_CLIP);
388 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
389 const CMPExtensionMode extension_y = static_cast<CMPExtensionMode>(menu_value.value);
390 switch (extension_y) {
392 return ExtensionMode::Clip;
397 }
398
399 return ExtensionMode::Clip;
400 }
401
402 const char *get_shader_name()
403 {
404 if (this->should_compute_mask()) {
405 switch (this->get_interpolation()) {
408 return "compositor_plane_deform_masked";
410 return "compositor_plane_deform_bicubic_masked";
412 return "compositor_plane_deform_anisotropic_masked";
413 }
414 }
415
416 switch (this->get_interpolation()) {
419 return "compositor_plane_deform";
421 return "compositor_plane_deform_bicubic";
422 /* Anisotropic does not implement extension modes. Return masked shader. */
424 break;
425 }
426
427 return "compositor_plane_deform_anisotropic_masked";
428 }
429
431 {
432 Result &output_mask = this->get_result("Plane");
433 const bool is_clipped_x = this->get_extension_mode_x() == ExtensionMode::Clip;
434 const bool is_clipped_y = this->get_extension_mode_y() == ExtensionMode::Clip;
435 const bool output_needed = output_mask.should_compute();
436 const bool use_anisotropic = this->get_interpolation() == Interpolation::Anisotropic;
437
438 return is_clipped_x || is_clipped_y || output_needed || use_anisotropic;
439 }
440
442 {
443 Domain domain = this->get_input("Image").domain();
444 /* Reset the location of the domain such that translations take effect, this will result in
445 * clipping but is more expected for the user. */
446 domain.transformation.location() = float2(0.0f);
447 return domain;
448 }
449};
450
452{
453 return new CornerPinOperation(context, node);
454}
455
456} // namespace blender::nodes::node_composite_cornerpin_cc
457
459{
461
462 static blender::bke::bNodeType ntype;
463
464 cmp_node_type_base(&ntype, "CompositorNodeCornerPin", CMP_NODE_CORNERPIN);
465 ntype.ui_name = "Corner Pin";
466 ntype.ui_description = "Plane warp transformation using explicit corner values";
467 ntype.enum_name_legacy = "CORNERPIN";
469 ntype.declare = file_ns::cmp_node_cornerpin_declare;
470 ntype.initfunc = file_ns::node_composit_init_cornerpin;
471 ntype.get_compositor_operation = file_ns::get_compositor_operation;
473 ntype, "NodeCornerPinData", node_free_standard_storage, node_copy_standard_storage);
475}
#define NODE_CLASS_DISTORT
Definition BKE_node.hh:455
#define CMP_NODE_CORNERPIN
void BKE_tracking_homography_between_two_quads(float reference_corners[4][2], float corners[4][2], float H[3][3])
bool is_quad_convex_v2(const float v1[2], const float v2[2], const float v3[2], const float v4[2])
#define ELEM(...)
CMPNodeInterpolation
@ CMP_NODE_INTERPOLATION_NEAREST
@ CMP_NODE_INTERPOLATION_BILINEAR
@ CMP_NODE_INTERPOLATION_BICUBIC
@ CMP_NODE_INTERPOLATION_ANISOTROPIC
CMPExtensionMode
@ CMP_NODE_EXTENSION_MODE_EXTEND
@ CMP_NODE_EXTENSION_MODE_CLIP
@ CMP_NODE_EXTENSION_MODE_REPEAT
void GPU_shader_uniform_1b(blender::gpu::Shader *sh, const char *name, bool value)
void GPU_shader_uniform_mat3_as_mat4(blender::gpu::Shader *sh, const char *name, const float data[3][3])
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_unbind()
void GPU_texture_extend_mode_y(blender::gpu::Texture *texture, GPUSamplerExtendMode extend_mode)
void GPU_texture_extend_mode_x(blender::gpu::Texture *texture, GPUSamplerExtendMode extend_mode)
void GPU_texture_anisotropic_filter(blender::gpu::Texture *texture, bool use_aniso)
void GPU_texture_mipmap_mode(blender::gpu::Texture *texture, bool use_mipmap, bool use_filter)
void GPU_texture_filter_mode(blender::gpu::Texture *texture, bool use_filter)
Read Guarded memory(de)allocation.
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_FACTOR
Definition RNA_types.hh:251
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
T get_single_value_default(const T &default_value) const
void store_pixel(const int2 &texel, const T &pixel_value)
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 set_single_value(const T &value)
void bind_as_texture(gpu::Shader *shader, const char *texture_name) const
Definition result.cc:487
const Domain & domain() const
T load_pixel(const int2 &texel) const
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
void steal_data(Result &source)
Definition result.cc:540
DeclType::Builder & add_input(StringRef name, StringRef identifier="")
void compute_plane_cpu(const float3x3 &homography_matrix, Result *plane_mask)
void compute_plane_gpu(const float3x3 &homography_matrix, Result *plane_mask)
void compute_plane(const float3x3 &homography_matrix, Result *plane_mask)
#define input
#define output
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 compute_dispatch_threads_at_least(gpu::Shader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:196
GPUSamplerExtendMode map_extension_mode_to_extend_mode(const ExtensionMode &mode)
Definition domain.cc:70
void smaa(Context &context, const Result &input, Result &output, const float threshold=0.1f, const float local_contrast_adaptation_factor=2.0f, const int corner_rounding=25)
Definition smaa.cc:1646
void parallel_for(const int2 range, const Function &function)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static void node_composit_init_cornerpin(bNodeTree *, bNode *node)
static void cmp_node_cornerpin_declare(NodeDeclarationBuilder &b)
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
static void register_node_type_cmp_cornerpin()
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
const EnumPropertyItem rna_enum_node_compositor_extension_items[]
const EnumPropertyItem rna_enum_node_compositor_interpolation_items[]
#define min(a, b)
Definition sort.cc:36
void * storage
const c_style_mat & ptr() const
VecBase< T, 2 > xy() const
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