Blender V5.0
node_composite_despeckle.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2006 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_math_base.hh"
11#include "BLI_math_vector.hh"
13
14#include "UI_resources.hh"
15
16#include "GPU_shader.hh"
17
18#include "COM_node_operation.hh"
19#include "COM_utilities.hh"
20
22
23/* **************** FILTER ******************** */
24
26
28{
29 b.use_custom_socket_order();
30 b.allow_any_socket_order();
31 b.add_input<decl::Color>("Image")
32 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
33 .hide_value()
34 .structure_type(StructureType::Dynamic);
35 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
36
37 b.add_input<decl::Float>("Factor", "Fac")
38 .default_value(1.0f)
39 .min(0.0f)
40 .max(1.0f)
42 .structure_type(StructureType::Dynamic);
43 b.add_input<decl::Float>("Color Threshold")
44 .default_value(0.5f)
45 .min(0.0f)
47 "Pixels are despeckled only if their color difference from the average color of their "
48 "neighbors exceeds this threshold");
49 b.add_input<decl::Float>("Neighbor Threshold")
50 .default_value(0.5f)
52 .min(0.0f)
53 .max(1.0f)
54 .description(
55 "Pixels are despeckled only if the number of pixels in their neighborhood that are "
56 "different exceed this ratio threshold relative to the total number of neighbors. "
57 "Neighbors are considered different if they exceed the color threshold input");
58}
59
60using namespace blender::compositor;
61
63 public:
65
66 void execute() override
67 {
68 const Result &input = this->get_input("Image");
69 if (input.is_single_value()) {
70 Result &output = this->get_result("Image");
71 output.share_data(input);
72 return;
73 }
74
75 if (this->context().use_gpu()) {
76 this->execute_gpu();
77 }
78 else {
79 this->execute_cpu();
80 }
81 }
82
84 {
85 gpu::Shader *shader = context().get_shader("compositor_despeckle");
86 GPU_shader_bind(shader);
87
88 GPU_shader_uniform_1f(shader, "color_threshold", get_color_threshold());
89 GPU_shader_uniform_1f(shader, "neighbor_threshold", get_neighbor_threshold());
90
91 const Result &input_image = get_input("Image");
92 input_image.bind_as_texture(shader, "input_tx");
93
94 const Result &factor_image = get_input("Fac");
95 factor_image.bind_as_texture(shader, "factor_tx");
96
97 const Domain domain = compute_domain();
98 Result &output_image = get_result("Image");
99 output_image.allocate_texture(domain);
100 output_image.bind_as_image(shader, "output_img");
101
103
105 output_image.unbind_as_image();
106 input_image.unbind_as_texture();
107 factor_image.unbind_as_texture();
108 }
109
111 {
112 const float color_threshold = this->get_color_threshold();
113 const float neighbor_threshold = this->get_neighbor_threshold();
114
115 const Result &input = get_input("Image");
116 const Result &factor_image = get_input("Fac");
117
118 const Domain domain = compute_domain();
119 Result &output = get_result("Image");
120 output.allocate_texture(domain);
121
122 /* A 3x3 weights kernel whose weights are the inverse of the distance to the center of the
123 * kernel. So the center weight is zero, the corners weights are (1 / sqrt(2)), and the rest
124 * of the weights are 1. The total sum of weights is 4 plus quadruple the corner weight. */
125 float corner_weight = 1.0f / math::sqrt(2.0f);
126 float sum_of_weights = 4.0f + corner_weight * 4.0f;
127 float3x3 weights = float3x3(float3(corner_weight, 1.0f, corner_weight),
128 float3(1.0f, 0.0f, 1.0f),
129 float3(corner_weight, 1.0f, corner_weight));
130
131 parallel_for(domain.size, [&](const int2 texel) {
132 float4 center_color = input.load_pixel<float4>(texel);
133
134 /* Go over the pixels in the 3x3 window around the center pixel and compute the total sum of
135 * their colors multiplied by their weights. Additionally, for pixels whose colors are not
136 * close enough to the color of the center pixel, accumulate their color as well as their
137 * weights. */
138 float4 sum_of_colors = float4(0.0f);
139 float accumulated_weight = 0.0f;
140 float4 accumulated_color = float4(0.0f);
141 for (int j = 0; j < 3; j++) {
142 for (int i = 0; i < 3; i++) {
143 float weight = weights[j][i];
144 float4 color = input.load_pixel_extended<float4>(texel + int2(i - 1, j - 1)) * weight;
145 sum_of_colors += color;
146 if (!math::is_equal(center_color.xyz(), color.xyz(), color_threshold)) {
147 accumulated_color += color;
148 accumulated_weight += weight;
149 }
150 }
151 }
152
153 /* If the accumulated weight is zero, that means all pixels in the 3x3 window are similar and
154 * no need to despeckle anything, so write the original center color and return. */
155 if (accumulated_weight == 0.0f) {
156 output.store_pixel(texel, center_color);
157 return;
158 }
159
160 /* If the ratio between the accumulated weights and the total sum of weights is not larger
161 * than the user specified neighbor threshold, then the number of pixels in the neighborhood
162 * that are not close enough to the center pixel is low, and no need to despeckle anything,
163 * so write the original center color and return. */
164 if (accumulated_weight / sum_of_weights < neighbor_threshold) {
165 output.store_pixel(texel, center_color);
166 return;
167 }
168
169 /* If the weighted average color of the neighborhood is close enough to the center pixel,
170 * then no need to despeckle anything, so write the original center color and return. */
171 if (math::is_equal(
172 center_color.xyz(), (sum_of_colors / sum_of_weights).xyz(), color_threshold))
173 {
174 output.store_pixel(texel, center_color);
175 return;
176 }
177
178 /* We need to despeckle, so write the mean accumulated color. */
179 float factor = factor_image.load_pixel<float, true>(texel);
180 float4 mean_color = accumulated_color / accumulated_weight;
181 output.store_pixel(texel, math::interpolate(center_color, mean_color, factor));
182 });
183 }
184
186 {
187 return math::max(0.0f, this->get_input("Color Threshold").get_single_value_default(0.5f));
188 }
189
191 {
192 return math::clamp(
193 this->get_input("Neighbor Threshold").get_single_value_default(0.5f), 0.0f, 1.0f);
194 }
195};
196
198{
199 return new DespeckleOperation(context, node);
200}
201
202} // namespace blender::nodes::node_composite_despeckle_cc
203
205{
207
208 static blender::bke::bNodeType ntype;
209
210 cmp_node_type_base(&ntype, "CompositorNodeDespeckle", CMP_NODE_DESPECKLE);
211 ntype.ui_name = "Despeckle";
212 ntype.ui_description =
213 "Smooth areas of an image in which noise is noticeable, while leaving complex areas "
214 "untouched";
215 ntype.enum_name_legacy = "DESPECKLE";
217 ntype.declare = file_ns::cmp_node_despeckle_declare;
218 ntype.flag |= NODE_PREVIEW;
219 ntype.get_compositor_operation = file_ns::get_compositor_operation;
220
222}
#define NODE_CLASS_OP_FILTER
Definition BKE_node.hh:451
#define CMP_NODE_DESPECKLE
@ NODE_PREVIEW
void GPU_shader_uniform_1f(blender::gpu::Shader *sh, const char *name, float value)
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_unbind()
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_FACTOR
Definition RNA_types.hh:251
gpu::Shader * get_shader(const char *info_name, ResultPrecision precision)
NodeOperation(Context &context, DNode node)
Result & get_result(StringRef identifier)
Definition operation.cc:39
Result & get_input(StringRef identifier) const
Definition operation.cc:138
virtual Domain compute_domain()
Definition operation.cc:56
void share_data(const Result &source)
Definition result.cc:523
void allocate_texture(const Domain domain, const bool from_pool=true, const std::optional< ResultStorageType > storage_type=std::nullopt)
Definition result.cc:389
void unbind_as_texture() const
Definition result.cc:511
void bind_as_texture(gpu::Shader *shader, const char *texture_name) const
Definition result.cc:487
T load_pixel(const int2 &texel) const
void unbind_as_image() const
Definition result.cc:517
void bind_as_image(gpu::Shader *shader, const char *image_name, bool read=false) const
Definition result.cc:498
bool is_single_value() const
Definition result.cc:758
#define input
#define output
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void compute_dispatch_threads_at_least(gpu::Shader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:196
void parallel_for(const int2 range, const Function &function)
T clamp(const T &a, const T &min, const T &max)
T sqrt(const T &a)
T interpolate(const T &a, const T &b, const FactorT &t)
bool is_equal(const MatBase< T, NumCol, NumRow > &a, const MatBase< T, NumCol, NumRow > &b, const T epsilon=T(0))
T max(const T &a, const T &b)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static void cmp_node_despeckle_declare(NodeDeclarationBuilder &b)
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
static void register_node_type_cmp_despeckle()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:348
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &input_name)