30 b.use_custom_socket_order();
31 b.allow_any_socket_order();
34 .default_value({0.8f, 0.8f, 0.8f, 1.0f})
37 b.add_output<
decl::Color>(
"Image").structure_type(StructureType::Dynamic).align_with_previous();
39 b.add_output<
decl::Float>(
"Matte").structure_type(StructureType::Dynamic);
41 .structure_type(StructureType::Dynamic)
45 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
46 .structure_type(StructureType::Dynamic);
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");
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 "
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");
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");
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");
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");
111 .structure_type(StructureType::Dynamic)
112 .description(
"Areas in the garbage matte mask are excluded from the matte");
118 .structure_type(StructureType::Dynamic)
119 .description(
"Areas in the core matte mask are included in the matte");
125 .
description(
"Blur the computed matte using a Gaussian blur of the given size");
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");
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 "
149 .description(
"Specifies the strength of the despill");
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");
234 if (blur_size == 0.0f) {
236 output.increment_reference_count();
250 return blurred_input;
255 return math::max(0, this->
get_input(
"Preprocess Blur Size").get_single_value_default(0));
260 if (this->
context().use_gpu()) {
272 input.bind_as_texture(shader,
"input_tx");
276 output.bind_as_image(shader,
"output_img");
281 input.unbind_as_texture();
295 const float4 color = input.load_pixel<float4>(texel);
304 color_ycca /= 255.0f;
305 color_ycca.w = color.w;
307 output.store_pixel(texel, color_ycca);
315 if (this->
context().use_gpu()) {
327 input.bind_as_texture(shader,
"input_tx");
333 output.bind_as_image(shader,
"output_img");
338 input.unbind_as_texture();
353 const float4 color = input.load_pixel<float4>(texel);
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;
368 ycc_to_rgb(color_ycca.x,
375 color_rgba.w = color.w;
377 output.store_pixel(texel, color_rgba);
385 if (this->
context().use_gpu()) {
405 output.bind_as_image(shader,
"output_img");
410 input.unbind_as_texture();
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);
440 float4 input_color = input.load_pixel<float4>(texel);
444 if (math::reduce_min(input_color) > 1.0f) {
445 output.store_pixel(texel, 1.0f);
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);
455 if (input_saturation < 0.0f) {
460 else if (input_saturation >= key_saturation) {
466 matte = 1.0f -
math::clamp(input_saturation / key_saturation, 0.0f, 1.0f);
469 output.store_pixel(texel, matte);
491 if (!output_edges.
should_compute() && black_level == 0.0f && white_level == 1.0f &&
495 Result output_matte = input_matte;
500 if (this->
context().use_gpu()) {
551 return output_edges.
should_compute() ?
"compositor_keying_tweak_matte_with_edges" :
552 "compositor_keying_tweak_matte_without_edges";
575 float matte = input_matte.load_pixel<float>(texel);
580 bool is_edge = false;
581 if (compute_edges || black_level != 0.0f || white_level != 1.0f) {
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);
595 is_edge = count < ((edge_search_radius * 2 + 1) * (edge_search_radius * 2 + 1)) * 0.9f;
598 float tweaked_matte = matte;
603 if (!is_edge && white_level != black_level) {
605 (matte - black_level) / (white_level - black_level), 0.0f, 1.0f);
610 float garbage_matte = garbage_matte_image.
load_pixel<
float,
true>(texel);
611 tweaked_matte =
math::min(tweaked_matte, 1.0f - garbage_matte);
615 tweaked_matte =
math::max(tweaked_matte, core_matte);
619 output_edges.
store_pixel(texel, is_edge ? 1.0f : 0.0f);
634 this->
get_input(
"Edge Tolerance").get_single_value_default(0.1f), 0.0f, 1.0f);
653 if (blur_size == 0.0f) {
654 Result output_matte = input_matte;
663 return blurred_matte;
668 return math::max(0, this->
get_input(
"Postprocess Blur Size").get_single_value_default(0));
678 Result output_matte = input_matte;
686 return morphed_matte;
691 return this->
get_input(
"Postprocess Dilate Size").get_single_value_default(0);
701 Result output_matte = input_matte;
710 return feathered_matte;
715 return this->
get_input(
"Postprocess Feather Size").get_single_value_default(0);
722 const MenuValue menu_value =
input.get_single_value_default(default_menu_value);
723 return menu_value.
value;
728 if (this->
context().use_gpu()) {
745 input.bind_as_texture(shader,
"input_tx");
754 output.bind_as_image(shader,
"output_img");
759 input.unbind_as_texture();
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);
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);
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);
798 output.store_pixel(texel, color);
804 return math::max(0.0f, this->
get_input(
"Despill Strength").get_single_value_default(1.0f));
810 this->
get_input(
"Despill Balance").get_single_value_default(0.5f), 0.0f, 1.0f);
830 "Perform both chroma keying (to remove the backdrop) and despill (to correct color cast "
831 "from the backdrop)";
834 ntype.
declare = file_ns::cmp_node_keying_declare;
835 ntype.
initfunc = file_ns::node_composit_init_keying;
constexpr int NODE_DEFAULT_MAX_WIDTH
#define BLT_I18NCONTEXT_ID_NODETREE
#define BLT_I18NCONTEXT_ID_IMAGE
#define BLT_I18NCONTEXT_ID_CURVE_LEGACY
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)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
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)
Context & context() const
Result & get_input(StringRef identifier) const
void share_data(const Result &source)
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)
void unbind_as_texture() const
void bind_as_texture(gpu::Shader *shader, const char *texture_name) const
void increment_reference_count(int count=1)
const Domain & domain() const
T load_pixel(const int2 &texel) const
void unbind_as_image() const
void bind_as_image(gpu::Shader *shader, const char *image_name, bool read=false) const
bool is_single_value() const
void steal_data(Result &source)
PanelDeclarationBuilder & add_panel(StringRef name, int identifier=-1)
DeclType::Builder & add_input(StringRef name, StringRef identifier="")
Self & default_closed(bool closed)
Self & translation_context(std::optional< std::string > value=std::nullopt)
StructureType structure_type
std::optional< std::string > translation_context
int get_postprocess_blur_size()
Result extract_input_chroma()
float get_edge_tolerance()
int get_preprocess_blur_size()
Result extract_input_chroma_gpu()
float get_despill_balance()
int get_edge_search_size()
Result compute_tweaked_matte_gpu(Result &input_matte)
Result compute_feathered_matte(Result &input_matte)
int get_postprocess_feather_size()
Result compute_tweaked_matte_cpu(Result &input_matte)
Result replace_input_chroma_gpu(Result &new_chroma)
Result compute_blurred_input()
Result compute_morphed_matte(Result &input_matte)
void compute_image_gpu(Result &matte)
Result compute_matte(Result &input)
Result compute_tweaked_matte(Result &input_matte)
Result compute_matte_cpu(Result &input)
Result compute_blurred_matte(Result &input_matte)
Result extract_input_chroma_cpu()
Result compute_matte_gpu(Result &input)
Result replace_input_chroma(Result &new_chroma)
int get_feather_falloff()
const char * get_tweak_matte_shader_name()
Result replace_input_chroma_cpu(Result &new_chroma)
int get_postprocess_dilate_size()
void compute_image(Result &matte)
NodeOperation(Context &context, DNode node)
float get_despill_strength()
void compute_image_cpu(Result &matte_image)
float distance(VecOp< float, D >, VecOp< float, D >) RET
VecBase< float, 2 > float2
void * MEM_callocN(size_t len, const char *str)
void node_type_size(bNodeType &ntype, int width, int minwidth, int maxwidth)
void node_register_type(bNodeType &ntype)
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))
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))
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)
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)
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
const EnumPropertyItem rna_enum_proportional_falloff_curve_only_items[]
std::string ui_description
NodeGetCompositorOperationFunction get_compositor_operation
void(* initfunc)(bNodeTree *ntree, bNode *node)
const char * enum_name_legacy
NodeDeclareFunction declare
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &input_name)