Blender V5.0
node_composite_keying.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_math_color.h"
11
12#include "DNA_scene_types.h"
13
14#include "RNA_enum_types.hh"
15
16#include "GPU_shader.hh"
17
21#include "COM_node_operation.hh"
22#include "COM_utilities.hh"
23
25
27
29{
30 b.use_custom_socket_order();
31 b.allow_any_socket_order();
32
33 b.add_input<decl::Color>("Image")
34 .default_value({0.8f, 0.8f, 0.8f, 1.0f})
35 .hide_value()
36 .structure_type(StructureType::Dynamic);
37 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
38
39 b.add_output<decl::Float>("Matte").structure_type(StructureType::Dynamic);
40 b.add_output<decl::Float>("Edges")
41 .structure_type(StructureType::Dynamic)
43
44 b.add_input<decl::Color>("Key Color")
45 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
46 .structure_type(StructureType::Dynamic);
47
48 PanelDeclarationBuilder &preprocess_panel = b.add_panel("Preprocess").default_closed(true);
49 preprocess_panel.add_input<decl::Int>("Blur Size", "Preprocess Blur Size")
50 .default_value(0)
51 .min(0)
53 "Blur the color of the input image in YCC color space before keying while leaving the "
54 "luminance intact using a Gaussian blur of the given size");
55
56 PanelDeclarationBuilder &key_panel = b.add_panel("Key").default_closed(true).translation_context(
58 key_panel.add_input<decl::Float>("Balance", "Key Balance")
59 .default_value(0.5f)
61 .min(0.0f)
62 .max(1.0f)
63 .description(
64 "Balances between the two non primary color channels that the primary channel compares "
65 "against. 0 means the latter channel of the two is used, while 1 means the former of "
66 "the two is used");
67
68 PanelDeclarationBuilder &tweak_panel = b.add_panel("Tweak").default_closed(true);
69 tweak_panel.add_input<decl::Float>("Black Level")
70 .default_value(0.0f)
72 .min(0.0f)
73 .max(1.0f)
74 .description(
75 "The matte gets remapped such matte values lower than the black level become black. "
76 "Pixels at the identified edges are excluded from the remapping to preserve details");
77 tweak_panel.add_input<decl::Float>("White Level")
78 .default_value(1.0f)
80 .min(0.0f)
81 .max(1.0f)
82 .description(
83 "The matte gets remapped such matte values higher than the white level become white. "
84 "Pixels at the identified edges are excluded from the remapping to preserve details");
85
86 PanelDeclarationBuilder &edges_panel =
87 tweak_panel.add_panel("Edges").default_closed(true).translation_context(
89 edges_panel.add_input<decl::Int>("Size", "Edge Search Size")
90 .default_value(3)
91 .min(0)
93 "Size of the search window used to identify edges. Higher search size corresponds to "
94 "less noisy and higher quality edges, not necessarily bigger edges. Edge tolerance can "
95 "be used to expend the size of the edges");
96 edges_panel.add_input<decl::Float>("Tolerance", "Edge Tolerance")
97 .default_value(0.1f)
99 .min(0.0f)
100 .max(1.0f)
101 .description(
102 "Pixels are considered part of the edges if more than 10% of the neighbouring pixels "
103 "have matte values that differ from the pixel's matte value by this tolerance");
104
105 PanelDeclarationBuilder &mask_panel = b.add_panel("Mask").default_closed(true);
106 mask_panel.add_input<decl::Float>("Garbage Matte")
107 .default_value(0.0f)
109 .min(0.0f)
110 .max(1.0f)
111 .structure_type(StructureType::Dynamic)
112 .description("Areas in the garbage matte mask are excluded from the matte");
113 mask_panel.add_input<decl::Float>("Core Matte")
114 .default_value(0.0f)
116 .min(0.0f)
117 .max(1.0f)
118 .structure_type(StructureType::Dynamic)
119 .description("Areas in the core matte mask are included in the matte");
120
121 PanelDeclarationBuilder &postprocess_panel = b.add_panel("Postprocess").default_closed(true);
122 postprocess_panel.add_input<decl::Int>("Blur Size", "Postprocess Blur Size")
123 .default_value(0)
124 .min(0)
125 .description("Blur the computed matte using a Gaussian blur of the given size");
126 postprocess_panel.add_input<decl::Int>("Dilate Size", "Postprocess Dilate Size")
127 .default_value(0)
129 "Dilate or erode the computed matte using a circular structuring element of the "
130 "specified size. Negative sizes means erosion while positive means dilation");
131 postprocess_panel.add_input<decl::Int>("Feather Size", "Postprocess Feather Size")
132 .default_value(0)
134 "Dilate or erode the computed matte using an inverse distance operation evaluated at "
135 "the given falloff of the specified size. Negative sizes means erosion while positive "
136 "means dilation");
137 postprocess_panel.add_input<decl::Menu>("Feather Falloff")
138 .default_value(PROP_SMOOTH)
141 .translation_context(BLT_I18NCONTEXT_ID_CURVE_LEGACY);
142
143 PanelDeclarationBuilder &despill_panel = b.add_panel("Despill").default_closed(true);
144 despill_panel.add_input<decl::Float>("Strength", "Despill Strength")
145 .default_value(1.0f)
147 .min(0.0f)
148 .max(1.0f)
149 .description("Specifies the strength of the despill");
150 despill_panel.add_input<decl::Float>("Balance", "Despill Balance")
151 .default_value(0.5f)
153 .min(0.0f)
154 .max(1.0f)
155 .description(
156 "Defines the channel used for despill limiting. Balances between the two non primary "
157 "color channels that the primary channel compares against. 0 means the latter channel "
158 "of the two is used, while 1 means the former of the two is used");
159}
160
161static void node_composit_init_keying(bNodeTree * /*ntree*/, bNode *node)
162{
163 /* Unused, only kept for forward compatibility. */
165 node->storage = data;
166}
167
168using namespace blender::compositor;
169
171 public:
173
174 void execute() override
175 {
176 const Result &input_image = get_input("Image");
177 Result &output_image = get_result("Image");
178 Result &output_matte = get_result("Matte");
179 Result &output_edges = get_result("Edges");
180 if (input_image.is_single_value()) {
181 if (output_image.should_compute()) {
182 output_image.share_data(input_image);
183 }
184 if (output_matte.should_compute()) {
185 output_matte.allocate_invalid();
186 }
187 if (output_edges.should_compute()) {
188 output_edges.allocate_invalid();
189 }
190 return;
191 }
192
193 Result blurred_input = compute_blurred_input();
194
195 Result matte = compute_matte(blurred_input);
196 blurred_input.release();
197
198 /* This also computes the edges output if needed. */
199 Result tweaked_matte = compute_tweaked_matte(matte);
200 matte.release();
201
202 if (output_image.should_compute() || output_matte.should_compute()) {
203 Result blurred_matte = compute_blurred_matte(tweaked_matte);
204 tweaked_matte.release();
205
206 Result morphed_matte = compute_morphed_matte(blurred_matte);
207 blurred_matte.release();
208
209 Result feathered_matte = compute_feathered_matte(morphed_matte);
210 morphed_matte.release();
211
212 if (output_image.should_compute()) {
213 compute_image(feathered_matte);
214 }
215
216 if (output_matte.should_compute()) {
217 output_matte.steal_data(feathered_matte);
218 }
219 else {
220 feathered_matte.release();
221 }
222 }
223 else {
224 tweaked_matte.release();
225 }
226 }
227
229 {
230 /* No blur needed, return the original matte. We also increment the reference count of the
231 * input because the caller will release it after the call, and we want to extend its life
232 * since it is now returned as the output. */
233 const float blur_size = this->get_preprocess_blur_size();
234 if (blur_size == 0.0f) {
235 Result output = get_input("Image");
236 output.increment_reference_count();
237 return output;
238 }
239
240 Result chroma = extract_input_chroma();
241
242 Result blurred_chroma = context().create_result(ResultType::Color);
244 context(), chroma, blurred_chroma, float2(blur_size) / 2, R_FILTER_BOX);
245 chroma.release();
246
247 Result blurred_input = replace_input_chroma(blurred_chroma);
248 blurred_chroma.release();
249
250 return blurred_input;
251 }
252
254 {
255 return math::max(0, this->get_input("Preprocess Blur Size").get_single_value_default(0));
256 }
257
259 {
260 if (this->context().use_gpu()) {
261 return this->extract_input_chroma_gpu();
262 }
263 return this->extract_input_chroma_cpu();
264 }
265
267 {
268 gpu::Shader *shader = context().get_shader("compositor_keying_extract_chroma");
269 GPU_shader_bind(shader);
270
271 Result &input = get_input("Image");
272 input.bind_as_texture(shader, "input_tx");
273
275 output.allocate_texture(input.domain());
276 output.bind_as_image(shader, "output_img");
277
278 compute_dispatch_threads_at_least(shader, input.domain().size);
279
281 input.unbind_as_texture();
282 output.unbind_as_image();
283
284 return output;
285 }
286
288 {
289 Result &input = get_input("Image");
290
292 output.allocate_texture(input.domain());
293
294 parallel_for(input.domain().size, [&](const int2 texel) {
295 const float4 color = input.load_pixel<float4>(texel);
296 float4 color_ycca;
297 rgb_to_ycc(color.x,
298 color.y,
299 color.z,
300 &color_ycca.x,
301 &color_ycca.y,
302 &color_ycca.z,
303 BLI_YCC_ITU_BT709);
304 color_ycca /= 255.0f;
305 color_ycca.w = color.w;
306
307 output.store_pixel(texel, color_ycca);
308 });
309
310 return output;
311 }
312
314 {
315 if (this->context().use_gpu()) {
316 return this->replace_input_chroma_gpu(new_chroma);
317 }
318 return this->replace_input_chroma_cpu(new_chroma);
319 }
320
322 {
323 gpu::Shader *shader = context().get_shader("compositor_keying_replace_chroma");
324 GPU_shader_bind(shader);
325
326 Result &input = get_input("Image");
327 input.bind_as_texture(shader, "input_tx");
328
329 new_chroma.bind_as_texture(shader, "new_chroma_tx");
330
332 output.allocate_texture(input.domain());
333 output.bind_as_image(shader, "output_img");
334
335 compute_dispatch_threads_at_least(shader, input.domain().size);
336
338 input.unbind_as_texture();
339 new_chroma.unbind_as_texture();
340 output.unbind_as_image();
341
342 return output;
343 }
344
346 {
347 Result &input = get_input("Image");
348
350 output.allocate_texture(input.domain());
351
352 parallel_for(input.domain().size, [&](const int2 texel) {
353 const float4 color = input.load_pixel<float4>(texel);
354 float4 color_ycca;
355 rgb_to_ycc(color.x,
356 color.y,
357 color.z,
358 &color_ycca.x,
359 &color_ycca.y,
360 &color_ycca.z,
361 BLI_YCC_ITU_BT709);
362
363 const float2 new_chroma_cb_cr = new_chroma.load_pixel<float4>(texel).yz();
364 color_ycca.y = new_chroma_cb_cr.x * 255.0f;
365 color_ycca.z = new_chroma_cb_cr.y * 255.0f;
366
367 float4 color_rgba;
368 ycc_to_rgb(color_ycca.x,
369 color_ycca.y,
370 color_ycca.z,
371 &color_rgba.x,
372 &color_rgba.y,
373 &color_rgba.z,
374 BLI_YCC_ITU_BT709);
375 color_rgba.w = color.w;
376
377 output.store_pixel(texel, color_rgba);
378 });
379
380 return output;
381 }
382
384 {
385 if (this->context().use_gpu()) {
386 return this->compute_matte_gpu(input);
387 }
388 return this->compute_matte_cpu(input);
389 }
390
392 {
393 gpu::Shader *shader = context().get_shader("compositor_keying_compute_matte");
394 GPU_shader_bind(shader);
395
396 GPU_shader_uniform_1f(shader, "key_balance", this->get_key_balance());
397
398 input.bind_as_texture(shader, "input_tx");
399
400 Result &key_color = get_input("Key Color");
401 key_color.bind_as_texture(shader, "key_tx");
402
404 output.allocate_texture(input.domain());
405 output.bind_as_image(shader, "output_img");
406
407 compute_dispatch_threads_at_least(shader, input.domain().size);
408
410 input.unbind_as_texture();
411 key_color.unbind_as_texture();
412 output.unbind_as_image();
413
414 return output;
415 }
416
418 {
419 const float key_balance = this->get_key_balance();
420
421 Result &key = get_input("Key Color");
422
424 output.allocate_texture(input.domain());
425
426 auto compute_saturation_indices = [](const float3 &v) {
427 int index_of_max = ((v.x > v.y) ? ((v.x > v.z) ? 0 : 2) : ((v.y > v.z) ? 1 : 2));
428 int2 other_indices = (int2(index_of_max) + int2(1, 2)) % 3;
429 int min_index = math::min(other_indices.x, other_indices.y);
430 int max_index = math::max(other_indices.x, other_indices.y);
431 return int3(index_of_max, max_index, min_index);
432 };
433
434 auto compute_saturation = [&](const float4 &color, const int3 &indices) {
435 float weighted_average = math::interpolate(color[indices.y], color[indices.z], key_balance);
436 return (color[indices.x] - weighted_average) * math::abs(1.0f - weighted_average);
437 };
438
439 parallel_for(input.domain().size, [&](const int2 texel) {
440 float4 input_color = input.load_pixel<float4>(texel);
441
442 /* We assume that the keying screen will not be overexposed in the image, so if the input
443 * brightness is high, we assume the pixel is opaque. */
444 if (math::reduce_min(input_color) > 1.0f) {
445 output.store_pixel(texel, 1.0f);
446 return;
447 }
448
449 float4 key_color = key.load_pixel<float4, true>(texel);
450 int3 key_saturation_indices = compute_saturation_indices(key_color.xyz());
451 float input_saturation = compute_saturation(input_color, key_saturation_indices);
452 float key_saturation = compute_saturation(key_color, key_saturation_indices);
453
454 float matte;
455 if (input_saturation < 0.0f) {
456 /* Means main channel of pixel is different from screen, assume this is completely a
457 * foreground. */
458 matte = 1.0f;
459 }
460 else if (input_saturation >= key_saturation) {
461 /* Matched main channels and higher saturation on pixel is treated as completely
462 * background. */
463 matte = 0.0f;
464 }
465 else {
466 matte = 1.0f - math::clamp(input_saturation / key_saturation, 0.0f, 1.0f);
467 }
468
469 output.store_pixel(texel, matte);
470 });
471
472 return output;
473 }
474
476 {
477 return math::clamp(this->get_input("Key Balance").get_single_value_default(0.5f), 0.0f, 1.0f);
478 }
479
481 {
482 const float black_level = this->get_black_level();
483 const float white_level = this->get_black_level();
484 const Result &garbage_matte = this->get_input("Garbage Matte");
485 const Result &core_matte = this->get_input("Core Matte");
486
487 /* The edges output is not needed and the matte is not tweaked, so return the original matte.
488 * We also increment the reference count of the input because the caller will release it after
489 * the call, and we want to extend its life since it is now returned as the output. */
490 Result &output_edges = get_result("Edges");
491 if (!output_edges.should_compute() && black_level == 0.0f && white_level == 1.0f &&
492 core_matte.get_single_value_default(1.0f) == 0.0f &&
493 garbage_matte.get_single_value_default(1.0f) == 0.0f)
494 {
495 Result output_matte = input_matte;
496 input_matte.increment_reference_count();
497 return output_matte;
498 }
499
500 if (this->context().use_gpu()) {
501 return this->compute_tweaked_matte_gpu(input_matte);
502 }
503 return this->compute_tweaked_matte_cpu(input_matte);
504 }
505
507 {
508 gpu::Shader *shader = context().get_shader(this->get_tweak_matte_shader_name());
509 GPU_shader_bind(shader);
510
511 GPU_shader_uniform_1i(shader, "edge_search_radius", this->get_edge_search_size());
512 GPU_shader_uniform_1f(shader, "edge_tolerance", this->get_edge_tolerance());
513 GPU_shader_uniform_1f(shader, "black_level", this->get_black_level());
514 GPU_shader_uniform_1f(shader, "white_level", this->get_white_level());
515
516 input_matte.bind_as_texture(shader, "input_matte_tx");
517
518 Result &garbage_matte = get_input("Garbage Matte");
519 garbage_matte.bind_as_texture(shader, "garbage_matte_tx");
520
521 Result &core_matte = get_input("Core Matte");
522 core_matte.bind_as_texture(shader, "core_matte_tx");
523
524 Result output_matte = context().create_result(ResultType::Float);
525 output_matte.allocate_texture(input_matte.domain());
526 output_matte.bind_as_image(shader, "output_matte_img");
527
528 Result &output_edges = get_result("Edges");
529 if (output_edges.should_compute()) {
530 output_edges.allocate_texture(input_matte.domain());
531 output_edges.bind_as_image(shader, "output_edges_img");
532 }
533
534 compute_dispatch_threads_at_least(shader, input_matte.domain().size);
535
537 input_matte.unbind_as_texture();
538 garbage_matte.unbind_as_texture();
539 core_matte.unbind_as_texture();
540 output_matte.unbind_as_image();
541 if (output_edges.should_compute()) {
542 output_edges.unbind_as_image();
543 }
544
545 return output_matte;
546 }
547
549 {
550 Result &output_edges = get_result("Edges");
551 return output_edges.should_compute() ? "compositor_keying_tweak_matte_with_edges" :
552 "compositor_keying_tweak_matte_without_edges";
553 }
554
556 {
557 const int edge_search_radius = this->get_edge_search_size();
558 const float edge_tolerance = this->get_edge_tolerance();
559 const float black_level = this->get_black_level();
560 const float white_level = this->get_white_level();
561
562 Result &garbage_matte_image = get_input("Garbage Matte");
563 Result &core_matte_image = get_input("Core Matte");
564
565 Result output_matte = context().create_result(ResultType::Float);
566 output_matte.allocate_texture(input_matte.domain());
567
568 Result &output_edges = this->get_result("Edges");
569 const bool compute_edges = output_edges.should_compute();
570 if (compute_edges) {
571 output_edges.allocate_texture(input_matte.domain());
572 }
573
574 parallel_for(input_matte.domain().size, [&](const int2 texel) {
575 float matte = input_matte.load_pixel<float>(texel);
576
577 /* Search the neighborhood around the current matte value and identify if it lies along the
578 * edges of the matte. This is needs to be computed only when we need to compute the edges
579 * output or tweak the levels of the matte. */
580 bool is_edge = false;
581 if (compute_edges || black_level != 0.0f || white_level != 1.0f) {
582 /* Count the number of neighbors whose matte is sufficiently similar to the current matte,
583 * as controlled by the edge_tolerance factor. */
584 int count = 0;
585 for (int j = -edge_search_radius; j <= edge_search_radius; j++) {
586 for (int i = -edge_search_radius; i <= edge_search_radius; i++) {
587 float neighbor_matte = input_matte.load_pixel_extended<float>(texel + int2(i, j));
588 count += int(math::distance(matte, neighbor_matte) < edge_tolerance);
589 }
590 }
591
592 /* If the number of neighbors that are sufficiently similar to the center matte is less
593 * that 90% of the total number of neighbors, then that means the variance is high in that
594 * areas and it is considered an edge. */
595 is_edge = count < ((edge_search_radius * 2 + 1) * (edge_search_radius * 2 + 1)) * 0.9f;
596 }
597
598 float tweaked_matte = matte;
599
600 /* Remap the matte using the black and white levels, but only for areas that are not on the
601 * edge of the matte to preserve details. Also check for equality between levels to avoid
602 * zero division. */
603 if (!is_edge && white_level != black_level) {
604 tweaked_matte = math::clamp(
605 (matte - black_level) / (white_level - black_level), 0.0f, 1.0f);
606 }
607
608 /* Exclude unwanted areas using the provided garbage matte, 1 means unwanted, so invert the
609 * garbage matte and take the minimum. */
610 float garbage_matte = garbage_matte_image.load_pixel<float, true>(texel);
611 tweaked_matte = math::min(tweaked_matte, 1.0f - garbage_matte);
612
613 /* Include wanted areas that were incorrectly keyed using the provided core matte. */
614 float core_matte = core_matte_image.load_pixel<float, true>(texel);
615 tweaked_matte = math::max(tweaked_matte, core_matte);
616
617 output_matte.store_pixel(texel, tweaked_matte);
618 if (compute_edges) {
619 output_edges.store_pixel(texel, is_edge ? 1.0f : 0.0f);
620 }
621 });
622
623 return output_matte;
624 }
625
627 {
628 return math::max(0, this->get_input("Edge Search Size").get_single_value_default(3));
629 }
630
632 {
633 return math::clamp(
634 this->get_input("Edge Tolerance").get_single_value_default(0.1f), 0.0f, 1.0f);
635 }
636
638 {
639 return math::clamp(this->get_input("Black Level").get_single_value_default(0.0f), 0.0f, 1.0f);
640 }
641
643 {
644 return math::clamp(this->get_input("White Level").get_single_value_default(1.0f), 0.0f, 1.0f);
645 }
646
648 {
649 const float blur_size = this->get_postprocess_blur_size();
650 /* No blur needed, return the original matte. We also increment the reference count of the
651 * input because the caller will release it after the call, and we want to extend its life
652 * since it is now returned as the output. */
653 if (blur_size == 0.0f) {
654 Result output_matte = input_matte;
655 input_matte.increment_reference_count();
656 return output_matte;
657 }
658
659 Result blurred_matte = context().create_result(ResultType::Float);
661 context(), input_matte, blurred_matte, float2(blur_size) / 2, R_FILTER_BOX);
662
663 return blurred_matte;
664 }
665
667 {
668 return math::max(0, this->get_input("Postprocess Blur Size").get_single_value_default(0));
669 }
670
672 {
673 const int distance = this->get_postprocess_dilate_size();
674 /* No morphology needed, return the original matte. We also increment the reference count of
675 * the input because the caller will release it after the call, and we want to extend its life
676 * since it is now returned as the output. */
677 if (distance == 0) {
678 Result output_matte = input_matte;
679 input_matte.increment_reference_count();
680 return output_matte;
681 }
682
683 Result morphed_matte = context().create_result(ResultType::Float);
684 morphological_distance(context(), input_matte, morphed_matte, distance);
685
686 return morphed_matte;
687 }
688
690 {
691 return this->get_input("Postprocess Dilate Size").get_single_value_default(0);
692 }
693
695 {
696 const int distance = this->get_postprocess_feather_size();
697 /* No feathering needed, return the original matte. We also increment the reference count of
698 * the input because the caller will release it after the call, and we want to extend its life
699 * since it is now returned as the output. */
700 if (distance == 0) {
701 Result output_matte = input_matte;
702 input_matte.increment_reference_count();
703 return output_matte;
704 }
705
706 Result feathered_matte = context().create_result(ResultType::Float);
708 context(), input_matte, feathered_matte, distance, this->get_feather_falloff());
709
710 return feathered_matte;
711 }
712
714 {
715 return this->get_input("Postprocess Feather Size").get_single_value_default(0);
716 }
717
719 {
720 const Result &input = this->get_input("Feather Falloff");
721 const MenuValue default_menu_value = MenuValue(PROP_SMOOTH);
722 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
723 return menu_value.value;
724 }
725
727 {
728 if (this->context().use_gpu()) {
729 this->compute_image_gpu(matte);
730 }
731 else {
732 this->compute_image_cpu(matte);
733 }
734 }
735
737 {
738 gpu::Shader *shader = context().get_shader("compositor_keying_compute_image");
739 GPU_shader_bind(shader);
740
741 GPU_shader_uniform_1f(shader, "despill_factor", this->get_despill_strength());
742 GPU_shader_uniform_1f(shader, "despill_balance", this->get_despill_balance());
743
744 Result &input = get_input("Image");
745 input.bind_as_texture(shader, "input_tx");
746
747 Result &key = get_input("Key Color");
748 key.bind_as_texture(shader, "key_tx");
749
750 matte.bind_as_texture(shader, "matte_tx");
751
752 Result &output = get_result("Image");
753 output.allocate_texture(matte.domain());
754 output.bind_as_image(shader, "output_img");
755
756 compute_dispatch_threads_at_least(shader, input.domain().size);
757
759 input.unbind_as_texture();
760 key.unbind_as_texture();
761 matte.unbind_as_texture();
762 output.unbind_as_image();
763 }
764
765 void compute_image_cpu(Result &matte_image)
766 {
767 const float despill_factor = this->get_despill_strength();
768 const float despill_balance = this->get_despill_balance();
769
770 Result &input = get_input("Image");
771 Result &key = get_input("Key Color");
772
773 Result &output = get_result("Image");
774 output.allocate_texture(matte_image.domain());
775
776 auto compute_saturation_indices = [](const float3 &v) {
777 int index_of_max = ((v.x > v.y) ? ((v.x > v.z) ? 0 : 2) : ((v.y > v.z) ? 1 : 2));
778 int2 other_indices = (int2(index_of_max) + int2(1, 2)) % 3;
779 int min_index = math::min(other_indices.x, other_indices.y);
780 int max_index = math::max(other_indices.x, other_indices.y);
781 return int3(index_of_max, max_index, min_index);
782 };
783
784 parallel_for(input.domain().size, [&](const int2 texel) {
785 float4 key_color = key.load_pixel<float4, true>(texel);
786 float4 color = input.load_pixel<float4>(texel);
787 float matte = matte_image.load_pixel<float>(texel);
788
789 /* Alpha multiply the matte to the image. */
790 color *= matte;
791
792 /* Color despill. */
793 int3 indices = compute_saturation_indices(key_color.xyz());
794 float weighted_average = math::interpolate(
795 color[indices.y], color[indices.z], despill_balance);
796 color[indices.x] -= math::max(0.0f, (color[indices.x] - weighted_average) * despill_factor);
797
798 output.store_pixel(texel, color);
799 });
800 }
801
803 {
804 return math::max(0.0f, this->get_input("Despill Strength").get_single_value_default(1.0f));
805 }
806
808 {
809 return math::clamp(
810 this->get_input("Despill Balance").get_single_value_default(0.5f), 0.0f, 1.0f);
811 }
812};
813
815{
816 return new KeyingOperation(context, node);
817}
818
819} // namespace blender::nodes::node_composite_keying_cc
820
822{
824
825 static blender::bke::bNodeType ntype;
826
827 cmp_node_type_base(&ntype, "CompositorNodeKeying", CMP_NODE_KEYING);
828 ntype.ui_name = "Keying";
829 ntype.ui_description =
830 "Perform both chroma keying (to remove the backdrop) and despill (to correct color cast "
831 "from the backdrop)";
832 ntype.enum_name_legacy = "KEYING";
833 ntype.nclass = NODE_CLASS_MATTE;
834 ntype.declare = file_ns::cmp_node_keying_declare;
835 ntype.initfunc = file_ns::node_composit_init_keying;
838 ntype.get_compositor_operation = file_ns::get_compositor_operation;
840
842}
#define NODE_CLASS_MATTE
Definition BKE_node.hh:454
constexpr int NODE_DEFAULT_MAX_WIDTH
Definition BKE_node.hh:1250
#define CMP_NODE_KEYING
#define BLT_I18NCONTEXT_ID_NODETREE
#define BLT_I18NCONTEXT_ID_IMAGE
#define BLT_I18NCONTEXT_ID_CURVE_LEGACY
@ PROP_SMOOTH
@ R_FILTER_BOX
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_unbind()
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_FACTOR
Definition RNA_types.hh:251
BMesh const char void * data
ATTR_WARN_UNUSED_RESULT const BMVert * v
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 bind_as_texture(gpu::Shader *shader, const char *texture_name) const
Definition result.cc:487
void increment_reference_count(int count=1)
Definition result.cc:645
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
PanelDeclarationBuilder & add_panel(StringRef name, int identifier=-1)
DeclType::Builder & add_input(StringRef name, StringRef identifier="")
Self & translation_context(std::optional< std::string > value=std::nullopt)
std::optional< std::string > translation_context
nullptr float
static ushort indices[]
#define input
#define output
float distance(VecOp< float, D >, VecOp< float, D >) RET
VecBase< float, 2 > float2
VecBase< int, 2 > int2
VecBase< int, 3 > int3
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void node_type_size(bNodeType &ntype, int width, int minwidth, int maxwidth)
Definition node.cc:5384
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 morphological_distance(Context &context, const Result &input, Result &output, const int distance)
void morphological_distance_feather(Context &context, const Result &input, Result &output, const int distance, const int falloff_type=PROP_SMOOTH)
void parallel_for(const int2 range, const Function &function)
T clamp(const T &a, const T &min, const T &max)
T min(const T &a, const T &b)
T interpolate(const T &a, const T &b, const FactorT &t)
T max(const T &a, const T &b)
T abs(const T &a)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static void node_composit_init_keying(bNodeTree *, bNode *node)
static void cmp_node_keying_declare(NodeDeclarationBuilder &b)
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
VecBase< int32_t, 3 > int3
VecBase< float, 3 > float3
static void register_node_type_cmp_keying()
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_proportional_falloff_curve_only_items[]
Definition rna_scene.cc:112
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)