Compositor: add new node: Kuwahara filter #107015

Merged
Habib Gahbiche merged 22 commits from zazizizou/blender:com-kuwahara-filter-node into main 2023-06-08 16:14:51 +02:00
5 changed files with 55 additions and 49 deletions
Showing only changes of commit b76e5025bf - Show all commits

View File

@ -19,7 +19,7 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter,
switch (data->variation) {
case CMP_NODE_KUWAHARA_CLASSIC: {
KuwaharaClassicOperation *operation = new KuwaharaClassicOperation();
operation->set_kernel_size(data->kernel_size);
operation->set_kernel_size(data->size);
converter.add_operation(operation);
converter.map_input_socket(get_input_socket(0), operation->get_input_socket(0));
@ -61,8 +61,8 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter,
converter.add_link(sobel_x->get_output_socket(0), sobel_xy->get_input_socket(0));
converter.add_link(sobel_y->get_output_socket(0), sobel_xy->get_input_socket(1));
/* blurring for more robustness. */
const int sigma = data->sigma;
/* Blurring for more robustness. */
const int sigma = data->smoothing;
auto blur_sobel_xx = new FastGaussianBlurOperation();
auto blur_sobel_yy = new FastGaussianBlurOperation();
@ -80,13 +80,9 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter,
converter.add_link(sobel_yy->get_output_socket(0), blur_sobel_yy->get_input_socket(0));
converter.add_link(sobel_xy->get_output_socket(0), blur_sobel_xy->get_input_socket(0));
// For now, orientation is part of kuwahara operation.
// todo: implement orientation as a separate operation
// auto orientation = new OrientationOperation(); // OrientationOperation
/* Apply anisotropic Kuwahara filter */
KuwaharaAnisotropicOperation *aniso = new KuwaharaAnisotropicOperation();
aniso->set_kernel_size(data->kernel_size);
aniso->set_kernel_size(data->size);
converter.map_input_socket(get_input_socket(0), aniso->get_input_socket(0));
converter.add_operation(aniso);
@ -96,11 +92,6 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter,
converter.map_output_socket(get_output_socket(0), aniso->get_output_socket(0));
// For debug. Todo: remove
// converter.map_output_socket(get_output_socket(1), sobel_xx->get_output_socket(0));
// converter.map_output_socket(get_output_socket(2), blur_sobel_xx->get_output_socket(0));
// converter.map_output_socket(get_output_socket(3), blur_sobel_xy->get_output_socket(0));
break;
}
}

View File

@ -13,7 +13,7 @@ namespace blender::compositor {
KuwaharaAnisotropicOperation::KuwaharaAnisotropicOperation()
{
this->add_input_socket(DataType::Color);
this->add_input_socket(DataType::Color);
this->add_input_socket(DataType::Color);
this->add_input_socket(DataType::Color);
this->add_input_socket(DataType::Color);
@ -44,7 +44,7 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4],
float y,
PixelSampler sampler)
{
/* Not implemented */
/* Not implemented */
zazizizou marked this conversation as resolved Outdated

Should we consider something from:

  • Pass-through the input as-is
  • Output magenta

So that if someone saves file with the Kuwahara node in it and someone opens it without enabling full-frame compositor we do not leave the output uninitialized?

Should we consider something from: - Pass-through the input as-is - Output magenta So that if someone saves file with the Kuwahara node in it and someone opens it without enabling full-frame compositor we do not leave the output uninitialized?
}
void KuwaharaAnisotropicOperation::set_kernel_size(int kernel_size)
@ -65,7 +65,7 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
Implementation based on Kyprianidis, Jan & Kang, Henry & Döllner, Jürgen. (2009).
"Image and Video Abstraction by Anisotropic Kuwahara Filtering".
Comput. Graph. Forum. 28. 1955-1963. 10.1111/j.1467-8659.2009.01574.x.
Used reference implementation from lime image processing library (MIT license).
Used reference implementation from lime image processing library (MIT license).
*/
MemoryBuffer *image = inputs[0];
@ -80,12 +80,11 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
BLI_assert(image->get_width() == s_xy->get_width());
BLI_assert(image->get_height() == s_xy->get_height());
const int n_div = 8; // recommended by authors in original paper
const int n_div = 8; // recommended by authors in original paper
const float angle = 2.0 * M_PI / n_div;
const float q = 3.0;
const float EPS = 1.0e-10;
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const int x = it.x;
const int y = it.y;
@ -96,18 +95,19 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
const float b = s_xy->get_value(x, y, 1);
const float c = s_yy->get_value(x, y, 1);
/* Compute egenvalues of structure tensor */
const double tr = a + c;
const double discr = sqrt((a - b)*(a-b) + 4 * b * c);
const double discr = sqrt((a - b) * (a - b) + 4 * b * c);
const double lambda1 = (tr + discr) / 2;
const double lambda2 = (tr - discr) / 2;
/* Compute orientation and its strength based on structure tensor */
const double orientation = 0.5 * atan2(2 * b, a - c);
zazizizou marked this conversation as resolved Outdated

