Blender V4.3
node_composite_kuwahara.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "BLI_math_base.hh"
10
11#include "RNA_access.hh"
12
13#include "UI_interface.hh"
14#include "UI_resources.hh"
15
16#include "COM_node_operation.hh"
17#include "COM_utilities.hh"
18
21
23
24/* **************** Kuwahara ******************** */
25
27
29
31{
32 b.add_input<decl::Color>("Image")
33 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
34 .compositor_domain_priority(0);
35 b.add_input<decl::Float>("Size").default_value(6.0f).compositor_domain_priority(1);
36 b.add_output<decl::Color>("Image");
37}
38
39static void node_composit_init_kuwahara(bNodeTree * /*ntree*/, bNode *node)
40{
41 NodeKuwaharaData *data = MEM_cnew<NodeKuwaharaData>(__func__);
42 node->storage = data;
43
44 /* Set defaults. */
45 data->uniformity = 4;
46 data->eccentricity = 1.0;
47 data->sharpness = 0.5;
48}
49
51{
53
54 col = uiLayoutColumn(layout, false);
55
56 uiItemR(col, ptr, "variation", UI_ITEM_NONE, nullptr, ICON_NONE);
57
58 const int variation = RNA_enum_get(ptr, "variation");
59
60 if (variation == CMP_NODE_KUWAHARA_CLASSIC) {
61 uiItemR(col, ptr, "use_high_precision", UI_ITEM_NONE, nullptr, ICON_NONE);
62 }
63 else if (variation == CMP_NODE_KUWAHARA_ANISOTROPIC) {
64 uiItemR(col, ptr, "uniformity", UI_ITEM_NONE, nullptr, ICON_NONE);
65 uiItemR(col, ptr, "sharpness", UI_ITEM_NONE, nullptr, ICON_NONE);
66 uiItemR(col, ptr, "eccentricity", UI_ITEM_NONE, nullptr, ICON_NONE);
67 }
68}
69
70using namespace blender::realtime_compositor;
71
73 public:
75
76 void execute() override
77 {
78 if (get_input("Image").is_single_value()) {
79 get_input("Image").pass_through(get_result("Image"));
80 return;
81 }
82
83 if (node_storage(bnode()).variation == CMP_NODE_KUWAHARA_ANISOTROPIC) {
85 }
86 else {
88 }
89 }
90
92 {
93 /* For high radii, we accelerate the filter using a summed area table, making the filter
94 * execute in constant time as opposed to having quadratic complexity. Except if high precision
95 * is enabled, since summed area tables are less precise. */
96 Result &size_input = get_input("Size");
97 if (!node_storage(bnode()).high_precision &&
98 (!size_input.is_single_value() || size_input.get_float_value() > 5.0f))
99 {
101 return;
102 }
103
105 GPU_shader_bind(shader);
106
107 const Result &input_image = get_input("Image");
108 input_image.bind_as_texture(shader, "input_tx");
109
110 if (size_input.is_single_value()) {
111 GPU_shader_uniform_1i(shader, "size", int(size_input.get_float_value()));
112 }
113 else {
114 size_input.bind_as_texture(shader, "size_tx");
115 }
116
117 const Domain domain = compute_domain();
118 Result &output_image = get_result("Image");
119 output_image.allocate_texture(domain);
120 output_image.bind_as_image(shader, "output_img");
121
122 compute_dispatch_threads_at_least(shader, domain.size);
123
124 input_image.unbind_as_texture();
125 output_image.unbind_as_image();
127 }
128
130 {
131 Result table = context().create_result(ResultType::Color, ResultPrecision::Full);
132 summed_area_table(context(), get_input("Image"), table);
133
134 Result squared_table = context().create_result(ResultType::Color, ResultPrecision::Full);
136 context(), get_input("Image"), squared_table, SummedAreaTableOperation::Square);
137
139 GPU_shader_bind(shader);
140
141 Result &size_input = get_input("Size");
142 if (size_input.is_single_value()) {
143 GPU_shader_uniform_1i(shader, "size", int(size_input.get_float_value()));
144 }
145 else {
146 size_input.bind_as_texture(shader, "size_tx");
147 }
148
149 table.bind_as_texture(shader, "table_tx");
150 squared_table.bind_as_texture(shader, "squared_table_tx");
151
152 const Domain domain = compute_domain();
153 Result &output_image = get_result("Image");
154 output_image.allocate_texture(domain);
155 output_image.bind_as_image(shader, "output_img");
156
157 compute_dispatch_threads_at_least(shader, domain.size);
158
159 table.unbind_as_texture();
160 squared_table.unbind_as_texture();
161 output_image.unbind_as_image();
163
164 table.release();
165 squared_table.release();
166 }
167
168 /* An implementation of the Anisotropic Kuwahara filter described in the paper:
169 *
170 * Kyprianidis, Jan Eric, Henry Kang, and Jurgen Dollner. "Image and video abstraction by
171 * anisotropic Kuwahara filtering." 2009.
172 */
174 {
175 Result structure_tensor = compute_structure_tensor();
176 Result smoothed_structure_tensor = context().create_result(ResultType::Color);
178 structure_tensor,
179 smoothed_structure_tensor,
180 float2(node_storage(bnode()).uniformity));
181 structure_tensor.release();
182
184 GPU_shader_bind(shader);
185
186 GPU_shader_uniform_1f(shader, "eccentricity", get_eccentricity());
187 GPU_shader_uniform_1f(shader, "sharpness", get_sharpness());
188
189 Result &input = get_input("Image");
190 input.bind_as_texture(shader, "input_tx");
191
192 Result &size_input = get_input("Size");
193 if (size_input.is_single_value()) {
194 GPU_shader_uniform_1f(shader, "size", size_input.get_float_value());
195 }
196 else {
197 size_input.bind_as_texture(shader, "size_tx");
198 }
199
200 smoothed_structure_tensor.bind_as_texture(shader, "structure_tensor_tx");
201
202 const Domain domain = compute_domain();
203 Result &output_image = get_result("Image");
204 output_image.allocate_texture(domain);
205 output_image.bind_as_image(shader, "output_img");
206
207 compute_dispatch_threads_at_least(shader, domain.size);
208
209 input.unbind_as_texture();
210 smoothed_structure_tensor.unbind_as_texture();
211 output_image.unbind_as_image();
213
214 smoothed_structure_tensor.release();
215 }
216
218 {
219 GPUShader *shader = context().get_shader(
220 "compositor_kuwahara_anisotropic_compute_structure_tensor");
221 GPU_shader_bind(shader);
222
223 Result &input = get_input("Image");
224 input.bind_as_texture(shader, "input_tx");
225
226 const Domain domain = compute_domain();
227 Result structure_tensor = context().create_result(ResultType::Color);
228 structure_tensor.allocate_texture(domain);
229 structure_tensor.bind_as_image(shader, "structure_tensor_img");
230
231 compute_dispatch_threads_at_least(shader, domain.size);
232
233 input.unbind_as_texture();
234 structure_tensor.unbind_as_image();
236
237 return structure_tensor;
238 }
239
241 {
242 if (is_constant_size()) {
243 return "compositor_kuwahara_classic_convolution_constant_size";
244 }
245 return "compositor_kuwahara_classic_convolution_variable_size";
246 }
247
249 {
250 if (is_constant_size()) {
251 return "compositor_kuwahara_classic_summed_area_table_constant_size";
252 }
253 return "compositor_kuwahara_classic_summed_area_table_variable_size";
254 }
255
257 {
258 if (is_constant_size()) {
259 return "compositor_kuwahara_anisotropic_constant_size";
260 }
261 return "compositor_kuwahara_anisotropic_variable_size";
262 }
263
265 {
266 return get_input("Size").is_single_value();
267 }
268
269 /* The sharpness controls the sharpness of the transitions between the kuwahara sectors, which
270 * is controlled by the weighting function pow(standard_deviation, -sharpness) as can be seen
271 * in the shader. The transition is completely smooth when the sharpness is zero and completely
272 * sharp when it is infinity. But realistically, the sharpness doesn't change much beyond the
273 * value of 16 due to its exponential nature, so we just assume a maximum sharpness of 16.
274 *
275 * The stored sharpness is in the range [0, 1], so we multiply by 16 to get it in the range
276 * [0, 16], however, we also square it before multiplication to slow down the rate of change
277 * near zero to counter its exponential nature for more intuitive user control. */
279 {
280 const float sharpness_factor = node_storage(bnode()).sharpness;
281 return sharpness_factor * sharpness_factor * 16.0f;
282 }
283
284 /* The eccentricity controls how much the image anisotropy affects the eccentricity of the
285 * kuwahara sectors, which is controlled by the following factor that gets multiplied to the
286 * radius to get the ellipse width and divides the radius to get the ellipse height:
287 *
288 * (eccentricity + anisotropy) / eccentricity
289 *
290 * Since the anisotropy is in the [0, 1] range, the factor tends to 1 as the eccentricity tends
291 * to infinity and tends to infinity when the eccentricity tends to zero. The stored
292 * eccentricity is in the range [0, 2], we map that to the range [infinity, 0.5] by taking the
293 * reciprocal, satisfying the aforementioned limits. The upper limit doubles the computed
294 * default eccentricity, which users can use to enhance the directionality of the filter.
295 * Instead of actual infinity, we just use an eccentricity of 1 / 0.01 since the result is very
296 * similar to that of infinity. */
298 {
299 return 1.0f / math::max(0.01f, node_storage(bnode()).eccentricity);
300 }
301};
302
304{
305 return new ConvertKuwaharaOperation(context, node);
306}
307
308} // namespace blender::nodes::node_composite_kuwahara_cc
309
311{
313
314 static blender::bke::bNodeType ntype;
315
316 cmp_node_type_base(&ntype, CMP_NODE_KUWAHARA, "Kuwahara", NODE_CLASS_OP_FILTER);
317 ntype.declare = file_ns::cmp_node_kuwahara_declare;
318 ntype.draw_buttons = file_ns::node_composit_buts_kuwahara;
319 ntype.initfunc = file_ns::node_composit_init_kuwahara;
321 &ntype, "NodeKuwaharaData", node_free_standard_storage, node_copy_standard_storage);
322 ntype.get_compositor_operation = file_ns::get_compositor_operation;
323
325}
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1799
#define NODE_CLASS_OP_FILTER
Definition BKE_node.hh:408
@ CMP_NODE_KUWAHARA_CLASSIC
@ CMP_NODE_KUWAHARA_ANISOTROPIC
void GPU_shader_uniform_1i(GPUShader *sh, const char *name, int value)
void GPU_shader_uniform_1f(GPUShader *sh, const char *name, float value)
void GPU_shader_bind(GPUShader *shader)
void GPU_shader_unbind()
#define UI_ITEM_NONE
uiLayout * uiLayoutColumn(uiLayout *layout, bool align)
void uiItemR(uiLayout *layout, PointerRNA *ptr, const char *propname, eUI_Item_Flag flag, const char *name, int icon)
struct GPUShader GPUShader
GPUShader * get_shader(const char *info_name, ResultPrecision precision)
Result create_result(ResultType type, ResultPrecision precision)
NodeOperation(Context &context, DNode node)
Result & get_input(StringRef identifier) const
Definition operation.cc:144
Result & get_result(StringRef identifier)
Definition operation.cc:46
void bind_as_image(GPUShader *shader, const char *image_name, bool read=false) const
Definition result.cc:264
void pass_through(Result &target)
Definition result.cc:289
void allocate_texture(Domain domain, bool from_pool=true)
Definition result.cc:204
void bind_as_texture(GPUShader *shader, const char *texture_name) const
Definition result.cc:253
local_group_size(16, 16) .push_constant(Type b
uint col
void node_type_storage(bNodeType *ntype, const char *storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:4632
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
T max(const T &a, const T &b)
static void node_composit_buts_kuwahara(uiLayout *layout, bContext *, PointerRNA *ptr)
static void cmp_node_kuwahara_declare(NodeDeclarationBuilder &b)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static void node_composit_init_kuwahara(bNodeTree *, bNode *node)
void symmetric_separable_blur(Context &context, Result &input, Result &output, float2 radius, int filter_type=R_FILTER_GAUSS, bool extend_bounds=false, bool gamma_correct=false)
void summed_area_table(Context &context, Result &input, Result &output, SummedAreaTableOperation operation=SummedAreaTableOperation::Identity)
void compute_dispatch_threads_at_least(GPUShader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:131
VecBase< float, 2 > float2
void register_node_type_cmp_kuwahara()
void cmp_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:46
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:58
int RNA_enum_get(PointerRNA *ptr, const char *name)
Defines a node type.
Definition BKE_node.hh:218
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:324
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:267
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:238
NodeDeclareFunction declare
Definition BKE_node.hh:347
PointerRNA * ptr
Definition wm_files.cc:4126