Blender V4.5
node_composite_double_edge_mask.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "UI_interface.hh"
10#include "UI_resources.hh"
11
13#include "COM_node_operation.hh"
14#include "COM_utilities.hh"
15
17
18/* **************** Double Edge Mask ******************** */
19
21
23{
24 b.add_input<decl::Float>("Inner Mask")
25 .default_value(0.8f)
26 .min(0.0f)
27 .max(1.0f)
29 b.add_input<decl::Float>("Outer Mask")
30 .default_value(0.8f)
31 .min(0.0f)
32 .max(1.0f)
34 b.add_output<decl::Float>("Mask");
35}
36
38 bContext * /*C*/,
40{
42
43 col = &layout->column(false);
44
45 col->label(IFACE_("Inner Edge:"), ICON_NONE);
46 col->prop(ptr, "inner_mode", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
47 col->label(IFACE_("Buffer Edge:"), ICON_NONE);
48 col->prop(ptr, "edge_mode", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
49}
50
51using namespace blender::compositor;
52
54 public:
56
57 void execute() override
58 {
59 Result &inner_mask = get_input("Inner Mask");
60 Result &outer_mask = get_input("Outer Mask");
61 Result &output = get_result("Mask");
62 if (inner_mask.is_single_value() || outer_mask.is_single_value()) {
63 output.allocate_invalid();
64 return;
65 }
66
67 /* Compute an image that marks the boundary pixels of the masks as seed pixels in the format
68 * expected by the jump flooding algorithm. */
71 compute_boundary(inner_boundary, outer_boundary);
72
73 /* Compute a jump flooding table for each mask boundary to get a distance transform to each of
74 * the boundaries. */
75 Result flooded_inner_boundary = context().create_result(ResultType::Int2,
77 Result flooded_outer_boundary = context().create_result(ResultType::Int2,
79 jump_flooding(context(), inner_boundary, flooded_inner_boundary);
80 jump_flooding(context(), outer_boundary, flooded_outer_boundary);
81 inner_boundary.release();
82 outer_boundary.release();
83
84 /* Compute the gradient based on the jump flooding table. */
85 compute_gradient(flooded_inner_boundary, flooded_outer_boundary);
86 flooded_inner_boundary.release();
87 flooded_outer_boundary.release();
88 }
89
90 void compute_boundary(Result &inner_boundary, Result &outer_boundary)
91 {
92 if (this->context().use_gpu()) {
93 this->compute_boundary_gpu(inner_boundary, outer_boundary);
94 }
95 else {
96 this->compute_boundary_cpu(inner_boundary, outer_boundary);
97 }
98 }
99
100 void compute_boundary_gpu(Result &inner_boundary, Result &outer_boundary)
101 {
102 GPUShader *shader = context().get_shader("compositor_double_edge_mask_compute_boundary",
104 GPU_shader_bind(shader);
105
106 GPU_shader_uniform_1b(shader, "include_all_inner_edges", include_all_inner_edges());
107 GPU_shader_uniform_1b(shader, "include_edges_of_image", include_edges_of_image());
108
109 const Result &inner_mask = get_input("Inner Mask");
110 inner_mask.bind_as_texture(shader, "inner_mask_tx");
111
112 const Result &outer_mask = get_input("Outer Mask");
113 outer_mask.bind_as_texture(shader, "outer_mask_tx");
114
115 const Domain domain = compute_domain();
116
117 inner_boundary.allocate_texture(domain);
118 inner_boundary.bind_as_image(shader, "inner_boundary_img");
119
120 outer_boundary.allocate_texture(domain);
121 outer_boundary.bind_as_image(shader, "outer_boundary_img");
122
124
125 inner_mask.unbind_as_texture();
126 outer_mask.unbind_as_texture();
127 inner_boundary.unbind_as_image();
128 outer_boundary.unbind_as_image();
130 }
131
132 void compute_boundary_cpu(Result &inner_boundary, Result &outer_boundary)
133 {
136
137 const Result &inner_mask = get_input("Inner Mask");
138 const Result &outer_mask = get_input("Outer Mask");
139
140 const Domain domain = compute_domain();
141 inner_boundary.allocate_texture(domain);
142 outer_boundary.allocate_texture(domain);
143
144 /* The Double Edge Mask operation uses a jump flood algorithm to compute a distance transform
145 * to the boundary of the inner and outer masks. The algorithm expects an input image whose
146 * values are those returned by the initialize_jump_flooding_value function, given the texel
147 * location and a boolean specifying if the pixel is a boundary one.
148 *
149 * Technically, we needn't restrict the output to just the boundary pixels, since the algorithm
150 * can still operate if the interior of the masks was also included. However, the algorithm
151 * operates more accurately when the number of pixels to be flooded is minimum. */
152 parallel_for(domain.size, [&](const int2 texel) {
153 /* Identify if any of the 8 neighbors around the center pixel are not masked. */
154 bool has_inner_non_masked_neighbors = false;
155 bool has_outer_non_masked_neighbors = false;
156 for (int j = -1; j <= 1; j++) {
157 for (int i = -1; i <= 1; i++) {
158 int2 offset = int2(i, j);
159
160 /* Exempt the center pixel. */
161 if (offset == int2(0)) {
162 continue;
163 }
164
165 if (inner_mask.load_pixel_extended<float>(texel + offset) == 0.0f) {
166 has_inner_non_masked_neighbors = true;
167 }
168
169 /* If the user specified include_edges_of_image to be true, then we assume the outer mask
170 * is bounded by the image boundary, otherwise, we assume the outer mask is open-ended.
171 * This is practically implemented by falling back to 0.0f or 1.0f for out of bound
172 * pixels. */
173 float boundary_fallback = include_edges_of_image ? 0.0f : 1.0f;
174 if (outer_mask.load_pixel_fallback(texel + offset, boundary_fallback) == 0.0f) {
175 has_outer_non_masked_neighbors = true;
176 }
177
178 /* Both are true, no need to continue. */
179 if (has_inner_non_masked_neighbors && has_outer_non_masked_neighbors) {
180 break;
181 }
182 }
183 }
184
185 bool is_inner_masked = inner_mask.load_pixel<float>(texel) > 0.0f;
186 bool is_outer_masked = outer_mask.load_pixel<float>(texel) > 0.0f;
187
188 /* The pixels at the boundary are those that are masked and have non masked neighbors. The
189 * inner boundary has a specialization, if include_all_inner_edges is false, only inner
190 * boundaries that lie inside the outer mask will be considered a boundary. The outer
191 * boundary is only considered if it is not inside the inner mask. */
192 bool is_inner_boundary = is_inner_masked && has_inner_non_masked_neighbors &&
193 (is_outer_masked || include_all_inner_edges);
194 bool is_outer_boundary = is_outer_masked && !is_inner_masked &&
195 has_outer_non_masked_neighbors;
196
197 /* Encode the boundary information in the format expected by the jump flooding algorithm. */
198 int2 inner_jump_flooding_value = initialize_jump_flooding_value(texel, is_inner_boundary);
199 int2 outer_jump_flooding_value = initialize_jump_flooding_value(texel, is_outer_boundary);
200
201 inner_boundary.store_pixel(texel, inner_jump_flooding_value);
202 outer_boundary.store_pixel(texel, outer_jump_flooding_value);
203 });
204 }
205
206 void compute_gradient(const Result &flooded_inner_boundary, const Result &flooded_outer_boundary)
207 {
208 if (this->context().use_gpu()) {
209 this->compute_gradient_gpu(flooded_inner_boundary, flooded_outer_boundary);
210 }
211 else {
212 this->compute_gradient_cpu(flooded_inner_boundary, flooded_outer_boundary);
213 }
214 }
215
216 void compute_gradient_gpu(const Result &flooded_inner_boundary,
217 const Result &flooded_outer_boundary)
218 {
219 GPUShader *shader = context().get_shader("compositor_double_edge_mask_compute_gradient");
220 GPU_shader_bind(shader);
221
222 const Result &inner_mask = get_input("Inner Mask");
223 inner_mask.bind_as_texture(shader, "inner_mask_tx");
224
225 const Result &outer_mask = get_input("Outer Mask");
226 outer_mask.bind_as_texture(shader, "outer_mask_tx");
227
228 flooded_inner_boundary.bind_as_texture(shader, "flooded_inner_boundary_tx");
229 flooded_outer_boundary.bind_as_texture(shader, "flooded_outer_boundary_tx");
230
231 const Domain domain = compute_domain();
232 Result &output = get_result("Mask");
233 output.allocate_texture(domain);
234 output.bind_as_image(shader, "output_img");
235
237
238 inner_mask.unbind_as_texture();
239 outer_mask.unbind_as_texture();
240 output.unbind_as_image();
242 }
243
244 void compute_gradient_cpu(const Result &flooded_inner_boundary,
245 const Result &flooded_outer_boundary)
246 {
247 const Result &inner_mask_input = get_input("Inner Mask");
248 const Result &outer_mask_input = get_input("Outer Mask");
249
250 const Domain domain = compute_domain();
251 Result &output = get_result("Mask");
252 output.allocate_texture(domain);
253
254 /* Computes a linear gradient from the outer mask boundary to the inner mask boundary, starting
255 * from 0 and ending at 1. This is computed using the equation:
256 *
257 * Gradient = O / (O + I)
258 *
259 * Where O is the distance to the outer boundary and I is the distance to the inner boundary.
260 * This can be viewed as computing the ratio between the distance to the outer boundary to the
261 * distance between the outer and inner boundaries as can be seen in the following illustration
262 * where the $ sign designates a pixel between both boundaries.
263 *
264 * | O I |
265 * Outer Boundary |---------$---------| Inner Boundary
266 * | |
267 */
268 parallel_for(domain.size, [&](const int2 texel) {
269 /* Pixels inside the inner mask are always 1.0. */
270 float inner_mask = inner_mask_input.load_pixel<float>(texel);
271 if (inner_mask != 0.0f) {
272 output.store_pixel(texel, 1.0f);
273 return;
274 }
275
276 /* Pixels outside the outer mask are always 0.0. */
277 float outer_mask = outer_mask_input.load_pixel<float>(texel);
278 if (outer_mask == 0.0f) {
279 output.store_pixel(texel, 0.0f);
280 return;
281 }
282
283 /* Compute the distances to the inner and outer boundaries from the jump flooding tables. */
284 int2 inner_boundary_texel = flooded_inner_boundary.load_pixel<int2>(texel);
285 int2 outer_boundary_texel = flooded_outer_boundary.load_pixel<int2>(texel);
286 float distance_to_inner = math::distance(float2(texel), float2(inner_boundary_texel));
287 float distance_to_outer = math::distance(float2(texel), float2(outer_boundary_texel));
288
289 float gradient = distance_to_outer / (distance_to_outer + distance_to_inner);
290
291 output.store_pixel(texel, gradient);
292 });
293 }
294
295 /* If false, only edges of the inner mask that lie inside the outer mask will be considered. If
296 * true, all edges of the inner mask will be considered. */
298 {
299 return !bool(bnode().custom1);
300 }
301
302 /* If true, the edges of the image that intersects the outer mask will be considered edges o the
303 * outer mask. If false, the outer mask will be considered open-ended. */
305 {
306 return bool(bnode().custom2);
307 }
308};
309
311{
312 return new DoubleEdgeMaskOperation(context, node);
313}
314
315} // namespace blender::nodes::node_composite_double_edge_mask_cc
316
318{
320
321 static blender::bke::bNodeType ntype; /* Allocate a node type data structure. */
322
323 cmp_node_type_base(&ntype, "CompositorNodeDoubleEdgeMask", CMP_NODE_DOUBLEEDGEMASK);
324 ntype.ui_name = "Double Edge Mask";
325 ntype.ui_description = "Create a gradient between two masks";
326 ntype.enum_name_legacy = "DOUBLEEDGEMASK";
327 ntype.nclass = NODE_CLASS_MATTE;
328 ntype.declare = file_ns::cmp_node_double_edge_mask_declare;
329 ntype.draw_buttons = file_ns::node_composit_buts_double_edge_mask;
330 ntype.get_compositor_operation = file_ns::get_compositor_operation;
331
333}
#define NODE_CLASS_MATTE
Definition BKE_node.hh:440
#define CMP_NODE_DOUBLEEDGEMASK
#define IFACE_(msgid)
void GPU_shader_bind(GPUShader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_uniform_1b(GPUShader *sh, const char *name, bool value)
void GPU_shader_unbind()
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ UI_ITEM_R_SPLIT_EMPTY_NAME
Result create_result(ResultType type, ResultPrecision precision)
GPUShader * 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 allocate_texture(Domain domain, bool from_pool=true)
Definition result.cc:309
void store_pixel(const int2 &texel, const T &pixel_value)
void unbind_as_texture() const
Definition result.cc:389
void bind_as_texture(GPUShader *shader, const char *texture_name) const
Definition result.cc:365
T load_pixel(const int2 &texel) const
void bind_as_image(GPUShader *shader, const char *image_name, bool read=false) const
Definition result.cc:376
void unbind_as_image() const
Definition result.cc:395
bool is_single_value() const
Definition result.cc:625
void compute_gradient_gpu(const Result &flooded_inner_boundary, const Result &flooded_outer_boundary)
void compute_gradient_cpu(const Result &flooded_inner_boundary, const Result &flooded_outer_boundary)
void compute_gradient(const Result &flooded_inner_boundary, const Result &flooded_outer_boundary)
uint col
#define output
void node_register_type(bNodeType &ntype)
Definition node.cc:2748
void compute_dispatch_threads_at_least(GPUShader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:170
int2 initialize_jump_flooding_value(const int2 &texel, const bool is_seed)
void jump_flooding(Context &context, Result &input, Result &output)
void parallel_for(const int2 range, const Function &function)
T distance(const T &a, const T &b)
static void cmp_node_double_edge_mask_declare(NodeDeclarationBuilder &b)
static void node_composit_buts_double_edge_mask(uiLayout *layout, bContext *, PointerRNA *ptr)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
static void register_node_type_cmp_doubleedgemask()
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:226
std::string ui_description
Definition BKE_node.hh:232
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:336
const char * enum_name_legacy
Definition BKE_node.hh:235
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:355
uiLayout & column(bool align)
static pxr::UsdShadeInput get_input(const pxr::UsdShadeShader &usd_shader, const pxr::TfToken &input_name)
PointerRNA * ptr
Definition wm_files.cc:4227