From c1fb210be9cbbebdfd671ecfd960b8c0400f94aa Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Sun, 16 Apr 2023 22:57:06 +0200 Subject: [PATCH 01/14] ompositor: add new node: Kuwahara filter The filter is used to reduce noise while preserving edges. It can be used to create a cartoon effect from photorealistic images. The idea is to offer two modes: 1) Calssic aka isotropic kuwahara filter: simple and faster computation. Algorithm splits an area around a single pixel in four parts and selects the mean of the region with the lowest standard deviation 2) Anisotropic Kuwahara filter: improve the classical approach by considering the direction of structures of regions This patch implements the isotropic approach as a single threaded operation for the full-frame compositor --- scripts/startup/nodeitems_builtins.py | 1 + source/blender/blenkernel/BKE_node.h | 1 + source/blender/compositor/CMakeLists.txt | 4 + .../compositor/intern/COM_Converter.cc | 4 + .../compositor/nodes/COM_KuwaharaNode.cc | 24 +++ .../compositor/nodes/COM_KuwaharaNode.h | 22 +++ .../operations/COM_KuwaharaOperation.cc | 159 ++++++++++++++++++ .../operations/COM_KuwaharaOperation.h | 47 ++++++ source/blender/makesdna/DNA_node_types.h | 6 + source/blender/makesrna/intern/rna_nodetree.c | 25 +++ source/blender/nodes/NOD_static_types.h | 1 + source/blender/nodes/composite/CMakeLists.txt | 1 + .../composite/node_composite_register.cc | 1 + .../composite/node_composite_register.hh | 1 + .../nodes/node_composite_kuwahara.cc | 97 +++++++++++ 15 files changed, 394 insertions(+) create mode 100644 source/blender/compositor/nodes/COM_KuwaharaNode.cc create mode 100644 source/blender/compositor/nodes/COM_KuwaharaNode.h create mode 100644 source/blender/compositor/operations/COM_KuwaharaOperation.cc create mode 100644 source/blender/compositor/operations/COM_KuwaharaOperation.h create mode 100644 source/blender/nodes/composite/nodes/node_composite_kuwahara.cc diff --git a/scripts/startup/nodeitems_builtins.py b/scripts/startup/nodeitems_builtins.py index 3deec135888..5ba5ad34bf9 100644 --- a/scripts/startup/nodeitems_builtins.py +++ b/scripts/startup/nodeitems_builtins.py @@ -336,6 +336,7 @@ compositor_node_categories = [ NodeItem("CompositorNodeSunBeams"), NodeItem("CompositorNodeDenoise"), NodeItem("CompositorNodeAntiAliasing"), + NodeItem("CompositorNodeKuwahara"), ]), CompositorNodeCategory("CMP_OP_VECTOR", "Vector", items=[ NodeItem("CompositorNodeNormal"), diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 3039334b8f8..02e0a2c9e38 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1333,6 +1333,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define CMP_NODE_INPAINT 272 #define CMP_NODE_DESPECKLE 273 #define CMP_NODE_ANTIALIASING 274 +#define CMP_NODE_KUWAHARA 275 #define CMP_NODE_GLARE 301 #define CMP_NODE_TONEMAP 302 diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index 6549516cc2c..d8dc5b7c63b 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -321,6 +321,8 @@ if(WITH_COMPOSITOR_CPU) nodes/COM_FilterNode.h nodes/COM_InpaintNode.cc nodes/COM_InpaintNode.h + nodes/COM_KuwaharaNode.h + nodes/COM_KuwaharaNode.cc nodes/COM_PosterizeNode.cc nodes/COM_PosterizeNode.h @@ -348,6 +350,8 @@ if(WITH_COMPOSITOR_CPU) operations/COM_GaussianXBlurOperation.h operations/COM_GaussianYBlurOperation.cc operations/COM_GaussianYBlurOperation.h + operations/COM_KuwaharaOperation.h + operations/COM_KuwaharaOperation.cc operations/COM_MovieClipAttributeOperation.cc operations/COM_MovieClipAttributeOperation.h operations/COM_MovieDistortionOperation.cc diff --git a/source/blender/compositor/intern/COM_Converter.cc b/source/blender/compositor/intern/COM_Converter.cc index 6d7341376e9..5979fd1b3dc 100644 --- a/source/blender/compositor/intern/COM_Converter.cc +++ b/source/blender/compositor/intern/COM_Converter.cc @@ -61,6 +61,7 @@ #include "COM_InvertNode.h" #include "COM_KeyingNode.h" #include "COM_KeyingScreenNode.h" +#include "COM_KuwaharaNode.h" #include "COM_LensDistortionNode.h" #include "COM_LuminanceMatteNode.h" #include "COM_MapRangeNode.h" @@ -435,6 +436,9 @@ Node *COM_convert_bnode(bNode *b_node) case CMP_NODE_COMBINE_XYZ: node = new CombineXYZNode(b_node); break; + case CMP_NODE_KUWAHARA: + node = new KuwaharaNode(b_node); + break; } return node; } diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.cc b/source/blender/compositor/nodes/COM_KuwaharaNode.cc new file mode 100644 index 00000000000..a5cb21dc7c8 --- /dev/null +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.cc @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2011 Blender Foundation. */ + +#include "COM_KuwaharaNode.h" +#include "COM_KuwaharaOperation.h" + +namespace blender::compositor { + +void KuwaharaNode::convert_to_operations(NodeConverter &converter, + const CompositorContext & /*context*/) const +{ + const bNode *node = this->get_bnode(); + const NodeKuwaharaData *data = (const NodeKuwaharaData *)node->storage; + + KuwaharaOperation *operation = new KuwaharaOperation(); + operation->set_kernel_size(data->kernel_size); + operation->set_variation(data->variation); + + converter.add_operation(operation); + converter.map_input_socket(get_input_socket(0), operation->get_input_socket(0)); + converter.map_output_socket(get_output_socket(0), operation->get_output_socket()); +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.h b/source/blender/compositor/nodes/COM_KuwaharaNode.h new file mode 100644 index 00000000000..1b8426154e2 --- /dev/null +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2011 Blender Foundation. */ + +#pragma once + +#include "COM_Node.h" + +namespace blender::compositor { + +/** + * \brief KuwaharaNode + * \ingroup Node + */ + +class KuwaharaNode : public Node { + public: + KuwaharaNode(bNode *editor_node) : Node(editor_node) {} + void convert_to_operations(NodeConverter &converter, + const CompositorContext &context) const override; +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_KuwaharaOperation.cc b/source/blender/compositor/operations/COM_KuwaharaOperation.cc new file mode 100644 index 00000000000..c61b0b515ef --- /dev/null +++ b/source/blender/compositor/operations/COM_KuwaharaOperation.cc @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2011 Blender Foundation. */ + +#include "COM_KuwaharaOperation.h" + +namespace blender::compositor { + +KuwaharaOperation::KuwaharaOperation() +{ + this->add_input_socket(DataType::Color); + this->add_output_socket(DataType::Color); + this->set_kernel_size(4.4f); + + this->flags_.is_fullframe_operation = true; +} + +void KuwaharaOperation::init_execution() +{ + image_reader_ = this->get_input_socket_reader(0); +} + +void KuwaharaOperation::deinit_execution() +{ + image_reader_ = nullptr; +} + +void KuwaharaOperation::execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) +{ + float input_value[4]; + image_reader_->read_sampled(input_value, x, y, sampler); + + output[0] = input_value[0] + 1.0; + output[1] = input_value[1] + 2.0; + output[2] = input_value[2] + 3.0; + output[3] = input_value[3] + 4.0; +} + +void KuwaharaOperation::set_kernel_size(int kernel_size) +{ + kernel_size_ = kernel_size; +} + +int KuwaharaOperation::get_kernel_size() +{ + return kernel_size_; +} + +void KuwaharaOperation::set_variation(int variation) +{ + variation_ = variation; +} + +int KuwaharaOperation::get_variation() +{ + return variation_; +} + + +void KuwaharaOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span inputs) +{ + MemoryBuffer *image = inputs[0]; + + const int bwidth = area.xmax - area.xmin; + const int bheight = area.ymax - area.ymin; + +// for (int y = 0; y < bheight; y++) +// { +// for (int x = 0; x < bwidth; x++) +// { +// output->get_value(x, y, 3) = 1; +// } +// } + + for (int ch = 0; ch < 3; ch++) + { + for (int y = 0; y < bheight; y++) + { + for (int x = 0; x < bwidth; x++) + { + float sum[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float var[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + int cnt[4] = {0, 0, 0, 0}; + for (int dy = -kernel_size_; dy <= kernel_size_; dy++) + { + for (int dx = -kernel_size_; dx <= kernel_size_; dx++) + { + int xx = x + dx; + int yy = y + dy; + if (xx >= 0 && yy >= 0 && xx < area.xmax && yy < area.ymax) + { + //double v = temp.at(yy, xx * dim + c); + float v; + v = image->get_value(xx, yy, ch); + + if (dx <= 0 && dy <= 0) + { + sum[0] += v; + var[0] += v * v; + cnt[0]++; + } + + if (dx >= 0 && dy <= 0) + { + sum[1] += v; + var[1] += v * v; + cnt[1]++; + } + + if (dx <= 0 && dy >= 0) + { + sum[2] += v; + var[2] += v * v; + cnt[2]++; + } + + if (dx >= 0 && dy >= 0) + { + sum[3] += v; + var[3] += v * v; + cnt[3]++; + } + } + } + } + + std::vector> vec; + for (int i = 0; i < 4; i++) + { + sum[i] = cnt[i] != 0 ? sum[i] / cnt[i] : 0.0f; + var[i] = cnt[i] != 0 ? var[i] / cnt[i] : 0.0f; + var[i] = sqrt(var[i] - sum[i] * sum[i]); + vec.push_back(std::make_pair(var[i], i)); + } + + sort(vec.begin(), vec.end()); + std::cout << "x: " << x << " y: " << y << std::endl; + for (int i = 0; i < 4; i++) + { + std::cout << "\t" << vec[i].first << " " << vec[i].second << std::endl; + } + //out.at(y, x * dim + c) = static_cast(sum[vec[0].second]); + float res = static_cast(sum[vec[0].second]); + + output->get_value(x, y, ch) = res; + } + } + } +} + +void KuwaharaOperation::get_area_of_interest(const int /*input_idx*/, + const rcti & /*output_area*/, + rcti &r_input_area) +{ + r_input_area = this->get_canvas(); +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_KuwaharaOperation.h b/source/blender/compositor/operations/COM_KuwaharaOperation.h new file mode 100644 index 00000000000..8fd5470f661 --- /dev/null +++ b/source/blender/compositor/operations/COM_KuwaharaOperation.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2011 Blender Foundation. */ + +#pragma once + +#include "COM_NodeOperation.h" + +namespace blender::compositor { + +class KuwaharaOperation : public NodeOperation { + SocketReader *image_reader_; + + int kernel_size_; + int variation_; + + public: + KuwaharaOperation(); + + void init_execution() override; + void deinit_execution() override; + void execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) override; + + void set_kernel_size(int kernel_size); + int get_kernel_size(); + + void set_variation(int variation); + int get_variation(); + + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span inputs) override; + + void get_area_of_interest(const int input_idx, + const rcti & output_area, + rcti &r_input_area) override; +// +// bool determine_depending_area_of_interest(rcti *input, +// ReadBufferOperation *read_operation, +// rcti *output) override; +// +// void update_memory_buffer_partial(MemoryBuffer *output, +// const rcti &area, +// Span inputs) override; + +}; + +} // namespace blender::compositor diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 8245637943a..771f07805ab 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -876,6 +876,12 @@ typedef struct NodeBilateralBlurData { char _pad[2]; } NodeBilateralBlurData; +typedef struct NodeKuwaharaData { + short kernel_size; + short variation; + char _pad[4]; +} NodeKuwaharaData; + typedef struct NodeAntiAliasingData { float threshold; float contrast_limit; diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 2399cfe74e3..b958403cf4a 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9242,6 +9242,31 @@ static void def_cmp_denoise(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_cmp_kuwahara(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeKuwaharaData", "storage"); + + static const EnumPropertyItem variation_items[] = { + {0, "CLASSIC", 0, "Classic", "Fast but less accurate variation"}, + {1, "ANISOTROPIC", 0, "Anisotropic", "Accurate but slower variation"}, + {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"); + RNA_def_property_ui_text( + prop, "Kernel Size", "Kernel size of filter. The larger the stronger the effect"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "variation", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "variation"); + RNA_def_property_enum_items(prop, variation_items); + RNA_def_property_ui_text(prop, "", "Variation of Kuwahara filter to use."); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + static void def_cmp_antialiasing(StructRNA *srna) { PropertyRNA *prop; diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index c4f7541e4bb..9ac4ac53726 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -223,6 +223,7 @@ DefNode(CompositorNode, CMP_NODE_COMBINE_XYZ, 0, "COMBIN DefNode(CompositorNode, CMP_NODE_SEPARATE_XYZ, 0, "SEPARATE_XYZ", SeparateXYZ, "Separate XYZ", "" ) DefNode(CompositorNode, CMP_NODE_SEPARATE_COLOR, def_cmp_combsep_color, "SEPARATE_COLOR", SeparateColor, "Separate Color", "" ) DefNode(CompositorNode, CMP_NODE_COMBINE_COLOR, def_cmp_combsep_color, "COMBINE_COLOR", CombineColor, "Combine Color", "" ) +DefNode(CompositorNode, CMP_NODE_KUWAHARA, def_cmp_kuwahara, "KUWAHARA", Kuwahara, "Kuwahara", "" ) DefNode(TextureNode, TEX_NODE_OUTPUT, def_tex_output, "OUTPUT", Output, "Output", "" ) DefNode(TextureNode, TEX_NODE_CHECKER, 0, "CHECKER", Checker, "Checker", "" ) diff --git a/source/blender/nodes/composite/CMakeLists.txt b/source/blender/nodes/composite/CMakeLists.txt index 73542f4c2bd..aaddd911ab6 100644 --- a/source/blender/nodes/composite/CMakeLists.txt +++ b/source/blender/nodes/composite/CMakeLists.txt @@ -76,6 +76,7 @@ set(SRC nodes/node_composite_invert.cc nodes/node_composite_keying.cc nodes/node_composite_keyingscreen.cc + nodes/node_composite_kuwahara.cc nodes/node_composite_lensdist.cc nodes/node_composite_levels.cc nodes/node_composite_luma_matte.cc diff --git a/source/blender/nodes/composite/node_composite_register.cc b/source/blender/nodes/composite/node_composite_register.cc index 38796cbea60..bd4d27cbe09 100644 --- a/source/blender/nodes/composite/node_composite_register.cc +++ b/source/blender/nodes/composite/node_composite_register.cc @@ -62,6 +62,7 @@ void register_composite_nodes() register_node_type_cmp_invert(); register_node_type_cmp_keying(); register_node_type_cmp_keyingscreen(); + register_node_type_cmp_kuwahara(); register_node_type_cmp_lensdist(); register_node_type_cmp_luma_matte(); register_node_type_cmp_map_range(); diff --git a/source/blender/nodes/composite/node_composite_register.hh b/source/blender/nodes/composite/node_composite_register.hh index f15a71cfae1..7fdb4ccc315 100644 --- a/source/blender/nodes/composite/node_composite_register.hh +++ b/source/blender/nodes/composite/node_composite_register.hh @@ -58,6 +58,7 @@ void register_node_type_cmp_inpaint(); void register_node_type_cmp_invert(); void register_node_type_cmp_keying(); void register_node_type_cmp_keyingscreen(); +void register_node_type_cmp_kuwahara(); void register_node_type_cmp_lensdist(); void register_node_type_cmp_luma_matte(); void register_node_type_cmp_map_range(); diff --git a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc new file mode 100644 index 00000000000..8152ef424d4 --- /dev/null +++ b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2020 Blender Foundation */ + +/** \file + * \ingroup cmpnodes + */ + + +//#include "COM_shader_node.hh" +#include "COM_node_operation.hh" + +#include "node_composite_util.hh" + +/* **************** Kuwahara ******************** */ + +namespace blender::nodes::node_composite_kuwahara_cc { + +NODE_STORAGE_FUNCS(NodeKuwaharaData) + +static void cmp_node_kuwahara_declare(NodeDeclarationBuilder &b) +{ + b.add_input(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_output(N_("Image")); +} + +static void node_composit_init_kuwahara(bNodeTree * /*ntree*/, bNode *node) +{ + NodeKuwaharaData *data = MEM_cnew(__func__); + node->storage = data; + + data->kernel_size = 4.1f; +} + +static void node_composit_buts_kuwahara(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiLayout *col; + + col = uiLayoutColumn(layout, false); + + uiItemR(col, ptr, "variation", 0, nullptr, ICON_NONE); + uiItemR(col, ptr, "kernel_size", 0, nullptr, ICON_NONE); +} + + + +//using namespace blender::realtime_compositor; +// +//class KuwaharaShaderNode : 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"); +// } +// using ShaderNode::ShaderNode; +// +// void compile(GPUMaterial *material) override +// { +// GPUNodeStack *inputs = get_inputs_array(); +// GPUNodeStack *outputs = get_outputs_array(); +// +// GPU_stack_link(material, &bnode(), "node_composite_kuwahara", inputs, outputs); +// } +//}; +// +//static NodeOperation *get_compositor_operation(Context *context, DNode node) +//{ +// return new KuwaharaOperation(context, node); +//} + +//static ShaderNode *get_compositor_shader_node(DNode node) +//{ +// return new KuwaharaShaderNode(node); +//} + +} // namespace blender::nodes::node_composite_kuwahara_cc + +void register_node_type_cmp_kuwahara() +{ + namespace file_ns = blender::nodes::node_composite_kuwahara_cc; + + static bNodeType ntype; + + cmp_node_type_base(&ntype, CMP_NODE_KUWAHARA, "Kuwahara", NODE_CLASS_OP_COLOR); + 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; + + nodeRegisterType(&ntype); +} -- 2.30.2 From 41e5526372a9541ae79ad9c02e3716428c201e43 Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Sun, 16 Apr 2023 23:32:58 +0200 Subject: [PATCH 02/14] Remove debug output and set alpha = 1 for demo --- .../operations/COM_KuwaharaOperation.cc | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/source/blender/compositor/operations/COM_KuwaharaOperation.cc b/source/blender/compositor/operations/COM_KuwaharaOperation.cc index c61b0b515ef..109421247c9 100644 --- a/source/blender/compositor/operations/COM_KuwaharaOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaOperation.cc @@ -65,13 +65,13 @@ void KuwaharaOperation::update_memory_buffer(MemoryBuffer *output, const int bwidth = area.xmax - area.xmin; const int bheight = area.ymax - area.ymin; -// for (int y = 0; y < bheight; y++) -// { -// for (int x = 0; x < bwidth; x++) -// { -// output->get_value(x, y, 3) = 1; -// } -// } + for (int y = 0; y < bheight; y++) + { + for (int x = 0; x < bwidth; x++) + { + output->get_value(x, y, 3) = 1; + } + } for (int ch = 0; ch < 3; ch++) { @@ -135,12 +135,6 @@ void KuwaharaOperation::update_memory_buffer(MemoryBuffer *output, } sort(vec.begin(), vec.end()); - std::cout << "x: " << x << " y: " << y << std::endl; - for (int i = 0; i < 4; i++) - { - std::cout << "\t" << vec[i].first << " " << vec[i].second << std::endl; - } - //out.at(y, x * dim + c) = static_cast(sum[vec[0].second]); float res = static_cast(sum[vec[0].second]); output->get_value(x, y, ch) = res; -- 2.30.2 From 961b514570fb1c5334d1f83f7aec2bb2812400ca Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Tue, 18 Apr 2023 10:18:42 +0100 Subject: [PATCH 03/14] Implement multi-threaded classical kuwahara filter --- .../operations/COM_KuwaharaOperation.cc | 131 ++++++++---------- .../operations/COM_KuwaharaOperation.h | 22 +-- .../nodes/node_composite_kuwahara.cc | 2 +- 3 files changed, 63 insertions(+), 92 deletions(-) diff --git a/source/blender/compositor/operations/COM_KuwaharaOperation.cc b/source/blender/compositor/operations/COM_KuwaharaOperation.cc index 109421247c9..fb2a5949699 100644 --- a/source/blender/compositor/operations/COM_KuwaharaOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaOperation.cc @@ -55,99 +55,82 @@ int KuwaharaOperation::get_variation() return variation_; } - -void KuwaharaOperation::update_memory_buffer(MemoryBuffer *output, - const rcti &area, - Span inputs) +void KuwaharaOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span inputs) { MemoryBuffer *image = inputs[0]; - const int bwidth = area.xmax - area.xmin; - const int bheight = area.ymax - area.ymin; + for (BuffersIterator it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const int x = it.x; + const int y = it.y; + it.out[3] = image->get_value(x, y, 3); - for (int y = 0; y < bheight; y++) - { - for (int x = 0; x < bwidth; x++) - { - output->get_value(x, y, 3) = 1; - } - } - for (int ch = 0; ch < 3; ch++) - { - for (int y = 0; y < bheight; y++) + for(int ch = 0; ch < 3; ch++) { - for (int x = 0; x < bwidth; x++) + float sum[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float var[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + int cnt[4] = {0, 0, 0, 0}; + + for (int dy = -kernel_size_; dy <= kernel_size_; dy++) { - float sum[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - float var[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - int cnt[4] = {0, 0, 0, 0}; - for (int dy = -kernel_size_; dy <= kernel_size_; dy++) + for (int dx = -kernel_size_; dx <= kernel_size_; dx++) { - for (int dx = -kernel_size_; dx <= kernel_size_; dx++) + + int xx = x + dx; + int yy = y + dy; + if (xx >= 0 && yy >= 0 && xx < area.xmax && yy < area.ymax) { - int xx = x + dx; - int yy = y + dy; - if (xx >= 0 && yy >= 0 && xx < area.xmax && yy < area.ymax) + float v; + v = image->get_value(xx, yy, ch); + + if (dx <= 0 && dy <= 0) { - //double v = temp.at(yy, xx * dim + c); - float v; - v = image->get_value(xx, yy, ch); + sum[0] += v; + var[0] += v * v; + cnt[0]++; + } - if (dx <= 0 && dy <= 0) - { - sum[0] += v; - var[0] += v * v; - cnt[0]++; - } + if (dx >= 0 && dy <= 0) + { + sum[1] += v; + var[1] += v * v; + cnt[1]++; + } - if (dx >= 0 && dy <= 0) - { - sum[1] += v; - var[1] += v * v; - cnt[1]++; - } + if (dx <= 0 && dy >= 0) + { + sum[2] += v; + var[2] += v * v; + cnt[2]++; + } - if (dx <= 0 && dy >= 0) - { - sum[2] += v; - var[2] += v * v; - cnt[2]++; - } - - if (dx >= 0 && dy >= 0) - { - sum[3] += v; - var[3] += v * v; - cnt[3]++; - } + if (dx >= 0 && dy >= 0) + { + sum[3] += v; + var[3] += v * v; + cnt[3]++; } } } - - std::vector> vec; - for (int i = 0; i < 4; i++) - { - sum[i] = cnt[i] != 0 ? sum[i] / cnt[i] : 0.0f; - var[i] = cnt[i] != 0 ? var[i] / cnt[i] : 0.0f; - var[i] = sqrt(var[i] - sum[i] * sum[i]); - vec.push_back(std::make_pair(var[i], i)); - } - - sort(vec.begin(), vec.end()); - float res = static_cast(sum[vec[0].second]); - - output->get_value(x, y, ch) = res; } + + std::vector> vec; + for (int i = 0; i < 4; i++) + { + sum[i] = cnt[i] != 0 ? sum[i] / cnt[i] : 0.0f; + var[i] = cnt[i] != 0 ? var[i] / cnt[i] : 0.0f; + var[i] = sqrt(var[i] - sum[i] * sum[i]); + vec.push_back(std::make_pair(var[i], i)); + } + + sort(vec.begin(), vec.end()); + float res = static_cast(sum[vec[0].second]); + + output->get_value(x, y, ch) = res; } } } -void KuwaharaOperation::get_area_of_interest(const int /*input_idx*/, - const rcti & /*output_area*/, - rcti &r_input_area) -{ - r_input_area = this->get_canvas(); -} - } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_KuwaharaOperation.h b/source/blender/compositor/operations/COM_KuwaharaOperation.h index 8fd5470f661..a2fdcabb7e2 100644 --- a/source/blender/compositor/operations/COM_KuwaharaOperation.h +++ b/source/blender/compositor/operations/COM_KuwaharaOperation.h @@ -3,11 +3,11 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class KuwaharaOperation : public NodeOperation { +class KuwaharaOperation : public MultiThreadedOperation { SocketReader *image_reader_; int kernel_size_; @@ -26,21 +26,9 @@ class KuwaharaOperation : public NodeOperation { void set_variation(int variation); int get_variation(); - void update_memory_buffer(MemoryBuffer *output, - const rcti &area, - Span inputs) override; - - void get_area_of_interest(const int input_idx, - const rcti & output_area, - rcti &r_input_area) override; -// -// bool determine_depending_area_of_interest(rcti *input, -// ReadBufferOperation *read_operation, -// rcti *output) override; -// -// void update_memory_buffer_partial(MemoryBuffer *output, -// const rcti &area, -// Span inputs) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span inputs) override; }; diff --git a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc index 8152ef424d4..13d0e0ccaed 100644 --- a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc +++ b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc @@ -30,7 +30,7 @@ static void node_composit_init_kuwahara(bNodeTree * /*ntree*/, bNode *node) NodeKuwaharaData *data = MEM_cnew(__func__); node->storage = data; - data->kernel_size = 4.1f; + data->kernel_size = 4; } static void node_composit_buts_kuwahara(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) -- 2.30.2 From f8c4f1a0e1de1bfc7119c9991f2fa14b0058437a Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Tue, 18 Apr 2023 15:35:28 +0100 Subject: [PATCH 04/14] Use fixed size arrays instead of std::vector / clang-format --- .../operations/COM_KuwaharaOperation.cc | 54 +++++++++---------- .../operations/COM_KuwaharaOperation.h | 3 +- .../nodes/node_composite_kuwahara.cc | 37 +------------ 3 files changed, 29 insertions(+), 65 deletions(-) diff --git a/source/blender/compositor/operations/COM_KuwaharaOperation.cc b/source/blender/compositor/operations/COM_KuwaharaOperation.cc index fb2a5949699..33a77eefcc8 100644 --- a/source/blender/compositor/operations/COM_KuwaharaOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaOperation.cc @@ -24,7 +24,10 @@ void KuwaharaOperation::deinit_execution() image_reader_ = nullptr; } -void KuwaharaOperation::execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) +void KuwaharaOperation::execute_pixel_sampled(float output[4], + float x, + float y, + PixelSampler sampler) { float input_value[4]; image_reader_->read_sampled(input_value, x, y, sampler); @@ -66,48 +69,40 @@ void KuwaharaOperation::update_memory_buffer_partial(MemoryBuffer *output, const int y = it.y; it.out[3] = image->get_value(x, y, 3); - - for(int ch = 0; ch < 3; ch++) - { + for (int ch = 0; ch < 3; ch++) { float sum[4] = {0.0f, 0.0f, 0.0f, 0.0f}; float var[4] = {0.0f, 0.0f, 0.0f, 0.0f}; int cnt[4] = {0, 0, 0, 0}; - for (int dy = -kernel_size_; dy <= kernel_size_; dy++) - { - for (int dx = -kernel_size_; dx <= kernel_size_; dx++) - { + /* Split surroundings of */ + for (int dy = -kernel_size_; dy <= kernel_size_; dy++) { + for (int dx = -kernel_size_; dx <= kernel_size_; dx++) { int xx = x + dx; int yy = y + dy; - if (xx >= 0 && yy >= 0 && xx < area.xmax && yy < area.ymax) - { + if (xx >= 0 && yy >= 0 && xx < area.xmax && yy < area.ymax) { float v; v = image->get_value(xx, yy, ch); - if (dx <= 0 && dy <= 0) - { + if (dx <= 0 && dy <= 0) { sum[0] += v; var[0] += v * v; cnt[0]++; } - if (dx >= 0 && dy <= 0) - { + if (dx >= 0 && dy <= 0) { sum[1] += v; var[1] += v * v; cnt[1]++; } - if (dx <= 0 && dy >= 0) - { + if (dx <= 0 && dy >= 0) { sum[2] += v; var[2] += v * v; cnt[2]++; } - if (dx >= 0 && dy >= 0) - { + if (dx >= 0 && dy >= 0) { sum[3] += v; var[3] += v * v; cnt[3]++; @@ -116,19 +111,24 @@ void KuwaharaOperation::update_memory_buffer_partial(MemoryBuffer *output, } } - std::vector> vec; - for (int i = 0; i < 4; i++) - { + /* Compute region variances */ + for (int i = 0; i < 4; i++) { sum[i] = cnt[i] != 0 ? sum[i] / cnt[i] : 0.0f; var[i] = cnt[i] != 0 ? var[i] / cnt[i] : 0.0f; - var[i] = sqrt(var[i] - sum[i] * sum[i]); - vec.push_back(std::make_pair(var[i], i)); + const float temp = sum[i] * sum[i]; + var[i] = var[i] > temp ? sqrt(var[i] - temp) : 0.0f; } - sort(vec.begin(), vec.end()); - float res = static_cast(sum[vec[0].second]); - - output->get_value(x, y, ch) = res; + /* Choose the region with lowest variance */ + float min_var = FLT_MAX; + int min_index = 0; + for (int i = 0; i < 4; i++) { + if (var[i] < min_var) { + min_var = var[i]; + min_index = i; + } + } + output->get_value(x, y, ch) = sum[min_index]; } } } diff --git a/source/blender/compositor/operations/COM_KuwaharaOperation.h b/source/blender/compositor/operations/COM_KuwaharaOperation.h index a2fdcabb7e2..d62837b500c 100644 --- a/source/blender/compositor/operations/COM_KuwaharaOperation.h +++ b/source/blender/compositor/operations/COM_KuwaharaOperation.h @@ -22,14 +22,13 @@ class KuwaharaOperation : public MultiThreadedOperation { void set_kernel_size(int kernel_size); int get_kernel_size(); - + void set_variation(int variation); int get_variation(); void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span inputs) override; - }; } // namespace blender::compositor diff --git a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc index 13d0e0ccaed..e318a2f5474 100644 --- a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc +++ b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc @@ -5,7 +5,6 @@ * \ingroup cmpnodes */ - //#include "COM_shader_node.hh" #include "COM_node_operation.hh" @@ -43,40 +42,6 @@ static void node_composit_buts_kuwahara(uiLayout *layout, bContext * /*C*/, Poin uiItemR(col, ptr, "kernel_size", 0, nullptr, ICON_NONE); } - - -//using namespace blender::realtime_compositor; -// -//class KuwaharaShaderNode : 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"); -// } -// using ShaderNode::ShaderNode; -// -// void compile(GPUMaterial *material) override -// { -// GPUNodeStack *inputs = get_inputs_array(); -// GPUNodeStack *outputs = get_outputs_array(); -// -// GPU_stack_link(material, &bnode(), "node_composite_kuwahara", inputs, outputs); -// } -//}; -// -//static NodeOperation *get_compositor_operation(Context *context, DNode node) -//{ -// return new KuwaharaOperation(context, node); -//} - -//static ShaderNode *get_compositor_shader_node(DNode node) -//{ -// return new KuwaharaShaderNode(node); -//} - } // namespace blender::nodes::node_composite_kuwahara_cc void register_node_type_cmp_kuwahara() @@ -91,7 +56,7 @@ void register_node_type_cmp_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_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } -- 2.30.2 From bd12c2bd8e086a872b7c92cd1715cfb7ef93e974 Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Tue, 25 Apr 2023 18:59:52 +0200 Subject: [PATCH 05/14] Compositor Kuwahara filter: Implement anisotropic variation --- source/blender/compositor/CMakeLists.txt | 6 +- .../compositor/nodes/COM_KuwaharaNode.cc | 105 ++++++++++- .../COM_FastGaussianBlurOperation.cc | 10 ++ .../COM_FastGaussianBlurOperation.h | 2 + .../COM_KuwaharaAnisotropicOperation.cc | 166 ++++++++++++++++++ .../COM_KuwaharaAnisotropicOperation.h | 30 ++++ ...ion.cc => COM_KuwaharaClassicOperation.cc} | 52 ++---- ...ation.h => COM_KuwaharaClassicOperation.h} | 10 +- source/blender/makesdna/DNA_node_types.h | 8 +- source/blender/makesrna/intern/rna_nodetree.c | 8 + .../nodes/node_composite_kuwahara.cc | 13 +- 11 files changed, 357 insertions(+), 53 deletions(-) create mode 100644 source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc create mode 100644 source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h rename source/blender/compositor/operations/{COM_KuwaharaOperation.cc => COM_KuwaharaClassicOperation.cc} (63%) rename source/blender/compositor/operations/{COM_KuwaharaOperation.h => COM_KuwaharaClassicOperation.h} (76%) diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index d8dc5b7c63b..23b2425a625 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -350,8 +350,10 @@ if(WITH_COMPOSITOR_CPU) operations/COM_GaussianXBlurOperation.h operations/COM_GaussianYBlurOperation.cc operations/COM_GaussianYBlurOperation.h - operations/COM_KuwaharaOperation.h - operations/COM_KuwaharaOperation.cc + operations/COM_KuwaharaClassicOperation.h + operations/COM_KuwaharaClassicOperation.cc + operations/COM_KuwaharaAnisotropicOperation.h + operations/COM_KuwaharaAnisotropicOperation.cc operations/COM_MovieClipAttributeOperation.cc operations/COM_MovieClipAttributeOperation.h operations/COM_MovieDistortionOperation.cc diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.cc b/source/blender/compositor/nodes/COM_KuwaharaNode.cc index a5cb21dc7c8..40c54968201 100644 --- a/source/blender/compositor/nodes/COM_KuwaharaNode.cc +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.cc @@ -1,8 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2011 Blender Foundation. */ + * Copyright 2023 Blender Foundation. */ #include "COM_KuwaharaNode.h" -#include "COM_KuwaharaOperation.h" +#include "COM_FastGaussianBlurOperation.h" +#include "COM_KuwaharaAnisotropicOperation.h" +#include "COM_KuwaharaClassicOperation.h" +#include "COM_MathBaseOperation.h" +#include "COM_SetValueOperation.h" namespace blender::compositor { @@ -12,13 +16,98 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter, const bNode *node = this->get_bnode(); const NodeKuwaharaData *data = (const NodeKuwaharaData *)node->storage; - KuwaharaOperation *operation = new KuwaharaOperation(); - operation->set_kernel_size(data->kernel_size); - operation->set_variation(data->variation); + switch (data->variation) { + case CMP_NODE_KUWAHARA_CLASSIC: { + KuwaharaClassicOperation *operation = new KuwaharaClassicOperation(); + operation->set_kernel_size(data->kernel_size); - converter.add_operation(operation); - converter.map_input_socket(get_input_socket(0), operation->get_input_socket(0)); - converter.map_output_socket(get_output_socket(0), operation->get_output_socket()); + converter.add_operation(operation); + converter.map_input_socket(get_input_socket(0), operation->get_input_socket(0)); + converter.map_output_socket(get_output_socket(0), operation->get_output_socket()); + } break; + + case CMP_NODE_KUWAHARA_ANISOTROPIC: { + /* Edge detection */ + auto const_fact = new SetValueOperation(); + const_fact->set_value(1.0f); + converter.add_operation(const_fact); + + auto sobel_x = new ConvolutionFilterOperation(); + sobel_x->set3x3Filter(1, 0, -1, 2, 0, -2, 1, 0, -1); + converter.add_operation(sobel_x); + converter.map_input_socket(get_input_socket(0), sobel_x->get_input_socket(0)); + converter.add_link(const_fact->get_output_socket(0), sobel_x->get_input_socket(1)); + + auto sobel_y = new ConvolutionFilterOperation(); + sobel_y->set3x3Filter(1, 2, 1, 0, 0, 0, -1, -2, -1); + converter.add_operation(sobel_y); + converter.map_input_socket(get_input_socket(0), sobel_y->get_input_socket(0)); + converter.add_link(const_fact->get_output_socket(0), sobel_y->get_input_socket(1)); + + /* Compute intensity of edges */ + auto sobel_xx = new MathMultiplyOperation(); + auto sobel_yy = new MathMultiplyOperation(); + auto sobel_xy = new MathMultiplyOperation(); + converter.add_operation(sobel_xx); + converter.add_operation(sobel_yy); + converter.add_operation(sobel_xy); + + converter.add_link(sobel_x->get_output_socket(0), sobel_xx->get_input_socket(0)); + converter.add_link(sobel_x->get_output_socket(0), sobel_xx->get_input_socket(1)); + + converter.add_link(sobel_y->get_output_socket(0), sobel_yy->get_input_socket(0)); + converter.add_link(sobel_y->get_output_socket(0), sobel_yy->get_input_socket(1)); + + 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. */ + // Note: blurring doesn't make as big of a difference as I was expecting, + // especially around edges. + // Todo: investigate further and remove if necessary. For now the parameter is kept for + // better user feedback + float sigma = data->sigma; + + auto blur_sobel_xx = new FastGaussianBlurOperation(); + auto blur_sobel_yy = new FastGaussianBlurOperation(); + auto blur_sobel_xy = new FastGaussianBlurOperation(); + + blur_sobel_yy->set_size(sigma, sigma); + blur_sobel_xx->set_size(sigma, sigma); + blur_sobel_xy->set_size(sigma, sigma); + + converter.add_operation(blur_sobel_xx); + converter.add_operation(blur_sobel_yy); + converter.add_operation(blur_sobel_xy); + + converter.add_link(sobel_xx->get_output_socket(0), blur_sobel_xx->get_input_socket(0)); + 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); + converter.map_input_socket(get_input_socket(0), aniso->get_input_socket(0)); + converter.add_operation(aniso); + + converter.add_link(blur_sobel_xx->get_output_socket(0), aniso->get_input_socket(1)); + converter.add_link(blur_sobel_yy->get_output_socket(0), aniso->get_input_socket(2)); + converter.add_link(blur_sobel_xy->get_output_socket(0), aniso->get_input_socket(3)); + + 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; + } + } } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc b/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc index 7557d1bc4bd..6f860c0f549 100644 --- a/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc +++ b/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc @@ -10,6 +10,7 @@ namespace blender::compositor { FastGaussianBlurOperation::FastGaussianBlurOperation() : BlurBaseOperation(DataType::Color) { iirgaus_ = nullptr; + data_.filtertype = R_FILTER_FAST_GAUSS; } void FastGaussianBlurOperation::execute_pixel(float output[4], int x, int y, void *data) @@ -67,6 +68,15 @@ void FastGaussianBlurOperation::deinit_execution() BlurBaseOperation::deinit_mutex(); } +void FastGaussianBlurOperation::set_size(int size_x, int size_y) +{ + // todo: there should be a better way to use the operation without knowing specifics of the blur + // node (data_) Could use factory pattern to solve this problem. + data_.sizex = size_x; + data_.sizey = size_y; + sizeavailable_ = true; +} + void *FastGaussianBlurOperation::initialize_tile_data(rcti *rect) { lock_mutex(); diff --git a/source/blender/compositor/operations/COM_FastGaussianBlurOperation.h b/source/blender/compositor/operations/COM_FastGaussianBlurOperation.h index 0f7064c19a5..8b0dd7a1052 100644 --- a/source/blender/compositor/operations/COM_FastGaussianBlurOperation.h +++ b/source/blender/compositor/operations/COM_FastGaussianBlurOperation.h @@ -27,6 +27,8 @@ class FastGaussianBlurOperation : public BlurBaseOperation { void deinit_execution() override; void init_execution() override; + void set_size(int size_x, int size_y); + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_started(MemoryBuffer *output, const rcti &area, diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc new file mode 100644 index 00000000000..7189f638258 --- /dev/null +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +#include "COM_KuwaharaAnisotropicOperation.h" +#include "BLI_vector.hh" + +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_output_socket(DataType::Color); + // output for debug + // todo: remove + this->add_output_socket(DataType::Color); + this->add_output_socket(DataType::Color); + this->add_output_socket(DataType::Color); + + this->set_kernel_size(8); + + this->flags_.is_fullframe_operation = true; +} + +void KuwaharaAnisotropicOperation::init_execution() +{ + image_reader_ = this->get_input_socket_reader(0); +} + +void KuwaharaAnisotropicOperation::deinit_execution() +{ + image_reader_ = nullptr; +} + +void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], + float x, + float y, + PixelSampler sampler) +{ + /* Not implemented */ +} + +void KuwaharaAnisotropicOperation::set_kernel_size(int kernel_size) +{ + kernel_size_ = kernel_size; +} + +int KuwaharaAnisotropicOperation::get_kernel_size() +{ + return kernel_size_; +} + +void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span inputs) +{ + /* + 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). + */ + + MemoryBuffer *image = inputs[0]; + MemoryBuffer *s_xx = inputs[1]; + MemoryBuffer *s_yy = inputs[2]; + MemoryBuffer *s_xy = inputs[3]; + + // BLI_assert all inputs have same size + + int n_div = 8; // recommended by authors in original paper + double angle = 2.0 * M_PI / n_div; + double q = 3.0; + const float EPS = 1.0e-10; + + + for (BuffersIterator it = output->iterate_with(inputs, area); !it.is_end(); ++it) { + const int x = it.x; + const int y = it.y; + + /* Compute orientation */ + // todo: make orientation separate operation + + // for now use green channel to compute orientation + // todo: convert to HSV and compute orientation and strength on luminance channel + const float a = s_xx->get_value(x, y, 1); + 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 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); + const double strength = (lambda1 == 0 && lambda2 == 0) ? 0 : (lambda1 - lambda2) / (lambda1 + lambda2); + + for(int ch = 0; ch < 3; ch++) { + // todo: compute anisotropy and weights on luminance channel to avoid color artifacts + + Vector sum(n_div, 0.0f); + Vector var(n_div, 0.0f); + Vector weight(n_div, 0.0f); + + float sx = 1.0f / (strength + 1.0f); + float sy = (1.0f + strength) / 1.0f; + float theta = -orientation; + + for (int dy = -kernel_size_; dy <= kernel_size_; dy++) { + for (int dx = -kernel_size_; dx <= kernel_size_; dx++) { + if (dx == 0 && dy == 0) continue; + + // rotate and scale the kernel. This is the "anisotropic" part. + int dx2 = static_cast(sx * (cos(theta) * dx - sin(theta) * dy)); + int dy2 = static_cast(sy * (sin(theta) * dx + cos(theta) * dy)); + int xx = x + dx2; + int yy = y + dy2; + + if (xx >= 0 && yy >= 0 && xx < image->get_width() && yy < image->get_height()) { + float ddx2 = (float)dx2; + float ddy2 = (float)dy2; + float theta = atan2(ddy2, ddx2) + M_PI; + int t = static_cast(floor(theta / angle)) % n_div; + + float d2 = dx2 * dx2 + dy2 * dy2; + float g = exp(-d2 / (2.0 * kernel_size_)); + float v = image->get_value(xx, yy, ch); + sum[t] += g * v; + var[t] += g * v * v; + weight[t] += g; + } + } + } + + // Calculate weighted average + float de = 0.0; + float nu = 0.0; + for (int i = 0; i < n_div; i++) { + sum[i] = weight[i] != 0 ? sum[i] / weight[i] : 0.0; + var[i] = weight[i] != 0 ? var[i] / weight[i] : 0.0; + var[i] = var[i] - sum[i] * sum[i]; + var[i] = var[i] > EPS ? sqrt(var[i]) : EPS; + float w = powf(var[i], -q); + + de += sum[i] * w; + nu += w; + } + + float val = nu > EPS ? de / nu : 0.0; + CLAMP_MAX(val, 1.0f); + it.out[ch] = val; + } + + /* No changes for alpha channel */ + it.out[3] = image->get_value(x, y, 3); + } +} + + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h new file mode 100644 index 00000000000..e1b921a5bd3 --- /dev/null +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2023 Blender Foundation. */ + +#pragma once + +#include "COM_MultiThreadedOperation.h" + +namespace blender::compositor { + +class KuwaharaAnisotropicOperation : public MultiThreadedOperation { + SocketReader *image_reader_; + + int kernel_size_; + + public: + KuwaharaAnisotropicOperation(); + + void init_execution() override; + void deinit_execution() override; + void execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) override; + + void set_kernel_size(int kernel_size); + int get_kernel_size(); + + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span inputs) override; +}; + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_KuwaharaOperation.cc b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc similarity index 63% rename from source/blender/compositor/operations/COM_KuwaharaOperation.cc rename to source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc index 33a77eefcc8..74ca4865153 100644 --- a/source/blender/compositor/operations/COM_KuwaharaOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc @@ -1,66 +1,50 @@ /* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2011 Blender Foundation. */ + * Copyright 2023 Blender Foundation. */ -#include "COM_KuwaharaOperation.h" +#include "COM_KuwaharaClassicOperation.h" namespace blender::compositor { -KuwaharaOperation::KuwaharaOperation() +KuwaharaClassicOperation::KuwaharaClassicOperation() { this->add_input_socket(DataType::Color); this->add_output_socket(DataType::Color); - this->set_kernel_size(4.4f); + this->set_kernel_size(4); this->flags_.is_fullframe_operation = true; } -void KuwaharaOperation::init_execution() +void KuwaharaClassicOperation::init_execution() { image_reader_ = this->get_input_socket_reader(0); } -void KuwaharaOperation::deinit_execution() +void KuwaharaClassicOperation::deinit_execution() { image_reader_ = nullptr; } -void KuwaharaOperation::execute_pixel_sampled(float output[4], - float x, - float y, - PixelSampler sampler) +void KuwaharaClassicOperation::execute_pixel_sampled(float output[4], + float x, + float y, + PixelSampler sampler) { - float input_value[4]; - image_reader_->read_sampled(input_value, x, y, sampler); - - output[0] = input_value[0] + 1.0; - output[1] = input_value[1] + 2.0; - output[2] = input_value[2] + 3.0; - output[3] = input_value[3] + 4.0; + /* Not implemented */ } -void KuwaharaOperation::set_kernel_size(int kernel_size) +void KuwaharaClassicOperation::set_kernel_size(int kernel_size) { kernel_size_ = kernel_size; } -int KuwaharaOperation::get_kernel_size() +int KuwaharaClassicOperation::get_kernel_size() { return kernel_size_; } -void KuwaharaOperation::set_variation(int variation) -{ - variation_ = variation; -} - -int KuwaharaOperation::get_variation() -{ - return variation_; -} - -void KuwaharaOperation::update_memory_buffer_partial(MemoryBuffer *output, - const rcti &area, - Span inputs) +void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span inputs) { MemoryBuffer *image = inputs[0]; @@ -74,13 +58,13 @@ void KuwaharaOperation::update_memory_buffer_partial(MemoryBuffer *output, float var[4] = {0.0f, 0.0f, 0.0f, 0.0f}; int cnt[4] = {0, 0, 0, 0}; - /* Split surroundings of */ + /* Split surroundings of pixel into 4 overlapping regions */ for (int dy = -kernel_size_; dy <= kernel_size_; dy++) { for (int dx = -kernel_size_; dx <= kernel_size_; dx++) { int xx = x + dx; int yy = y + dy; - if (xx >= 0 && yy >= 0 && xx < area.xmax && yy < area.ymax) { + if (xx >= 0 && yy >= 0 && xx < image->get_width() && yy < image->get_height()) { float v; v = image->get_value(xx, yy, ch); diff --git a/source/blender/compositor/operations/COM_KuwaharaOperation.h b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.h similarity index 76% rename from source/blender/compositor/operations/COM_KuwaharaOperation.h rename to source/blender/compositor/operations/COM_KuwaharaClassicOperation.h index d62837b500c..0164e94dbb3 100644 --- a/source/blender/compositor/operations/COM_KuwaharaOperation.h +++ b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2011 Blender Foundation. */ + * Copyright 2023 Blender Foundation. */ #pragma once @@ -7,14 +7,13 @@ namespace blender::compositor { -class KuwaharaOperation : public MultiThreadedOperation { +class KuwaharaClassicOperation : public MultiThreadedOperation { SocketReader *image_reader_; int kernel_size_; - int variation_; public: - KuwaharaOperation(); + KuwaharaClassicOperation(); void init_execution() override; void deinit_execution() override; @@ -23,9 +22,6 @@ class KuwaharaOperation : public MultiThreadedOperation { void set_kernel_size(int kernel_size); int get_kernel_size(); - void set_variation(int variation); - int get_variation(); - void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span inputs) override; diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 771f07805ab..01da062d547 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -879,7 +879,7 @@ typedef struct NodeBilateralBlurData { typedef struct NodeKuwaharaData { short kernel_size; short variation; - char _pad[4]; + float sigma; } NodeKuwaharaData; typedef struct NodeAntiAliasingData { @@ -2102,6 +2102,12 @@ typedef enum CMPNodeGlareType { CMP_NODE_GLARE_GHOST = 3, } CMPNodeGlareType; +/* Kuwahara Node. Stored in variation */ +typedef enum CMPNodeKuwahara { + CMP_NODE_KUWAHARA_CLASSIC = 0, + CMP_NODE_KUWAHARA_ANISOTROPIC = 1, +} CMPNodeKuwahara; + /* Plane track deform node. */ enum { diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index b958403cf4a..f0a11622436 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9265,6 +9265,14 @@ static void def_cmp_kuwahara(StructRNA *srna) RNA_def_property_enum_items(prop, variation_items); 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_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, NULL, "sigma"); + RNA_def_property_ui_text( + prop, + "Sigma", + "Edges get smoothed before applying filter. Sigma controls smoothing degree."); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } static void def_cmp_antialiasing(StructRNA *srna) diff --git a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc index e318a2f5474..ef3b0ef2598 100644 --- a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc +++ b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2020 Blender Foundation */ + * Copyright 2023 Blender Foundation */ /** \file * \ingroup cmpnodes @@ -22,6 +22,11 @@ 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(N_("Image")); + + // For debug. Todo:remove +// b.add_output(N_("Sobel x")); +// b.add_output(N_("Sobel xx blurred")); +// b.add_output(N_("Sobel xy blurred")); } static void node_composit_init_kuwahara(bNodeTree * /*ntree*/, bNode *node) @@ -40,6 +45,12 @@ static void node_composit_buts_kuwahara(uiLayout *layout, bContext * /*C*/, Poin uiItemR(col, ptr, "variation", 0, nullptr, ICON_NONE); uiItemR(col, ptr, "kernel_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); + } } } // namespace blender::nodes::node_composite_kuwahara_cc -- 2.30.2 From 104f744ed67b3f7f2ff12ffade7e72a53c09286f Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Sun, 30 Apr 2023 07:49:19 +0200 Subject: [PATCH 06/14] Select region based on luminance instead of single channel values --- .../compositor/nodes/COM_KuwaharaNode.cc | 6 +-- .../COM_KuwaharaAnisotropicOperation.cc | 38 ++++++++++++------- .../COM_KuwaharaClassicOperation.cc | 34 +++++++++++------ source/blender/makesdna/DNA_node_types.h | 2 +- source/blender/makesrna/intern/rna_nodetree.c | 10 +++-- 5 files changed, 56 insertions(+), 34 deletions(-) diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.cc b/source/blender/compositor/nodes/COM_KuwaharaNode.cc index 40c54968201..96a67c6e2e4 100644 --- a/source/blender/compositor/nodes/COM_KuwaharaNode.cc +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.cc @@ -62,11 +62,7 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter, converter.add_link(sobel_y->get_output_socket(0), sobel_xy->get_input_socket(1)); /* blurring for more robustness. */ - // Note: blurring doesn't make as big of a difference as I was expecting, - // especially around edges. - // Todo: investigate further and remove if necessary. For now the parameter is kept for - // better user feedback - float sigma = data->sigma; + const int sigma = data->sigma; auto blur_sobel_xx = new FastGaussianBlurOperation(); auto blur_sobel_yy = new FastGaussianBlurOperation(); diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc index 7189f638258..18e7ffc0f94 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc @@ -4,6 +4,10 @@ #include "COM_KuwaharaAnisotropicOperation.h" #include "BLI_vector.hh" +extern "C" { +#include "IMB_colormanagement.h" +} + namespace blender::compositor { KuwaharaAnisotropicOperation::KuwaharaAnisotropicOperation() @@ -69,11 +73,16 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou MemoryBuffer *s_yy = inputs[2]; MemoryBuffer *s_xy = inputs[3]; - // BLI_assert all inputs have same size + BLI_assert(image->get_width() == s_xx->get_width()); + BLI_assert(image->get_height() == s_xx->get_height()); + BLI_assert(image->get_width() == s_yy->get_width()); + BLI_assert(image->get_height() == s_yy->get_height()); + BLI_assert(image->get_width() == s_xy->get_width()); + BLI_assert(image->get_height() == s_xy->get_height()); - int n_div = 8; // recommended by authors in original paper - double angle = 2.0 * M_PI / n_div; - double q = 3.0; + 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; @@ -81,9 +90,6 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou const int x = it.x; const int y = it.y; - /* Compute orientation */ - // todo: make orientation separate operation - // for now use green channel to compute orientation // todo: convert to HSV and compute orientation and strength on luminance channel const float a = s_xx->get_value(x, y, 1); @@ -102,8 +108,8 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou const double strength = (lambda1 == 0 && lambda2 == 0) ? 0 : (lambda1 - lambda2) / (lambda1 + lambda2); for(int ch = 0; ch < 3; ch++) { - // todo: compute anisotropy and weights on luminance channel to avoid color artifacts + Vector mean(n_div, 0.0f); Vector sum(n_div, 0.0f); Vector var(n_div, 0.0f); Vector weight(n_div, 0.0f); @@ -127,12 +133,17 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou float ddy2 = (float)dy2; float theta = atan2(ddy2, ddx2) + M_PI; int t = static_cast(floor(theta / angle)) % n_div; - float d2 = dx2 * dx2 + dy2 * dy2; float g = exp(-d2 / (2.0 * kernel_size_)); float v = image->get_value(xx, yy, ch); - sum[t] += g * v; - var[t] += g * v * v; + float color[4]; + image->read_elem(xx, yy, color); + // todo(zazizizou): only compute lum once per region + const float lum = IMB_colormanagement_get_luminance(color); + // todo(zazizizou): only compute mean for the selected region + mean[t] += g * v; + sum[t] += g * lum; + var[t] += g * lum * lum; weight[t] += g; } } @@ -142,13 +153,14 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou float de = 0.0; float nu = 0.0; for (int i = 0; i < n_div; i++) { + mean[i] = weight[i] != 0 ? mean[i] / weight[i] : 0.0; sum[i] = weight[i] != 0 ? sum[i] / weight[i] : 0.0; var[i] = weight[i] != 0 ? var[i] / weight[i] : 0.0; var[i] = var[i] - sum[i] * sum[i]; - var[i] = var[i] > EPS ? sqrt(var[i]) : EPS; + var[i] = var[i] > FLT_EPSILON ? sqrt(var[i]) : FLT_EPSILON; float w = powf(var[i], -q); - de += sum[i] * w; + de += mean[i] * w; nu += w; } diff --git a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc index 74ca4865153..0ff85db3d3f 100644 --- a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc @@ -3,6 +3,10 @@ #include "COM_KuwaharaClassicOperation.h" +extern "C" { +#include "IMB_colormanagement.h" +} + namespace blender::compositor { KuwaharaClassicOperation::KuwaharaClassicOperation() @@ -55,6 +59,7 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output for (int ch = 0; ch < 3; ch++) { float sum[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float mean[4] = {0.0f, 0.0f, 0.0f, 0.0f}; float var[4] = {0.0f, 0.0f, 0.0f, 0.0f}; int cnt[4] = {0, 0, 0, 0}; @@ -65,30 +70,36 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output int xx = x + dx; int yy = y + dy; if (xx >= 0 && yy >= 0 && xx < image->get_width() && yy < image->get_height()) { - float v; - v = image->get_value(xx, yy, ch); + const float v = image->get_value(xx, yy, ch); + float color[4]; + image->read_elem(xx, yy, color); + const float lum = IMB_colormanagement_get_luminance(color); if (dx <= 0 && dy <= 0) { - sum[0] += v; - var[0] += v * v; + mean[0] += v; + sum[0] += lum; + var[0] += lum * lum; cnt[0]++; } if (dx >= 0 && dy <= 0) { - sum[1] += v; - var[1] += v * v; + mean[1] += v; + sum[1] += lum; + var[1] += lum * lum; cnt[1]++; } if (dx <= 0 && dy >= 0) { - sum[2] += v; - var[2] += v * v; + mean[2] += v; + sum[2] += lum; + var[2] += lum * lum; cnt[2]++; } if (dx >= 0 && dy >= 0) { - sum[3] += v; - var[3] += v * v; + mean[3] += v; + sum[3] += lum; + var[3] += lum * lum; cnt[3]++; } } @@ -97,6 +108,7 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output /* Compute region variances */ for (int i = 0; i < 4; i++) { + mean[i] = cnt[i] != 0 ? mean[i] / cnt[i] : 0.0f; sum[i] = cnt[i] != 0 ? sum[i] / cnt[i] : 0.0f; var[i] = cnt[i] != 0 ? var[i] / cnt[i] : 0.0f; const float temp = sum[i] * sum[i]; @@ -112,7 +124,7 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output min_index = i; } } - output->get_value(x, y, ch) = sum[min_index]; + output->get_value(x, y, ch) = mean[min_index]; } } } diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 01da062d547..915c29da4a4 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -879,7 +879,7 @@ typedef struct NodeBilateralBlurData { typedef struct NodeKuwaharaData { short kernel_size; short variation; - float sigma; + int sigma; } NodeKuwaharaData; typedef struct NodeAntiAliasingData { diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index f0a11622436..9757039cb4f 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9256,8 +9256,9 @@ static void def_cmp_kuwahara(StructRNA *srna) prop = RNA_def_property(srna, "kernel_size", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "kernel_size"); + RNA_def_property_ui_range(prop, 4, 50, 1, -1); RNA_def_property_ui_text( - prop, "Kernel Size", "Kernel size of filter. The larger the stronger the effect"); + prop, "Kernel Size", "Kernel 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); @@ -9266,12 +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_FLOAT, PROP_NONE); - RNA_def_property_float_sdna(prop, NULL, "sigma"); + prop = RNA_def_property(srna, "sigma", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "sigma"); + RNA_def_property_ui_range(prop, 0, 50, 1, -1); RNA_def_property_ui_text( prop, "Sigma", - "Edges get smoothed before applying filter. Sigma controls smoothing degree."); + "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"); } -- 2.30.2 From b76e5025bff5867fb57d2538eb6ee5ff625ca280 Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Mon, 1 May 2023 17:33:55 +0200 Subject: [PATCH 07/14] Changed defaults and parameter naming after user feedback --- .../compositor/nodes/COM_KuwaharaNode.cc | 17 ++------ .../COM_KuwaharaAnisotropicOperation.cc | 22 +++++----- source/blender/makesdna/DNA_node_types.h | 4 +- source/blender/makesrna/intern/rna_nodetree.c | 18 ++++---- .../nodes/node_composite_kuwahara.cc | 43 +++++++++++++------ 5 files changed, 55 insertions(+), 49 deletions(-) diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.cc b/source/blender/compositor/nodes/COM_KuwaharaNode.cc index 96a67c6e2e4..b2257bc2ca6 100644 --- a/source/blender/compositor/nodes/COM_KuwaharaNode.cc +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.cc @@ -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; } } diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc index 18e7ffc0f94..45356e166f1 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc @@ -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 */ } 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 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); - 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 mean(n_div, 0.0f); Vector 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(sx * (cos(theta) * dx - sin(theta) * dy)); @@ -174,5 +175,4 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou } } - } // namespace blender::compositor diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 915c29da4a4..4a174f10470 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -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 { diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 9757039cb4f..a4b3244655d 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -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); 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"); } diff --git a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc index ef3b0ef2598..6876fabfa9f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc +++ b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc @@ -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" /* **************** 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(N_("Image")); - - // For debug. Todo:remove -// b.add_output(N_("Sobel x")); -// b.add_output(N_("Sobel xx blurred")); -// b.add_output(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(__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); } -- 2.30.2 From f0780f9315067ef9a2da985a81bbd6389af710da Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Sat, 6 May 2023 22:10:48 +0200 Subject: [PATCH 08/14] Tiled implementation and eliminated artefacts --- .../compositor/nodes/COM_KuwaharaNode.cc | 2 +- .../compositor/nodes/COM_KuwaharaNode.h | 2 +- .../COM_FastGaussianBlurOperation.cc | 4 +- .../COM_KuwaharaAnisotropicOperation.cc | 218 ++++++++++++++---- .../COM_KuwaharaAnisotropicOperation.h | 5 + .../COM_KuwaharaClassicOperation.cc | 70 +++++- source/blender/makesrna/intern/rna_nodetree.c | 2 +- .../nodes/node_composite_kuwahara.cc | 1 - 8 files changed, 252 insertions(+), 52 deletions(-) diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.cc b/source/blender/compositor/nodes/COM_KuwaharaNode.cc index b2257bc2ca6..c53298b1ed4 100644 --- a/source/blender/compositor/nodes/COM_KuwaharaNode.cc +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.cc @@ -82,7 +82,7 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter, /* Apply anisotropic Kuwahara filter */ KuwaharaAnisotropicOperation *aniso = new KuwaharaAnisotropicOperation(); - aniso->set_kernel_size(data->size); + aniso->set_kernel_size(data->size + 4); converter.map_input_socket(get_input_socket(0), aniso->get_input_socket(0)); converter.add_operation(aniso); diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.h b/source/blender/compositor/nodes/COM_KuwaharaNode.h index 1b8426154e2..8b2f9b008d2 100644 --- a/source/blender/compositor/nodes/COM_KuwaharaNode.h +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2011 Blender Foundation. */ + * Copyright 2023 Blender Foundation. */ #pragma once diff --git a/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc b/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc index 6f860c0f549..8a69284ae34 100644 --- a/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc +++ b/source/blender/compositor/operations/COM_FastGaussianBlurOperation.cc @@ -70,8 +70,8 @@ void FastGaussianBlurOperation::deinit_execution() void FastGaussianBlurOperation::set_size(int size_x, int size_y) { - // todo: there should be a better way to use the operation without knowing specifics of the blur - // node (data_) Could use factory pattern to solve this problem. + /* TODO: there should be a better way to use the operation without knowing specifics of the blur + * node (i.e. data_). We could use factory pattern to solve this problem. */ data_.sizex = size_x; data_.sizey = size_y; sizeavailable_ = true; diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc index 45356e166f1..bf3ad490bda 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc @@ -17,14 +17,10 @@ KuwaharaAnisotropicOperation::KuwaharaAnisotropicOperation() this->add_input_socket(DataType::Color); this->add_input_socket(DataType::Color); - this->add_output_socket(DataType::Color); - // output for debug - // todo: remove - this->add_output_socket(DataType::Color); - this->add_output_socket(DataType::Color); this->add_output_socket(DataType::Color); - this->set_kernel_size(8); + this->n_div_ = 8; + this->set_kernel_size(5); this->flags_.is_fullframe_operation = true; } @@ -32,6 +28,9 @@ KuwaharaAnisotropicOperation::KuwaharaAnisotropicOperation() void KuwaharaAnisotropicOperation::init_execution() { image_reader_ = this->get_input_socket_reader(0); + s_xx_reader_ = this->get_input_socket_reader(1); + s_yy_reader_ = this->get_input_socket_reader(2); + s_xy_reader_ = this->get_input_socket_reader(3); } void KuwaharaAnisotropicOperation::deinit_execution() @@ -44,12 +43,124 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], float y, PixelSampler sampler) { - /* Not implemented */ + BLI_assert(this->get_width() == s_xx_reader_->get_width()); + BLI_assert(this->get_height() == s_xx_reader_->get_height()); + BLI_assert(this->get_width() == s_yy_reader_->get_width()); + BLI_assert(this->get_height() == s_yy_reader_->get_height()); + BLI_assert(this->get_width() == s_xy_reader_->get_width()); + BLI_assert(this->get_height() == s_xy_reader_->get_height()); + + /* Values 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 now use green channel to compute orientation */ + /* todo: convert to HSV and compute orientation and strength on luminance channel */ + float tmp[4]; + s_xx_reader_->read(tmp, x, y, nullptr); + const float a = tmp[1]; + s_xy_reader_->read(tmp, x, y, nullptr); + const float b = tmp[1]; + s_yy_reader_->read(tmp, x, y, nullptr); + const float c = tmp[1]; + + /* Compute egenvalues of structure tensor */ + const double tr = a + 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); + const double strength = (lambda1 == 0 && lambda2 == 0) ? + 0 : + (lambda1 - lambda2) / (lambda1 + lambda2); + + for (int ch = 0; ch < 3; ch++) { + Vector mean(n_div_, 0.0f); + Vector sum(n_div_, 0.0f); + Vector var(n_div_, 0.0f); + Vector weight(n_div_, 0.0f); + + double sx = 1.0f / (strength + 1.0f); + double sy = (1.0f + strength) / 1.0f; + double theta = -orientation; + + for (int dy = -kernel_size_; dy <= kernel_size_; dy++) { + for (int dx = -kernel_size_; dx <= kernel_size_; dx++) { + if (dx == 0 && dy == 0) + continue; + + /* Rotate and scale the kernel. This is the "anisotropic" part. */ + int dx2 = (int)(sx * (cos(theta) * dx - sin(theta) * dy)); + int dy2 = (int)(sy * (sin(theta) * dx + cos(theta) * dy)); + int xx = x + dx2; + int yy = y + dy2; + + /* Clamp image to avoid artefacts at borders. */ + if (xx < 0) { + xx = 0; + } + if (xx >= this->get_width()) { + xx = this->get_width() - 1; + } + if (yy < 0) { + yy = 0; + } + if (yy >= this->get_height()) { + yy = this->get_height() - 1; + } + + double ddx2 = (double)dx2; + double ddy2 = (double)dy2; + double theta = atan2(ddy2, ddx2) + M_PI; + int t = static_cast(floor(theta / angle)) % n_div_; + double d2 = dx2 * dx2 + dy2 * dy2; + double g = exp(-d2 / (2.0 * kernel_size_)); + float color[4]; + image_reader_->read(color, xx, yy, nullptr); + const double v = color[ch]; + /* todo(zazizizou): only compute lum once per region */ + const float lum = IMB_colormanagement_get_luminance(color); + /* todo(zazizizou): only compute mean for the selected region */ + mean[t] += g * v; + sum[t] += g * lum; + var[t] += g * lum * lum; + weight[t] += g; + } + } + + /* Calculate weighted average */ + double de = 0.0; + double nu = 0.0; + for (int i = 0; i < n_div_; i++) { + mean[i] = weight[i] != 0 ? mean[i] / weight[i] : 0.0; + sum[i] = weight[i] != 0 ? sum[i] / weight[i] : 0.0; + var[i] = weight[i] != 0 ? var[i] / weight[i] : 0.0; + var[i] = var[i] - sum[i] * sum[i]; + var[i] = var[i] > FLT_EPSILON ? sqrt(var[i]) : FLT_EPSILON; + double w = powf(var[i], -q); + + de += mean[i] * w; + nu += w; + } + + double val = nu > EPS ? de / nu : 0.0; + CLAMP_MAX(val, 1.0f); + output[ch] = val; + } + + /* No changes for alpha channel */ + image_reader_->read_sampled(tmp, x, y, sampler); + output[3] = tmp[3]; } void KuwaharaAnisotropicOperation::set_kernel_size(int kernel_size) { - kernel_size_ = kernel_size; + /* Filter will be split into n_div. + * Add n_div / 2 to avoid artefacts such as random black pixels in image. */ + kernel_size_ = kernel_size + n_div_ / 2; } int KuwaharaAnisotropicOperation::get_kernel_size() @@ -57,6 +168,11 @@ int KuwaharaAnisotropicOperation::get_kernel_size() return kernel_size_; } +int KuwaharaAnisotropicOperation::get_n_div() +{ + return n_div_; +} + void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span inputs) @@ -80,8 +196,8 @@ 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 float angle = 2.0 * M_PI / n_div; + /* Values 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; @@ -89,8 +205,8 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou const int x = it.x; const int y = it.y; - // for now use green channel to compute orientation - // todo: convert to HSV and compute orientation and strength on luminance channel + /* For now use green channel to compute orientation */ + /* todo: convert to HSV and compute orientation and strength on luminance channel */ const float a = s_xx->get_value(x, y, 1); const float b = s_xy->get_value(x, y, 1); const float c = s_yy->get_value(x, y, 1); @@ -109,63 +225,75 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou for (int ch = 0; ch < 3; ch++) { - Vector mean(n_div, 0.0f); - Vector sum(n_div, 0.0f); - Vector var(n_div, 0.0f); - Vector weight(n_div, 0.0f); + Vector mean(n_div_, 0.0f); + Vector sum(n_div_, 0.0f); + Vector var(n_div_, 0.0f); + Vector weight(n_div_, 0.0f); - float sx = 1.0f / (strength + 1.0f); - float sy = (1.0f + strength) / 1.0f; - float theta = -orientation; + double sx = 1.0f / (strength + 1.0f); + double sy = (1.0f + strength) / 1.0f; + double theta = -orientation; for (int dy = -kernel_size_; dy <= kernel_size_; dy++) { for (int dx = -kernel_size_; dx <= kernel_size_; dx++) { if (dx == 0 && dy == 0) continue; - // rotate and scale the kernel. This is the "anisotropic" part. - int dx2 = static_cast(sx * (cos(theta) * dx - sin(theta) * dy)); - int dy2 = static_cast(sy * (sin(theta) * dx + cos(theta) * dy)); + /* Rotate and scale the kernel. This is the "anisotropic" part. */ + int dx2 = (int)(sx * (cos(theta) * dx - sin(theta) * dy)); + int dy2 = (int)(sy * (sin(theta) * dx + cos(theta) * dy)); int xx = x + dx2; int yy = y + dy2; - if (xx >= 0 && yy >= 0 && xx < image->get_width() && yy < image->get_height()) { - float ddx2 = (float)dx2; - float ddy2 = (float)dy2; - float theta = atan2(ddy2, ddx2) + M_PI; - int t = static_cast(floor(theta / angle)) % n_div; - float d2 = dx2 * dx2 + dy2 * dy2; - float g = exp(-d2 / (2.0 * kernel_size_)); - float v = image->get_value(xx, yy, ch); - float color[4]; - image->read_elem(xx, yy, color); - // todo(zazizizou): only compute lum once per region - const float lum = IMB_colormanagement_get_luminance(color); - // todo(zazizizou): only compute mean for the selected region - mean[t] += g * v; - sum[t] += g * lum; - var[t] += g * lum * lum; - weight[t] += g; + /* Clamp image to avoid artefacts at borders. */ + if (xx < 0) { + xx = 0; } + if (xx >= image->get_width()) { + xx = image->get_width() - 1; + } + if (yy < 0) { + yy = 0; + } + if (yy >= image->get_height()) { + yy = image->get_height() - 1; + } + + double ddx2 = (double)dx2; + double ddy2 = (double)dy2; + double theta = atan2(ddy2, ddx2) + M_PI; + int t = static_cast(floor(theta / angle)) % n_div_; + double d2 = dx2 * dx2 + dy2 * dy2; + double g = exp(-d2 / (2.0 * kernel_size_)); + double v = image->get_value(xx, yy, ch); + float color[4]; + image->read_elem(xx, yy, color); + /* todo(zazizizou): only compute lum once per region */ + const float lum = IMB_colormanagement_get_luminance(color); + /* todo(zazizizou): only compute mean for the selected region */ + mean[t] += g * v; + sum[t] += g * lum; + var[t] += g * lum * lum; + weight[t] += g; } } - // Calculate weighted average - float de = 0.0; - float nu = 0.0; - for (int i = 0; i < n_div; i++) { + /* Calculate weighted average */ + double de = 0.0; + double nu = 0.0; + for (int i = 0; i < n_div_; i++) { mean[i] = weight[i] != 0 ? mean[i] / weight[i] : 0.0; sum[i] = weight[i] != 0 ? sum[i] / weight[i] : 0.0; var[i] = weight[i] != 0 ? var[i] / weight[i] : 0.0; var[i] = var[i] - sum[i] * sum[i]; var[i] = var[i] > FLT_EPSILON ? sqrt(var[i]) : FLT_EPSILON; - float w = powf(var[i], -q); + double w = powf(var[i], -q); de += mean[i] * w; nu += w; } - float val = nu > EPS ? de / nu : 0.0; + double val = nu > EPS ? de / nu : 0.0; CLAMP_MAX(val, 1.0f); it.out[ch] = val; } diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h index e1b921a5bd3..db1732cf899 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h @@ -9,8 +9,12 @@ namespace blender::compositor { class KuwaharaAnisotropicOperation : public MultiThreadedOperation { SocketReader *image_reader_; + SocketReader *s_xx_reader_; + SocketReader *s_yy_reader_; + SocketReader *s_xy_reader_; int kernel_size_; + int n_div_; public: KuwaharaAnisotropicOperation(); @@ -21,6 +25,7 @@ class KuwaharaAnisotropicOperation : public MultiThreadedOperation { void set_kernel_size(int kernel_size); int get_kernel_size(); + int get_n_div(); void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, diff --git a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc index 0ff85db3d3f..77ba524ef31 100644 --- a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc @@ -33,7 +33,75 @@ void KuwaharaClassicOperation::execute_pixel_sampled(float output[4], float y, PixelSampler sampler) { - /* Not implemented */ + for (int ch = 0; ch < 3; ch++) { + float sum[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float mean[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float var[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + int cnt[4] = {0, 0, 0, 0}; + + /* Split surroundings of pixel into 4 overlapping regions */ + for (int dy = -kernel_size_; dy <= kernel_size_; dy++) { + for (int dx = -kernel_size_; dx <= kernel_size_; dx++) { + + int xx = x + dx; + int yy = y + dy; + if (xx >= 0 && yy >= 0 && xx < this->get_width() && yy < this->get_height()) { + float color[4]; + image_reader_->read_sampled(color, xx, yy, sampler); + const float v = color[ch]; + const float lum = IMB_colormanagement_get_luminance(color); + + if (dx <= 0 && dy <= 0) { + mean[0] += v; + sum[0] += lum; + var[0] += lum * lum; + cnt[0]++; + } + + if (dx >= 0 && dy <= 0) { + mean[1] += v; + sum[1] += lum; + var[1] += lum * lum; + cnt[1]++; + } + + if (dx <= 0 && dy >= 0) { + mean[2] += v; + sum[2] += lum; + var[2] += lum * lum; + cnt[2]++; + } + + if (dx >= 0 && dy >= 0) { + mean[3] += v; + sum[3] += lum; + var[3] += lum * lum; + cnt[3]++; + } + } + } + } + + /* Compute region variances */ + for (int i = 0; i < 4; i++) { + mean[i] = cnt[i] != 0 ? mean[i] / cnt[i] : 0.0f; + sum[i] = cnt[i] != 0 ? sum[i] / cnt[i] : 0.0f; + var[i] = cnt[i] != 0 ? var[i] / cnt[i] : 0.0f; + const float temp = sum[i] * sum[i]; + var[i] = var[i] > temp ? sqrt(var[i] - temp) : 0.0f; + } + + /* Choose the region with lowest variance */ + float min_var = FLT_MAX; + int min_index = 0; + for (int i = 0; i < 4; i++) { + if (var[i] < min_var) { + min_var = var[i]; + min_index = i; + } + } + output[ch] = mean[min_index]; + } } void KuwaharaClassicOperation::set_kernel_size(int kernel_size) diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index a4b3244655d..c1f8dcb6201 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9256,7 +9256,7 @@ static void def_cmp_kuwahara(StructRNA *srna) 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); + RNA_def_property_ui_range(prop, 1, 100, 1, -1); RNA_def_property_ui_text( prop, "Size", "Size of filter. Larger values give stronger stylized effect"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); diff --git a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc index 6876fabfa9f..deed7dec9f7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc +++ b/source/blender/nodes/composite/nodes/node_composite_kuwahara.cc @@ -6,7 +6,6 @@ */ #include "COM_node_operation.hh" -//#include "node_composite_util.hh" /* **************** Kuwahara ******************** */ -- 2.30.2 From f5a92f9fd6b920f51490065aa11feec856885ce3 Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Sat, 13 May 2023 13:15:40 +0200 Subject: [PATCH 09/14] Addressed comments in review & Code style changes --- .../COM_KuwaharaAnisotropicOperation.cc | 88 ++++++++++--------- source/blender/makesrna/intern/rna_nodetree.c | 2 + 2 files changed, 49 insertions(+), 41 deletions(-) diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc index bf3ad490bda..4b1faa4dc1c 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc @@ -43,12 +43,15 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], float y, PixelSampler sampler) { - BLI_assert(this->get_width() == s_xx_reader_->get_width()); - BLI_assert(this->get_height() == s_xx_reader_->get_height()); - BLI_assert(this->get_width() == s_yy_reader_->get_width()); - BLI_assert(this->get_height() == s_yy_reader_->get_height()); - BLI_assert(this->get_width() == s_xy_reader_->get_width()); - BLI_assert(this->get_height() == s_xy_reader_->get_height()); + const int width = this->get_width(); + const int height = this->get_width(); + + BLI_assert(width == s_xx_reader_->get_width()); + BLI_assert(height == s_xx_reader_->get_height()); + BLI_assert(width == s_yy_reader_->get_width()); + BLI_assert(height == s_yy_reader_->get_height()); + BLI_assert(width == s_xy_reader_->get_width()); + BLI_assert(height == s_xy_reader_->get_height()); /* Values recommended by authors in original paper. */ const float angle = 2.0 * M_PI / n_div_; @@ -93,8 +96,8 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], continue; /* Rotate and scale the kernel. This is the "anisotropic" part. */ - int dx2 = (int)(sx * (cos(theta) * dx - sin(theta) * dy)); - int dy2 = (int)(sy * (sin(theta) * dx + cos(theta) * dy)); + int dx2 = int(sx * (cos(theta) * dx - sin(theta) * dy)); + int dy2 = int(sy * (sin(theta) * dx + cos(theta) * dy)); int xx = x + dx2; int yy = y + dy2; @@ -102,20 +105,20 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], if (xx < 0) { xx = 0; } - if (xx >= this->get_width()) { - xx = this->get_width() - 1; + if (xx >= width) { + xx = width - 1; } if (yy < 0) { yy = 0; } - if (yy >= this->get_height()) { - yy = this->get_height() - 1; + if (yy >= height) { + yy = height - 1; } - double ddx2 = (double)dx2; - double ddy2 = (double)dy2; - double theta = atan2(ddy2, ddx2) + M_PI; - int t = static_cast(floor(theta / angle)) % n_div_; + const double ddx2 = double(dx2); + const double ddy2 = double(dy2); + const double theta = atan2(ddy2, ddx2) + M_PI; + const int t = int(floor(theta / angle)) % n_div_; double d2 = dx2 * dx2 + dy2 * dy2; double g = exp(-d2 / (2.0 * kernel_size_)); float color[4]; @@ -135,9 +138,10 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], double de = 0.0; double nu = 0.0; for (int i = 0; i < n_div_; i++) { - mean[i] = weight[i] != 0 ? mean[i] / weight[i] : 0.0; - sum[i] = weight[i] != 0 ? sum[i] / weight[i] : 0.0; - var[i] = weight[i] != 0 ? var[i] / weight[i] : 0.0; + double weight_inv = 1.0 / weight[i]; + mean[i] = weight[i] != 0 ? mean[i] * weight_inv : 0.0; + sum[i] = weight[i] != 0 ? sum[i] * weight_inv : 0.0; + var[i] = weight[i] != 0 ? var[i] * weight_inv : 0.0; var[i] = var[i] - sum[i] * sum[i]; var[i] = var[i] > FLT_EPSILON ? sqrt(var[i]) : FLT_EPSILON; double w = powf(var[i], -q); @@ -147,7 +151,6 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], } double val = nu > EPS ? de / nu : 0.0; - CLAMP_MAX(val, 1.0f); output[ch] = val; } @@ -189,12 +192,15 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou MemoryBuffer *s_yy = inputs[2]; MemoryBuffer *s_xy = inputs[3]; - BLI_assert(image->get_width() == s_xx->get_width()); - BLI_assert(image->get_height() == s_xx->get_height()); - BLI_assert(image->get_width() == s_yy->get_width()); - BLI_assert(image->get_height() == s_yy->get_height()); - BLI_assert(image->get_width() == s_xy->get_width()); - BLI_assert(image->get_height() == s_xy->get_height()); + const int width = image->get_width(); + const int height = image->get_width(); + + BLI_assert(width == s_xx->get_width()); + BLI_assert(height == s_xx->get_height()); + BLI_assert(width == s_yy->get_width()); + BLI_assert(height == s_yy->get_height()); + BLI_assert(width == s_xy->get_width()); + BLI_assert(height == s_xy->get_height()); /* Values recommended by authors in original paper. */ const float angle = 2.0 * M_PI / n_div_; @@ -240,8 +246,8 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou continue; /* Rotate and scale the kernel. This is the "anisotropic" part. */ - int dx2 = (int)(sx * (cos(theta) * dx - sin(theta) * dy)); - int dy2 = (int)(sy * (sin(theta) * dx + cos(theta) * dy)); + int dx2 = int(sx * (cos(theta) * dx - sin(theta) * dy)); + int dy2 = int(sy * (sin(theta) * dx + cos(theta) * dy)); int xx = x + dx2; int yy = y + dy2; @@ -249,23 +255,23 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou if (xx < 0) { xx = 0; } - if (xx >= image->get_width()) { - xx = image->get_width() - 1; + if (xx >= width) { + xx = width - 1; } if (yy < 0) { yy = 0; } - if (yy >= image->get_height()) { - yy = image->get_height() - 1; + if (yy >= height) { + yy = height - 1; } - double ddx2 = (double)dx2; - double ddy2 = (double)dy2; - double theta = atan2(ddy2, ddx2) + M_PI; - int t = static_cast(floor(theta / angle)) % n_div_; + const double ddx2 = double(dx2); + const double ddy2 = double(dy2); + const double theta = atan2(ddy2, ddx2) + M_PI; + const int t = int(floor(theta / angle)) % n_div_; double d2 = dx2 * dx2 + dy2 * dy2; double g = exp(-d2 / (2.0 * kernel_size_)); - double v = image->get_value(xx, yy, ch); + const double v = image->get_value(xx, yy, ch); float color[4]; image->read_elem(xx, yy, color); /* todo(zazizizou): only compute lum once per region */ @@ -282,9 +288,10 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou double de = 0.0; double nu = 0.0; for (int i = 0; i < n_div_; i++) { - mean[i] = weight[i] != 0 ? mean[i] / weight[i] : 0.0; - sum[i] = weight[i] != 0 ? sum[i] / weight[i] : 0.0; - var[i] = weight[i] != 0 ? var[i] / weight[i] : 0.0; + double weight_inv = 1.0 / weight[i]; + mean[i] = weight[i] != 0 ? mean[i] * weight_inv : 0.0; + sum[i] = weight[i] != 0 ? sum[i] * weight_inv : 0.0; + var[i] = weight[i] != 0 ? var[i] * weight_inv : 0.0; var[i] = var[i] - sum[i] * sum[i]; var[i] = var[i] > FLT_EPSILON ? sqrt(var[i]) : FLT_EPSILON; double w = powf(var[i], -q); @@ -294,7 +301,6 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou } double val = nu > EPS ? de / nu : 0.0; - CLAMP_MAX(val, 1.0f); it.out[ch] = val; } diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index e3e2cc958bf..2fd69f92b65 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9423,6 +9423,7 @@ static void def_cmp_kuwahara(StructRNA *srna) prop = RNA_def_property(srna, "size", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "size"); + RNA_def_property_range(prop, 1.0, 100.0); RNA_def_property_ui_range(prop, 1, 100, 1, -1); RNA_def_property_ui_text( prop, "Size", "Size of filter. Larger values give stronger stylized effect"); @@ -9436,6 +9437,7 @@ static void def_cmp_kuwahara(StructRNA *srna) prop = RNA_def_property(srna, "smoothing", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "smoothing"); + RNA_def_property_range(prop, 0.0, 50.0); RNA_def_property_ui_range(prop, 0, 50, 1, -1); RNA_def_property_ui_text(prop, "Smoothing", -- 2.30.2 From b2c3e604de97ed0523b5c77da34887f631e80d32 Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Sat, 13 May 2023 13:27:27 +0200 Subject: [PATCH 10/14] Fixed failing assert caused by typo in width and height --- .../compositor/operations/COM_KuwaharaAnisotropicOperation.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc index 4b1faa4dc1c..3b227b1c7d0 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc @@ -44,7 +44,7 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], PixelSampler sampler) { const int width = this->get_width(); - const int height = this->get_width(); + const int height = this->get_height(); BLI_assert(width == s_xx_reader_->get_width()); BLI_assert(height == s_xx_reader_->get_height()); @@ -193,7 +193,7 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou MemoryBuffer *s_xy = inputs[3]; const int width = image->get_width(); - const int height = image->get_width(); + const int height = image->get_height(); BLI_assert(width == s_xx->get_width()); BLI_assert(height == s_xx->get_height()); -- 2.30.2 From 8805335270dc3f18537ad29176c27e1221504c37 Mon Sep 17 00:00:00 2001 From: Habib Gahbiche Date: Thu, 8 Jun 2023 09:57:17 +0200 Subject: [PATCH 11/14] Addressed comments from code review + Code style --- .../compositor/nodes/COM_KuwaharaNode.cc | 3 +- .../COM_KuwaharaAnisotropicOperation.cc | 63 +++++++------------ .../operations/COM_SMAAOperation.cc | 4 +- 3 files changed, 26 insertions(+), 44 deletions(-) diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.cc b/source/blender/compositor/nodes/COM_KuwaharaNode.cc index c53298b1ed4..68c85f996e2 100644 --- a/source/blender/compositor/nodes/COM_KuwaharaNode.cc +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.cc @@ -24,7 +24,8 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter, converter.add_operation(operation); converter.map_input_socket(get_input_socket(0), operation->get_input_socket(0)); converter.map_output_socket(get_output_socket(0), operation->get_output_socket()); - } break; + break; + } case CMP_NODE_KUWAHARA_ANISOTROPIC: { /* Edge detection */ diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc index 3b227b1c7d0..2b2a116ebd8 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc @@ -2,11 +2,9 @@ * Copyright 2023 Blender Foundation. */ #include "COM_KuwaharaAnisotropicOperation.h" +#include "BLI_math_base.hh" #include "BLI_vector.hh" - -extern "C" { #include "IMB_colormanagement.h" -} namespace blender::compositor { @@ -80,11 +78,16 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], 0 : (lambda1 - lambda2) / (lambda1 + lambda2); + Vector mean(n_div_); + Vector sum(n_div_); + Vector var(n_div_); + Vector weight(n_div_); + for (int ch = 0; ch < 3; ch++) { - Vector mean(n_div_, 0.0f); - Vector sum(n_div_, 0.0f); - Vector var(n_div_, 0.0f); - Vector weight(n_div_, 0.0f); + mean.fill(0.0); + sum.fill(0.0); + var.fill(0.0); + weight.fill(0.0); double sx = 1.0f / (strength + 1.0f); double sy = (1.0f + strength) / 1.0f; @@ -98,22 +101,10 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], /* Rotate and scale the kernel. This is the "anisotropic" part. */ int dx2 = int(sx * (cos(theta) * dx - sin(theta) * dy)); int dy2 = int(sy * (sin(theta) * dx + cos(theta) * dy)); - int xx = x + dx2; - int yy = y + dy2; /* Clamp image to avoid artefacts at borders. */ - if (xx < 0) { - xx = 0; - } - if (xx >= width) { - xx = width - 1; - } - if (yy < 0) { - yy = 0; - } - if (yy >= height) { - yy = height - 1; - } + const int xx = math::clamp(int(x) + dx2, 0, width - 1); + const int yy = math::clamp(int(y) + dy2, 0, height - 1); const double ddx2 = double(dx2); const double ddy2 = double(dy2); @@ -229,12 +220,16 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou 0 : (lambda1 - lambda2) / (lambda1 + lambda2); - for (int ch = 0; ch < 3; ch++) { + Vector mean(n_div_); + Vector sum(n_div_); + Vector var(n_div_); + Vector weight(n_div_); - Vector mean(n_div_, 0.0f); - Vector sum(n_div_, 0.0f); - Vector var(n_div_, 0.0f); - Vector weight(n_div_, 0.0f); + for (int ch = 0; ch < 3; ch++) { + mean.fill(0.0); + sum.fill(0.0); + var.fill(0.0); + weight.fill(0.0); double sx = 1.0f / (strength + 1.0f); double sy = (1.0f + strength) / 1.0f; @@ -248,22 +243,10 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou /* Rotate and scale the kernel. This is the "anisotropic" part. */ int dx2 = int(sx * (cos(theta) * dx - sin(theta) * dy)); int dy2 = int(sy * (sin(theta) * dx + cos(theta) * dy)); - int xx = x + dx2; - int yy = y + dy2; /* Clamp image to avoid artefacts at borders. */ - if (xx < 0) { - xx = 0; - } - if (xx >= width) { - xx = width - 1; - } - if (yy < 0) { - yy = 0; - } - if (yy >= height) { - yy = height - 1; - } + const int xx = math::clamp(x + dx2, 0, width - 1); + const int yy = math::clamp(y + dy2, 0, height - 1); const double ddx2 = double(dx2); const double ddy2 = double(dy2); diff --git a/source/blender/compositor/operations/COM_SMAAOperation.cc b/source/blender/compositor/operations/COM_SMAAOperation.cc index 1f77332b6b2..0bceeb552c9 100644 --- a/source/blender/compositor/operations/COM_SMAAOperation.cc +++ b/source/blender/compositor/operations/COM_SMAAOperation.cc @@ -3,12 +3,10 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "COM_SMAAOperation.h" -#include "BKE_node.hh" #include "COM_SMAAAreaTexture.h" -extern "C" { +#include "BKE_node.hh" #include "IMB_colormanagement.h" -} namespace blender::compositor { -- 2.30.2 From 8f8a430a5db02f9f05191c5fba550dab9929d04d Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 8 Jun 2023 12:55:10 +0200 Subject: [PATCH 12/14] Cleanup: Use SPDX fields in file headers --- source/blender/compositor/nodes/COM_KuwaharaNode.cc | 5 +++-- source/blender/compositor/nodes/COM_KuwaharaNode.h | 5 +++-- .../operations/COM_KuwaharaAnisotropicOperation.cc | 5 +++-- .../compositor/operations/COM_KuwaharaAnisotropicOperation.h | 5 +++-- .../compositor/operations/COM_KuwaharaClassicOperation.cc | 5 +++-- .../compositor/operations/COM_KuwaharaClassicOperation.h | 5 +++-- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.cc b/source/blender/compositor/nodes/COM_KuwaharaNode.cc index 68c85f996e2..20d5431bffd 100644 --- a/source/blender/compositor/nodes/COM_KuwaharaNode.cc +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.cc @@ -1,5 +1,6 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2023 Blender Foundation. */ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "COM_KuwaharaNode.h" #include "COM_FastGaussianBlurOperation.h" diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.h b/source/blender/compositor/nodes/COM_KuwaharaNode.h index 8b2f9b008d2..c7038be2c0e 100644 --- a/source/blender/compositor/nodes/COM_KuwaharaNode.h +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.h @@ -1,5 +1,6 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2023 Blender Foundation. */ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc index 2b2a116ebd8..bae14755c77 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc @@ -1,5 +1,6 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2023 Blender Foundation. */ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "COM_KuwaharaAnisotropicOperation.h" #include "BLI_math_base.hh" diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h index db1732cf899..d572ca68774 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.h @@ -1,5 +1,6 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2023 Blender Foundation. */ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once diff --git a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc index 77ba524ef31..964df512114 100644 --- a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc @@ -1,5 +1,6 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2023 Blender Foundation. */ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ #include "COM_KuwaharaClassicOperation.h" diff --git a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.h b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.h index 0164e94dbb3..b8061532953 100644 --- a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.h +++ b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.h @@ -1,5 +1,6 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2023 Blender Foundation. */ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -- 2.30.2 From d89739f3d5f0c76bb0ab581e3ccbef4012b28665 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 8 Jun 2023 12:46:44 +0200 Subject: [PATCH 13/14] Cleanup: carious changes to match the code style - Add an empty line after first include. The idea is to include own header first, so that it is a bit better guaranteed that the header is self-sufficient. In order to keep the order an empty line is needed, so that clang-format does not move the own include to the bottom to keep an alphabetic order. - Full stop in comments. - Remove full-stop from the RNA description. There is warning about it in debug builds. --- .../compositor/nodes/COM_KuwaharaNode.cc | 3 +- .../COM_KuwaharaAnisotropicOperation.cc | 35 +++++++++---------- .../COM_KuwaharaClassicOperation.cc | 14 ++++---- source/blender/makesrna/intern/rna_nodetree.c | 2 +- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/source/blender/compositor/nodes/COM_KuwaharaNode.cc b/source/blender/compositor/nodes/COM_KuwaharaNode.cc index 20d5431bffd..370214a3732 100644 --- a/source/blender/compositor/nodes/COM_KuwaharaNode.cc +++ b/source/blender/compositor/nodes/COM_KuwaharaNode.cc @@ -3,6 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "COM_KuwaharaNode.h" + #include "COM_FastGaussianBlurOperation.h" #include "COM_KuwaharaAnisotropicOperation.h" #include "COM_KuwaharaClassicOperation.h" @@ -82,7 +83,7 @@ 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)); - /* Apply anisotropic Kuwahara filter */ + /* Apply anisotropic Kuwahara filter. */ KuwaharaAnisotropicOperation *aniso = new KuwaharaAnisotropicOperation(); aniso->set_kernel_size(data->size + 4); converter.map_input_socket(get_input_socket(0), aniso->get_input_socket(0)); diff --git a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc index bae14755c77..8b821912cb1 100644 --- a/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaAnisotropicOperation.cc @@ -3,6 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "COM_KuwaharaAnisotropicOperation.h" + #include "BLI_math_base.hh" #include "BLI_vector.hh" #include "IMB_colormanagement.h" @@ -57,8 +58,8 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], const float q = 3.0; const float EPS = 1.0e-10; - /* For now use green channel to compute orientation */ - /* todo: convert to HSV and compute orientation and strength on luminance channel */ + /* For now use green channel to compute orientation. */ + /* TODO: convert to HSV and compute orientation and strength on luminance channel */ float tmp[4]; s_xx_reader_->read(tmp, x, y, nullptr); const float a = tmp[1]; @@ -67,13 +68,13 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], s_yy_reader_->read(tmp, x, y, nullptr); const float c = tmp[1]; - /* Compute egenvalues of structure tensor */ + /* Compute egenvalues of structure tensor. */ const double tr = a + 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 */ + /* Compute orientation and its strength based on structure tensor. */ const double orientation = 0.5 * atan2(2 * b, a - c); const double strength = (lambda1 == 0 && lambda2 == 0) ? 0 : @@ -146,7 +147,7 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4], output[ch] = val; } - /* No changes for alpha channel */ + /* No changes for alpha channel. */ image_reader_->read_sampled(tmp, x, y, sampler); output[3] = tmp[3]; } @@ -172,12 +173,10 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou const rcti &area, Span inputs) { - /* - 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). - */ + /* 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). */ MemoryBuffer *image = inputs[0]; MemoryBuffer *s_xx = inputs[1]; @@ -203,8 +202,8 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou const int x = it.x; const int y = it.y; - /* For now use green channel to compute orientation */ - /* todo: convert to HSV and compute orientation and strength on luminance channel */ + /* For now use green channel to compute orientation. */ + /* TODO: convert to HSV and compute orientation and strength on luminance channel. */ const float a = s_xx->get_value(x, y, 1); const float b = s_xy->get_value(x, y, 1); const float c = s_yy->get_value(x, y, 1); @@ -215,7 +214,7 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou const double lambda1 = (tr + discr) / 2; const double lambda2 = (tr - discr) / 2; - /* Compute orientation and its strength based on structure tensor */ + /* Compute orientation and its strength based on structure tensor. */ const double orientation = 0.5 * atan2(2 * b, a - c); const double strength = (lambda1 == 0 && lambda2 == 0) ? 0 : @@ -258,9 +257,9 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou const double v = image->get_value(xx, yy, ch); float color[4]; image->read_elem(xx, yy, color); - /* todo(zazizizou): only compute lum once per region */ + /* TODO(zazizizou): only compute lum once per region. */ const float lum = IMB_colormanagement_get_luminance(color); - /* todo(zazizizou): only compute mean for the selected region */ + /* TODO(zazizizou): only compute mean for the selected region. */ mean[t] += g * v; sum[t] += g * lum; var[t] += g * lum * lum; @@ -268,7 +267,7 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou } } - /* Calculate weighted average */ + /* Calculate weighted average. */ double de = 0.0; double nu = 0.0; for (int i = 0; i < n_div_; i++) { @@ -288,7 +287,7 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou it.out[ch] = val; } - /* No changes for alpha channel */ + /* No changes for alpha channel. */ it.out[3] = image->get_value(x, y, 3); } } diff --git a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc index 964df512114..bdcd5383938 100644 --- a/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc +++ b/source/blender/compositor/operations/COM_KuwaharaClassicOperation.cc @@ -4,9 +4,7 @@ #include "COM_KuwaharaClassicOperation.h" -extern "C" { #include "IMB_colormanagement.h" -} namespace blender::compositor { @@ -40,7 +38,7 @@ void KuwaharaClassicOperation::execute_pixel_sampled(float output[4], float var[4] = {0.0f, 0.0f, 0.0f, 0.0f}; int cnt[4] = {0, 0, 0, 0}; - /* Split surroundings of pixel into 4 overlapping regions */ + /* Split surroundings of pixel into 4 overlapping regions. */ for (int dy = -kernel_size_; dy <= kernel_size_; dy++) { for (int dx = -kernel_size_; dx <= kernel_size_; dx++) { @@ -83,7 +81,7 @@ void KuwaharaClassicOperation::execute_pixel_sampled(float output[4], } } - /* Compute region variances */ + /* Compute region variances. */ for (int i = 0; i < 4; i++) { mean[i] = cnt[i] != 0 ? mean[i] / cnt[i] : 0.0f; sum[i] = cnt[i] != 0 ? sum[i] / cnt[i] : 0.0f; @@ -92,7 +90,7 @@ void KuwaharaClassicOperation::execute_pixel_sampled(float output[4], var[i] = var[i] > temp ? sqrt(var[i] - temp) : 0.0f; } - /* Choose the region with lowest variance */ + /* Choose the region with lowest variance. */ float min_var = FLT_MAX; int min_index = 0; for (int i = 0; i < 4; i++) { @@ -132,7 +130,7 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output float var[4] = {0.0f, 0.0f, 0.0f, 0.0f}; int cnt[4] = {0, 0, 0, 0}; - /* Split surroundings of pixel into 4 overlapping regions */ + /* Split surroundings of pixel into 4 overlapping regions. */ for (int dy = -kernel_size_; dy <= kernel_size_; dy++) { for (int dx = -kernel_size_; dx <= kernel_size_; dx++) { @@ -175,7 +173,7 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output } } - /* Compute region variances */ + /* Compute region variances. */ for (int i = 0; i < 4; i++) { mean[i] = cnt[i] != 0 ? mean[i] / cnt[i] : 0.0f; sum[i] = cnt[i] != 0 ? sum[i] / cnt[i] : 0.0f; @@ -184,7 +182,7 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output var[i] = var[i] > temp ? sqrt(var[i] - temp) : 0.0f; } - /* Choose the region with lowest variance */ + /* Choose the region with lowest variance. */ float min_var = FLT_MAX; int min_index = 0; for (int i = 0; i < 4; i++) { diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index dcbc29cd5b7..a242e512dd7 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -9397,7 +9397,7 @@ static void def_cmp_kuwahara(StructRNA *srna) prop = RNA_def_property(srna, "variation", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "variation"); RNA_def_property_enum_items(prop, variation_items); - RNA_def_property_ui_text(prop, "", "Variation of Kuwahara filter to use."); + 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, "smoothing", PROP_INT, PROP_NONE); -- 2.30.2 From 401ea11d474339cc3d0cfae354de2ce03bc84c97 Mon Sep 17 00:00:00 2001 From: Sergey Sharybin Date: Thu, 8 Jun 2023 12:56:46 +0200 Subject: [PATCH 14/14] Cleanup: Revert changes in SMAA operation The changes themselves are good, but is not a part of the Kuwahara project. Need to somehow remember to re-apply the changes in the main branch. --- source/blender/compositor/operations/COM_SMAAOperation.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/blender/compositor/operations/COM_SMAAOperation.cc b/source/blender/compositor/operations/COM_SMAAOperation.cc index 0bceeb552c9..1f77332b6b2 100644 --- a/source/blender/compositor/operations/COM_SMAAOperation.cc +++ b/source/blender/compositor/operations/COM_SMAAOperation.cc @@ -3,10 +3,12 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "COM_SMAAOperation.h" +#include "BKE_node.hh" #include "COM_SMAAAreaTexture.h" -#include "BKE_node.hh" +extern "C" { #include "IMB_colormanagement.h" +} namespace blender::compositor { -- 2.30.2