It might help making it an explicit const int height = this->get_height() outside of the loop. I am not sure compiler is smart enough to do it for us.
Although, also not suer it will give measurable time impact, but still feels like a good thing to do.

It might help making it an explicit `const int height = this->get_height()` outside of the loop. I am not sure compiler is smart enough to do it for us. Although, also not suer it will give measurable time impact, but still feels like a good thing to do.
const double strength = (lambda1 == 0 && lambda2 == 0) ? 0 : (lambda1 - lambda2) / (lambda1 + lambda2);
const double strength = (lambda1 == 0 && lambda2 == 0) ?
0 :
(lambda1 - lambda2) / (lambda1 + lambda2);
for(int ch = 0; ch < 3; ch++) {
for (int ch = 0; ch < 3; ch++) {
Vector<float> mean(n_div, 0.0f);
Vector<float> sum(n_div, 0.0f);
@ -120,7 +120,8 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
for (int dy = -kernel_size_; dy <= kernel_size_; dy++) {
for (int dx = -kernel_size_; dx <= kernel_size_; dx++) {
if (dx == 0 && dy == 0) continue;
if (dx == 0 && dy == 0)
continue;
// rotate and scale the kernel. This is the "anisotropic" part.
int dx2 = static_cast<int>(sx * (cos(theta) * dx - sin(theta) * dy));
@ -174,5 +175,4 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
}
}
} // namespace blender::compositor

View File

@ -877,9 +877,9 @@ typedef struct NodeBilateralBlurData {
} NodeBilateralBlurData;
typedef struct NodeKuwaharaData {
short kernel_size;
short size;
short variation;
int sigma;
int smoothing;
} NodeKuwaharaData;
typedef struct NodeAntiAliasingData {

View File

@ -9254,11 +9254,11 @@ static void def_cmp_kuwahara(StructRNA *srna)
{0, NULL, 0, NULL, NULL},
};
prop = RNA_def_property(srna, "kernel_size", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "kernel_size");
prop = RNA_def_property(srna, "size", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "size");
RNA_def_property_ui_range(prop, 4, 50, 1, -1);
zazizizou marked this conversation as resolved Outdated

This doesn't seem to work. User can still set values outside the specified range. This is also the case for Vector Blur. Is there another way to specify UI range?

This doesn't seem to work. User can still set values outside the specified range. This is also the case for Vector Blur. Is there another way to specify UI range?

The UI range is the soft limit. It applies when an artist drags the slider, but it is still possible to click on the property and type a value which is within a hard limit.

The hard limit is controlled by RNA_def_property_range.

You can see example in the Subsurf modifier:

RNA_def_property_range(prop, 0, 11);
RNA_def_property_ui_range(prop, 0, 6, 1, -1);
The UI range is the soft limit. It applies when an artist drags the slider, but it is still possible to click on the property and type a value which is within a hard limit. The hard limit is controlled by `RNA_def_property_range`. You can see example in the Subsurf modifier: ``` RNA_def_property_range(prop, 0, 11); RNA_def_property_ui_range(prop, 0, 6, 1, -1); ```

This did solve it, thanks!

This did solve it, thanks!
RNA_def_property_ui_text(
prop, "Kernel Size", "Kernel size of filter. Larger values give stronger stylized effect");
prop, "Size", "Size of filter. Larger values give stronger stylized effect");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "variation", PROP_ENUM, PROP_NONE);
@ -9267,13 +9267,13 @@ static void def_cmp_kuwahara(StructRNA *srna)
RNA_def_property_ui_text(prop, "", "Variation of Kuwahara filter to use.");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "sigma", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "sigma");
prop = RNA_def_property(srna, "smoothing", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "smoothing");
RNA_def_property_ui_range(prop, 0, 50, 1, -1);
RNA_def_property_ui_text(
prop,
"Sigma",
"Smoothing degree before applying filter. Higher values remove details and give smoother edges");
RNA_def_property_ui_text(prop,
"Smoothing",
"Smoothing degree before applying filter. Higher values remove details "
"and give smoother edges");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}

