Blender V5.0
node_composite_filter.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
10#include "BLI_math_vector.hh"
12
13#include "RNA_types.hh"
14
15#include "COM_node_operation.hh"
16#include "COM_utilities.hh"
17
19
21
22static const EnumPropertyItem type_items[] = {
23 {CMP_NODE_FILTER_SOFT, "SOFTEN", 0, N_("Soften"), ""},
25 "SHARPEN",
26 0,
27 N_("Box Sharpen"),
28 N_("An aggressive sharpening filter")},
30 "SHARPEN_DIAMOND",
31 0,
32 N_("Diamond Sharpen"),
33 N_("A moderate sharpening filter")},
34 {CMP_NODE_FILTER_LAPLACE, "LAPLACE", 0, N_("Laplace"), ""},
35 {CMP_NODE_FILTER_SOBEL, "SOBEL", 0, N_("Sobel"), ""},
36 {CMP_NODE_FILTER_PREWITT, "PREWITT", 0, N_("Prewitt"), ""},
37 {CMP_NODE_FILTER_KIRSCH, "KIRSCH", 0, N_("Kirsch"), ""},
38 {CMP_NODE_FILTER_SHADOW, "SHADOW", 0, N_("Shadow"), ""},
39 {0, nullptr, 0, nullptr, nullptr},
40};
41
43{
44 b.use_custom_socket_order();
45 b.allow_any_socket_order();
46 b.add_input<decl::Color>("Image")
47 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
48 .hide_value()
49 .structure_type(StructureType::Dynamic);
50 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
51
52 b.add_input<decl::Float>("Factor", "Fac")
53 .default_value(1.0f)
54 .min(0.0f)
55 .max(1.0f)
57 .structure_type(StructureType::Dynamic);
58 b.add_input<decl::Menu>("Type")
59 .default_value(CMP_NODE_FILTER_SOFT)
60 .static_items(type_items)
62}
63
65 public:
68 {
69 bNode &node = params.add_node("CompositorNodeFilter");
70 bNodeSocket &type_socket = *blender::bke::node_find_socket(node, SOCK_IN, "Type");
71 type_socket.default_value_typed<bNodeSocketValueMenu>()->value = this->filter_type;
72 params.update_and_connect_available_socket(node, "Image");
73 }
74};
75
77{
78 const eNodeSocketDatatype from_socket_type = eNodeSocketDatatype(params.other_socket().type);
79 if (!params.node_tree().typeinfo->validate_link(from_socket_type, SOCK_RGBA)) {
80 return;
81 }
82
84 params.add_item(IFACE_("Box Sharpen"), SocketSearchOp{CMP_NODE_FILTER_SHARP_BOX});
90 params.add_item(IFACE_("Diamond Sharpen"), SocketSearchOp{CMP_NODE_FILTER_SHARP_DIAMOND});
91}
92
93using namespace blender::compositor;
94
96 public:
98
99 void execute() override
100 {
101 const Result &input_image = this->get_input("Image");
102 if (input_image.is_single_value()) {
103 Result &output_image = this->get_result("Image");
104 output_image.share_data(input_image);
105 return;
106 }
107
108 if (this->context().use_gpu()) {
109 this->execute_gpu();
110 }
111 else {
112 this->execute_cpu();
113 }
114 }
115
117 {
119 GPU_shader_bind(shader);
120
122
123 const Result &input_image = get_input("Image");
124 input_image.bind_as_texture(shader, "input_tx");
125
126 const Result &factor = get_input("Fac");
127 factor.bind_as_texture(shader, "factor_tx");
128
129 const Domain domain = compute_domain();
130
131 Result &output_image = get_result("Image");
132 output_image.allocate_texture(domain);
133 output_image.bind_as_image(shader, "output_img");
134
136
137 input_image.unbind_as_texture();
138 factor.unbind_as_texture();
139 output_image.unbind_as_image();
141 }
142
143 const char *get_shader_name()
144 {
145 if (this->is_edge_filter()) {
146 return "compositor_edge_filter";
147 }
148 return "compositor_filter";
149 }
150
152 {
153 const float3x3 kernel = this->get_filter_kernel();
154
155 const Result &input = get_input("Image");
156 const Result &factor = get_input("Fac");
157
158 const Domain domain = compute_domain();
159 Result &output = get_result("Image");
160 output.allocate_texture(domain);
161
162 if (this->is_edge_filter()) {
163 parallel_for(domain.size, [&](const int2 texel) {
164 /* Compute the dot product between the 3x3 window around the pixel and the edge detection
165 * kernel in the X direction and Y direction. The Y direction kernel is computed by
166 * transposing the given X direction kernel. */
167 float3 color_x = float3(0.0f);
168 float3 color_y = float3(0.0f);
169 for (int j = 0; j < 3; j++) {
170 for (int i = 0; i < 3; i++) {
171 float3 color = input.load_pixel_extended<float4>(texel + int2(i - 1, j - 1)).xyz();
172 color_x += color * kernel[j][i];
173 color_y += color * kernel[i][j];
174 }
175 }
176
177 /* Compute the channel-wise magnitude of the 2D vector composed from the X and Y edge
178 * detection filter results. */
179 float3 magnitude = math::sqrt(color_x * color_x + color_y * color_y);
180
181 /* Mix the channel-wise magnitude with the original color at the center of the kernel using
182 * the input factor. */
183 float4 color = input.load_pixel<float4>(texel);
184 magnitude = math::interpolate(
185 color.xyz(), magnitude, factor.load_pixel<float, true>(texel));
186
187 /* Store the channel-wise magnitude with the original alpha of the input. */
188 output.store_pixel(texel, float4(magnitude, color.w));
189 });
190 }
191 else {
192 parallel_for(domain.size, [&](const int2 texel) {
193 /* Compute the dot product between the 3x3 window around the pixel and the kernel. */
194 float4 color = float4(0.0f);
195 for (int j = 0; j < 3; j++) {
196 for (int i = 0; i < 3; i++) {
197 color += input.load_pixel_extended<float4>(texel + int2(i - 1, j - 1)) * kernel[j][i];
198 }
199 }
200
201 /* Mix with the original color at the center of the kernel using the input factor. */
203 input.load_pixel<float4>(texel), color, factor.load_pixel<float, true>(texel));
204
205 /* Store the color making sure it is not negative. */
206 output.store_pixel(texel, math::max(color, float4(0.0f)));
207 });
208 }
209 }
210
212 {
213 switch (this->get_type()) {
218 return true;
223 return false;
224 }
225 return false;
226 }
227
229 {
230 /* Initialize the kernels as arrays of rows with the top row first. Edge detection kernels
231 * return the kernel in the X direction, while the kernel in the Y direction will be computed
232 * inside the shader by transposing the kernel in the X direction. */
233 switch (this->get_type()) {
235 const float kernel[3][3] = {{1.0f / 16.0f, 2.0f / 16.0f, 1.0f / 16.0f},
236 {2.0f / 16.0f, 4.0f / 16.0f, 2.0f / 16.0f},
237 {1.0f / 16.0f, 2.0f / 16.0f, 1.0f / 16.0f}};
238 return float3x3(kernel);
239 }
241 const float kernel[3][3] = {
242 {-1.0f, -1.0f, -1.0f}, {-1.0f, 9.0f, -1.0f}, {-1.0f, -1.0f, -1.0f}};
243 return float3x3(kernel);
244 }
246 const float kernel[3][3] = {{-1.0f / 8.0f, -1.0f / 8.0f, -1.0f / 8.0f},
247 {-1.0f / 8.0f, 1.0f, -1.0f / 8.0f},
248 {-1.0f / 8.0f, -1.0f / 8.0f, -1.0f / 8.0f}};
249 return float3x3(kernel);
250 }
252 const float kernel[3][3] = {{1.0f, 0.0f, -1.0f}, {2.0f, 0.0f, -2.0f}, {1.0f, 0.0f, -1.0f}};
253 return float3x3(kernel);
254 }
256 const float kernel[3][3] = {{1.0f, 0.0f, -1.0f}, {1.0f, 0.0f, -1.0f}, {1.0f, 0.0f, -1.0f}};
257 return float3x3(kernel);
258 }
260 const float kernel[3][3] = {
261 {5.0f, -3.0f, -2.0f}, {5.0f, -3.0f, -2.0f}, {5.0f, -3.0f, -2.0f}};
262 return float3x3(kernel);
263 }
265 const float kernel[3][3] = {{1.0f, 2.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, {-1.0f, -2.0f, -1.0f}};
266 return float3x3(kernel);
267 }
269 const float kernel[3][3] = {
270 {0.0f, -1.0f, 0.0f}, {-1.0f, 5.0f, -1.0f}, {0.0f, -1.0f, 0.0f}};
271 return float3x3(kernel);
272 }
273 }
274
275 const float kernel[3][3] = {{0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}};
276 return float3x3(kernel);
277 }
278
280 {
281 const Result &input = this->get_input("Type");
282 const MenuValue default_menu_value = MenuValue(CMP_NODE_FILTER_SOFT);
283 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
284 return static_cast<CMPNodeFilterMethod>(menu_value.value);
285 }
286};
287
289{
290 return new FilterOperation(context, node);
291}
292
293} // namespace blender::nodes::node_composite_filter_cc
294
296{
298
299 static blender::bke::bNodeType ntype;
300
301 cmp_node_type_base(&ntype, "CompositorNodeFilter", CMP_NODE_FILTER);
302 ntype.ui_name = "Filter";
303 ntype.ui_description = "Apply common image enhancement filters";
304 ntype.enum_name_legacy = "FILTER";
306 ntype.declare = file_ns::cmp_node_filter_declare;
307 ntype.flag |= NODE_PREVIEW;
308 ntype.get_compositor_operation = file_ns::get_compositor_operation;
309 ntype.gather_link_search_ops = file_ns::gather_link_searches;
310
312}
#define NODE_CLASS_OP_FILTER
Definition BKE_node.hh:451
#define CMP_NODE_FILTER
#define IFACE_(msgid)
@ NODE_PREVIEW
@ SOCK_IN
eNodeSocketDatatype
@ SOCK_RGBA
CMPNodeFilterMethod
@ CMP_NODE_FILTER_PREWITT
@ CMP_NODE_FILTER_SHARP_BOX
@ CMP_NODE_FILTER_KIRSCH
@ CMP_NODE_FILTER_LAPLACE
@ CMP_NODE_FILTER_SHARP_DIAMOND
@ CMP_NODE_FILTER_SOFT
@ CMP_NODE_FILTER_SOBEL
@ CMP_NODE_FILTER_SHADOW
void GPU_shader_uniform_mat3_as_mat4(blender::gpu::Shader *sh, const char *name, const float data[3][3])
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
MatBase< 3, 3 > float3x3
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
bNodeSocket * node_find_socket(bNode &node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:2532
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 sqrt(const T &a)
T interpolate(const T &a, const T &b, const FactorT &t)
T max(const T &a, const T &b)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static void gather_link_searches(GatherLinkSearchOpParams &params)
static void cmp_node_filter_declare(NodeDeclarationBuilder &b)
static const EnumPropertyItem type_items[]
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
static void register_node_type_cmp_filter()
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
NodeGatherSocketLinkOperationsFunction gather_link_search_ops
Definition BKE_node.hh:378
NodeDeclareFunction declare
Definition BKE_node.hh:362
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &input_name)
#define N_(msgid)
PointerRNA * ptr
Definition wm_files.cc:4238