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