Blender V4.5
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 "BKE_node.hh"
10#include "BLI_math_geom.h"
13
14#include "GPU_shader.hh"
15#include "GPU_texture.hh"
16
17#include "BKE_tracking.h"
18
19#include "UI_interface.hh"
20
21#include "COM_algorithm_smaa.hh"
22#include "COM_node_operation.hh"
23#include "COM_utilities.hh"
24
26
28
30{
31 b.add_input<decl::Color>("Image")
32 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
33 .compositor_domain_priority(0);
34 b.add_input<decl::Vector>("Upper Left")
35 .subtype(PROP_FACTOR)
36 .dimensions(2)
37 .default_value({0.0f, 1.0f})
38 .min(0.0f)
39 .max(1.0f)
40 .compositor_expects_single_value();
41 b.add_input<decl::Vector>("Upper Right")
42 .subtype(PROP_FACTOR)
43 .dimensions(2)
44 .default_value({1.0f, 1.0f})
45 .min(0.0f)
46 .max(1.0f)
47 .compositor_expects_single_value();
48 b.add_input<decl::Vector>("Lower Left")
49 .subtype(PROP_FACTOR)
50 .dimensions(2)
51 .default_value({0.0f, 0.0f})
52 .min(0.0f)
53 .max(1.0f)
54 .compositor_expects_single_value();
55 b.add_input<decl::Vector>("Lower Right")
56 .subtype(PROP_FACTOR)
57 .dimensions(2)
58 .default_value({1.0f, 0.0f})
59 .min(0.0f)
60 .max(1.0f)
61 .compositor_expects_single_value();
62
63 b.add_output<decl::Color>("Image");
64 b.add_output<decl::Float>("Plane");
65}
66
71
73{
74 layout->prop(ptr, "interpolation", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
75}
76
77using namespace blender::compositor;
78
80 public:
82
83 void execute() override
84 {
85 const float3x3 homography_matrix = compute_homography_matrix();
86
87 const Result &input_image = this->get_input("Image");
88 Result &output_image = this->get_result("Image");
89 Result &output_mask = this->get_result("Plane");
90 if (input_image.is_single_value() || homography_matrix == float3x3::identity()) {
91 if (output_image.should_compute()) {
92 output_image.share_data(input_image);
93 }
94 if (output_mask.should_compute()) {
95 output_mask.allocate_single_value();
96 output_mask.set_single_value(1.0f);
97 }
98 return;
99 }
100
101 Result plane_mask = compute_plane_mask(homography_matrix);
102 Result anti_aliased_plane_mask = context().create_result(ResultType::Float);
103 smaa(context(), plane_mask, anti_aliased_plane_mask);
104 plane_mask.release();
105
106 if (output_image.should_compute()) {
107 compute_plane(homography_matrix, anti_aliased_plane_mask);
108 }
109
110 if (output_mask.should_compute()) {
111 output_mask.steal_data(anti_aliased_plane_mask);
112 }
113 else {
114 anti_aliased_plane_mask.release();
115 }
116 }
117
118 void compute_plane(const float3x3 &homography_matrix, Result &plane_mask)
119 {
120 if (this->context().use_gpu()) {
121 this->compute_plane_gpu(homography_matrix, plane_mask);
122 }
123 else {
124 this->compute_plane_cpu(homography_matrix, plane_mask);
125 }
126 }
127
128 void compute_plane_gpu(const float3x3 &homography_matrix, Result &plane_mask)
129 {
130 GPUShader *shader = this->context().get_shader(this->get_realization_shader_name());
131 GPU_shader_bind(shader);
132
133 GPU_shader_uniform_mat3_as_mat4(shader, "homography_matrix", homography_matrix.ptr());
134
135 Result &input_image = get_input("Image");
136 GPU_texture_mipmap_mode(input_image, true, true);
137 /* The texture sampler should use bilinear interpolation for both the bilinear and bicubic
138 * cases, as the logic used by the bicubic realization shader expects textures to use bilinear
139 * interpolation. */
140 const CMPNodeCornerPinInterpolation interpolation = this->get_interpolation();
141 const bool use_bilinear = ELEM(interpolation,
144 const bool use_anisotropic = interpolation == CMP_NODE_CORNER_PIN_INTERPOLATION_ANISOTROPIC;
145 GPU_texture_filter_mode(input_image, use_bilinear);
146 GPU_texture_anisotropic_filter(input_image, use_anisotropic);
148 input_image.bind_as_texture(shader, "input_tx");
149
150 plane_mask.bind_as_texture(shader, "mask_tx");
151
152 const Domain domain = compute_domain();
153 Result &output_image = get_result("Image");
154 output_image.allocate_texture(domain);
155 output_image.bind_as_image(shader, "output_img");
156
158
159 input_image.unbind_as_texture();
160 plane_mask.unbind_as_texture();
161 output_image.unbind_as_image();
163 }
164
165 void compute_plane_cpu(const float3x3 &homography_matrix, Result &plane_mask)
166 {
167 Result &input = get_input("Image");
168
169 const Domain domain = compute_domain();
170 Result &output = get_result("Image");
171 output.allocate_texture(domain);
172
173 const int2 size = domain.size;
174 parallel_for(size, [&](const int2 texel) {
175 float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
176
177 float3 transformed_coordinates = float3x3(homography_matrix) * float3(coordinates, 1.0f);
178 /* Point is at infinity and will be zero when sampled, so early exit. */
179 if (transformed_coordinates.z == 0.0f) {
180 output.store_pixel(texel, float4(0.0f));
181 return;
182 }
183
184 float2 projected_coordinates = transformed_coordinates.xy() / transformed_coordinates.z;
185
186 float4 sampled_color;
187 switch (this->get_interpolation()) {
189 sampled_color = input.sample_cubic_extended(projected_coordinates);
190 break;
192 sampled_color = input.sample_bilinear_extended(projected_coordinates);
193 break;
195 sampled_color = input.sample_nearest_extended(projected_coordinates);
196 break;
198 /* The derivatives of the projected coordinates with respect to x and y are the first and
199 * second columns respectively, divided by the z projection factor as can be shown by
200 * differentiating the above matrix multiplication with respect to x and y. Divide by the
201 * output size since sample_ewa assumes derivatives with respect to texel coordinates. */
202 float2 x_gradient = (homography_matrix[0].xy() / transformed_coordinates.z) / size.x;
203 float2 y_gradient = (homography_matrix[1].xy() / transformed_coordinates.z) / size.y;
204 sampled_color = input.sample_ewa_extended(projected_coordinates, x_gradient, y_gradient);
205 break;
206 }
207
208 /* Premultiply the mask value as an alpha. */
209 float4 plane_color = sampled_color * plane_mask.load_pixel<float>(texel);
210
211 output.store_pixel(texel, plane_color);
212 });
213 }
214
215 Result compute_plane_mask(const float3x3 &homography_matrix)
216 {
217 if (this->context().use_gpu()) {
218 return this->compute_plane_mask_gpu(homography_matrix);
219 }
220
221 return this->compute_plane_mask_cpu(homography_matrix);
222 }
223
224 Result compute_plane_mask_gpu(const float3x3 &homography_matrix)
225 {
226 GPUShader *shader = context().get_shader("compositor_plane_deform_mask");
227 GPU_shader_bind(shader);
228
229 GPU_shader_uniform_mat3_as_mat4(shader, "homography_matrix", homography_matrix.ptr());
230
231 const Domain domain = compute_domain();
233 plane_mask.allocate_texture(domain);
234 plane_mask.bind_as_image(shader, "mask_img");
235
237
238 plane_mask.unbind_as_image();
240
241 return plane_mask;
242 }
243
244 Result compute_plane_mask_cpu(const float3x3 &homography_matrix)
245 {
246 const Domain domain = compute_domain();
248 plane_mask.allocate_texture(domain);
249
250 const int2 size = domain.size;
251 parallel_for(size, [&](const int2 texel) {
252 float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
253
254 float3 transformed_coordinates = float3x3(homography_matrix) * float3(coordinates, 1.0f);
255 /* Point is at infinity and will be zero when sampled, so early exit. */
256 if (transformed_coordinates.z == 0.0f) {
257 plane_mask.store_pixel(texel, 0.0f);
258 return;
259 }
260 float2 projected_coordinates = transformed_coordinates.xy() / transformed_coordinates.z;
261
262 bool is_inside_plane = projected_coordinates.x >= 0.0f && projected_coordinates.y >= 0.0f &&
263 projected_coordinates.x <= 1.0f && projected_coordinates.y <= 1.0f;
264 float mask_value = is_inside_plane ? 1.0f : 0.0f;
265
266 plane_mask.store_pixel(texel, mask_value);
267 });
268
269 return plane_mask;
270 }
271
273 {
274 float2 lower_left = get_input("Lower Left").get_single_value_default(float3(0.0f)).xy();
275 float2 lower_right = get_input("Lower Right").get_single_value_default(float3(0.0f)).xy();
276 float2 upper_right = get_input("Upper Right").get_single_value_default(float3(0.0f)).xy();
277 float2 upper_left = get_input("Upper Left").get_single_value_default(float3(0.0f)).xy();
278
279 /* The inputs are invalid because the plane is not convex, fall back to an identity operation
280 * in that case. */
281 if (!is_quad_convex_v2(lower_left, lower_right, upper_right, upper_left)) {
282 return float3x3::identity();
283 }
284
285 /* Compute a 2D projection matrix that projects from the corners of the image in normalized
286 * coordinates into the corners of the input plane. */
287 float3x3 homography_matrix;
288 float corners[4][2] = {{lower_left.x, lower_left.y},
289 {lower_right.x, lower_right.y},
290 {upper_right.x, upper_right.y},
291 {upper_left.x, upper_left.y}};
292 float identity_corners[4][2] = {{0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f}};
293 BKE_tracking_homography_between_two_quads(corners, identity_corners, homography_matrix.ptr());
294 return homography_matrix;
295 }
296
297 const char *get_realization_shader_name() const
298 {
299 switch (this->get_interpolation()) {
302 return "compositor_plane_deform";
304 return "compositor_plane_deform_bicubic";
306 return "compositor_plane_deform_anisotropic";
307 }
308
310 return "compositor_plane_deform_anisotropic";
311 }
312
317};
318
320{
321 return new CornerPinOperation(context, node);
322}
323
324} // namespace blender::nodes::node_composite_cornerpin_cc
325
327{
329
330 static blender::bke::bNodeType ntype;
331
332 cmp_node_type_base(&ntype, "CompositorNodeCornerPin", CMP_NODE_CORNERPIN);
333 ntype.ui_name = "Corner Pin";
334 ntype.ui_description = "Plane warp transformation using explicit corner values";
335 ntype.enum_name_legacy = "CORNERPIN";
337 ntype.declare = file_ns::cmp_node_cornerpin_declare;
338 ntype.initfunc = file_ns::node_composit_init_cornerpin;
339 ntype.draw_buttons = file_ns::node_composit_buts_cornerpin;
340 ntype.get_compositor_operation = file_ns::get_compositor_operation;
341
343}
#define NODE_CLASS_DISTORT
Definition BKE_node.hh:441
#define CMP_NODE_CORNERPIN
void BKE_tracking_homography_between_two_quads(float reference_corners[4][2], float corners[4][2], float H[3][3])
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
bool is_quad_convex_v2(const float v1[2], const float v2[2], const float v3[2], const float v4[2])
#define ELEM(...)
CMPNodeCornerPinInterpolation
@ CMP_NODE_CORNER_PIN_INTERPOLATION_BILINEAR
@ CMP_NODE_CORNER_PIN_INTERPOLATION_BICUBIC
@ CMP_NODE_CORNER_PIN_INTERPOLATION_ANISOTROPIC
@ CMP_NODE_CORNER_PIN_INTERPOLATION_NEAREST
void GPU_shader_bind(GPUShader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_uniform_mat3_as_mat4(GPUShader *sh, const char *name, const float data[3][3])
void GPU_shader_unbind()
void GPU_texture_anisotropic_filter(GPUTexture *texture, bool use_aniso)
void GPU_texture_extend_mode(GPUTexture *texture, GPUSamplerExtendMode extend_mode)
@ GPU_SAMPLER_EXTEND_MODE_EXTEND
void GPU_texture_mipmap_mode(GPUTexture *texture, bool use_mipmap, bool use_filter)
void GPU_texture_filter_mode(GPUTexture *texture, bool use_filter)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_FACTOR
Definition RNA_types.hh:239
@ UI_ITEM_R_SPLIT_EMPTY_NAME
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
Result create_result(ResultType type, ResultPrecision precision)
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 store_pixel(const int2 &texel, const T &pixel_value)
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 set_single_value(const T &value)
T load_pixel(const int2 &texel) const
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
void steal_data(Result &source)
Definition result.cc:418
void compute_plane_gpu(const float3x3 &homography_matrix, Result &plane_mask)
void compute_plane_cpu(const float3x3 &homography_matrix, Result &plane_mask)
void compute_plane(const float3x3 &homography_matrix, Result &plane_mask)
#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 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 node_composit_buts_cornerpin(uiLayout *layout, bContext *, PointerRNA *ptr)
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)
#define min(a, b)
Definition sort.cc:36
int16_t custom1
const c_style_mat & ptr() const
VecBase< T, 2 > xy() const
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)
PointerRNA * ptr
Definition wm_files.cc:4227