View File

@ -5,10 +5,8 @@
* \ingroup cmpnodes
*/
//#include "COM_shader_node.hh"
#include "COM_node_operation.hh"
#include "node_composite_util.hh"
//#include "node_composite_util.hh"
zazizizou marked this conversation as resolved Outdated

Remove the commented out code.

Remove the commented out code.
/* **************** Kuwahara ******************** */
@ -22,11 +20,6 @@ static void cmp_node_kuwahara_declare(NodeDeclarationBuilder &b)
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
.compositor_domain_priority(0);
b.add_output<decl::Color>(N_("Image"));
// For debug. Todo:remove
// b.add_output<decl::Color>(N_("Sobel x"));
// b.add_output<decl::Color>(N_("Sobel xx blurred"));
// b.add_output<decl::Color>(N_("Sobel xy blurred"));
}
static void node_composit_init_kuwahara(bNodeTree * /*ntree*/, bNode *node)
@ -34,7 +27,9 @@ static void node_composit_init_kuwahara(bNodeTree * /*ntree*/, bNode *node)
NodeKuwaharaData *data = MEM_cnew<NodeKuwaharaData>(__func__);
node->storage = data;
data->kernel_size = 4;
/* Set defaults. */
data->size = 4;
data->smoothing = 2;
}
static void node_composit_buts_kuwahara(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
@ -44,15 +39,33 @@ static void node_composit_buts_kuwahara(uiLayout *layout, bContext * /*C*/, Poin
col = uiLayoutColumn(layout, false);
uiItemR(col, ptr, "variation", 0, nullptr, ICON_NONE);
uiItemR(col, ptr, "kernel_size", 0, nullptr, ICON_NONE);
uiItemR(col, ptr, "size", 0, nullptr, ICON_NONE);
const int variation = RNA_enum_get(ptr, "variation");
if(variation == CMP_NODE_KUWAHARA_ANISOTROPIC) {
uiItemR(col, ptr, "sigma", 0, nullptr, ICON_NONE);
if (variation == CMP_NODE_KUWAHARA_ANISOTROPIC) {
uiItemR(col, ptr, "smoothing", 0, nullptr, ICON_NONE);
}
}
using namespace blender::realtime_compositor;
class ConvertKuwaharaOperation : public NodeOperation {
public:
using NodeOperation::NodeOperation;
void execute() override
{
get_input("Image").pass_through(get_result("Image"));
context().set_info_message("Viewport compositor setup not fully supported");
}
};
static NodeOperation *get_compositor_operation(Context &context, DNode node)
{
return new ConvertKuwaharaOperation(context, node);
}
} // namespace blender::nodes::node_composite_kuwahara_cc
void register_node_type_cmp_kuwahara()
@ -61,13 +74,15 @@ void register_node_type_cmp_kuwahara()
static bNodeType ntype;
cmp_node_type_base(&ntype, CMP_NODE_KUWAHARA, "Kuwahara", NODE_CLASS_OP_COLOR);
cmp_node_type_base(&ntype, CMP_NODE_KUWAHARA, "Kuwahara", NODE_CLASS_OP_FILTER);
ntype.declare = file_ns::cmp_node_kuwahara_declare;
ntype.draw_buttons = file_ns::node_composit_buts_kuwahara;
ntype.initfunc = file_ns::node_composit_init_kuwahara;
node_type_storage(
&ntype, "NodeKuwaharaData", node_free_standard_storage, node_copy_standard_storage);
// ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node;
ntype.get_compositor_operation = file_ns::get_compositor_operation;
ntype.realtime_compositor_unsupported_message = N_(
"Node not supported in the Viewport compositor");
nodeRegisterType(&ntype);
}