35 b.use_custom_socket_order();
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();
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 "
64 .compositor_expects_single_value();
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();
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();
87 tweak_panel.
add_panel(
"Edges").default_closed(
true).translation_context(
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();
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();
113 .description(
"Areas in the garbage matte mask are excluded from the matte");
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")
126 .compositor_expects_single_value();
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();
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 "
139 .compositor_expects_single_value();
150 .description(
"Specifies the strength of the despill")
151 .compositor_expects_single_value();
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();
236 if (blur_size == 0.0f) {
238 output.increment_reference_count();
252 return blurred_input;
257 return math::max(0, this->
get_input(
"Preprocess Blur Size").get_single_value_default(0));
262 if (this->
context().use_gpu()) {
274 input.bind_as_texture(shader,
"input_tx");
278 output.bind_as_image(shader,
"output_img");
283 input.unbind_as_texture();
297 const float4 color = input.load_pixel<float4>(texel);
306 color_ycca /= 255.0f;
307 color_ycca.w = color.w;
309 output.store_pixel(texel, color_ycca);
317 if (this->
context().use_gpu()) {
329 input.bind_as_texture(shader,
"input_tx");
335 output.bind_as_image(shader,
"output_img");
340 input.unbind_as_texture();
355 const float4 color = input.load_pixel<float4>(texel);
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;
370 ycc_to_rgb(color_ycca.x,
377 color_rgba.w = color.w;
379 output.store_pixel(texel, color_rgba);
387 if (this->
context().use_gpu()) {
407 output.bind_as_image(shader,
"output_img");
412 input.unbind_as_texture();
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);
442 float4 input_color = input.load_pixel<float4>(texel);
446 if (math::reduce_min(input_color) > 1.0f) {
447 output.store_pixel(texel, 1.0f);
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);
457 if (input_saturation < 0.0f) {
462 else if (input_saturation >= key_saturation) {
468 matte = 1.0f -
math::clamp(input_saturation / key_saturation, 0.0f, 1.0f);
471 output.store_pixel(texel, matte);
493 if (!output_edges.
should_compute() && black_level == 0.0f && white_level == 1.0f &&
497 Result output_matte = input_matte;
502 if (this->
context().use_gpu()) {
553 return output_edges.
should_compute() ?
"compositor_keying_tweak_matte_with_edges" :
554 "compositor_keying_tweak_matte_without_edges";
577 float matte = input_matte.load_pixel<float>(texel);
582 bool is_edge = false;
583 if (compute_edges || black_level != 0.0f || white_level != 1.0f) {
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);
597 is_edge = count < ((edge_search_radius * 2 + 1) * (edge_search_radius * 2 + 1)) * 0.9f;
600 float tweaked_matte = matte;
605 if (!is_edge && white_level != black_level) {
607 (matte - black_level) / (white_level - black_level), 0.0f, 1.0f);
612 float garbage_matte = garbage_matte_image.
load_pixel<float,
true>(texel);
613 tweaked_matte =
math::min(tweaked_matte, 1.0f - garbage_matte);
616 float core_matte = core_matte_image.
load_pixel<float,
true>(texel);
617 tweaked_matte =
math::max(tweaked_matte, core_matte);
621 output_edges.
store_pixel(texel, is_edge ? 1.0f : 0.0f);
636 this->
get_input(
"Edge Tolerance").get_single_value_default(0.1f), 0.0f, 1.0f);
655 if (blur_size == 0.0f) {
656 Result output_matte = input_matte;
665 return blurred_matte;
670 return math::max(0, this->
get_input(
"Postprocess Blur Size").get_single_value_default(0));
680 Result output_matte = input_matte;
688 return morphed_matte;
693 return this->
get_input(
"Postprocess Dilate Size").get_single_value_default(0);
703 Result output_matte = input_matte;
712 return feathered_matte;
717 return this->
get_input(
"Postprocess Feather Size").get_single_value_default(0);
722 if (this->
context().use_gpu()) {
732 GPUShader *shader =
context().get_shader(
"compositor_keying_compute_image");
739 input.bind_as_texture(shader,
"input_tx");
748 output.bind_as_image(shader,
"output_img");
753 input.unbind_as_texture();
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);
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);
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);
792 output.store_pixel(texel, color);
798 return math::max(0.0f, this->
get_input(
"Despill Strength").get_single_value_default(1.0f));
804 this->
get_input(
"Despill Balance").get_single_value_default(0.5f), 0.0f, 1.0f);
824 "Perform both chroma keying (to remove the backdrop) and despill (to correct color cast "
825 "from the backdrop)";
828 ntype.
declare = file_ns::cmp_node_keying_declare;
829 ntype.
initfunc = file_ns::node_composit_init_keying;
#define NODE_STORAGE_FUNCS(StorageT)
#define BLT_I18NCONTEXT_ID_NODETREE
#define BLT_I18NCONTEXT_ID_IMAGE
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)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ 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)
const bNode & bnode() const
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 allocate_texture(Domain domain, bool from_pool=true)
void store_pixel(const int2 &texel, const T &pixel_value)
void unbind_as_texture() const
void bind_as_texture(GPUShader *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 bind_as_image(GPUShader *shader, const char *image_name, bool read=false) const
void unbind_as_image() const
bool is_single_value() const
void steal_data(Result &source)
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
ColorGeometry4f default_value
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)
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)
VecBase< float, 2 > float2
float distance(VecOp< float, D >, VecOp< float, D >) RET
void * MEM_callocN(size_t len, const char *str)
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 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))
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)
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)
std::string ui_description
NodeGetCompositorOperationFunction get_compositor_operation
void(* initfunc)(bNodeTree *ntree, bNode *node)
const char * enum_name_legacy
NodeDeclareFunction declare
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)
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &input_name)