Blender V4.3
COM_DoubleEdgeMaskOperation.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
5#include <cstdlib>
6
7#include "BLI_math_vector.hh"
8#include "BLI_span.hh"
9#include "BLI_task.hh"
10
13
14/* Exact copies of the functions in compositor_double_edge_mask_compute_boundary.glsl and
15 * compositor_double_edge_mask_compute_gradient.glsl but adapted for CPU. See those files for more
16 * information. */
17
18namespace blender::compositor {
19
20static float load_mask(const float *input, int2 texel, int2 size)
21{
22 const int2 clamped_texel = math::clamp(texel, int2(0), size - 1);
23 return input[size_t(clamped_texel.y) * size.x + clamped_texel.x];
24}
25
26static float load_mask(const float *input, int2 texel, int2 size, float fallback)
27{
28 if (texel.x < 0 || texel.x >= size.x || texel.y < 0 || texel.y >= size.y) {
29 return fallback;
30 }
31 return input[size_t(texel.y) * size.x + texel.x];
32}
33
34void DoubleEdgeMaskOperation::compute_boundary(const float *inner_mask,
35 const float *outer_mask,
36 MutableSpan<int2> inner_boundary,
37 MutableSpan<int2> outer_boundary)
38{
39 const int2 size = int2(this->get_width(), this->get_height());
40 threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
41 for (const int64_t y : sub_y_range) {
42 for (const int64_t x : IndexRange(size.x)) {
43 int2 texel = int2(x, y);
44
45 bool has_inner_non_masked_neighbors = false;
46 bool has_outer_non_masked_neighbors = false;
47 for (int j = -1; j <= 1; j++) {
48 for (int i = -1; i <= 1; i++) {
49 int2 offset = int2(i, j);
50
51 if (offset == int2(0)) {
52 continue;
53 }
54
55 if (load_mask(inner_mask, texel + offset, size) == 0.0f) {
56 has_inner_non_masked_neighbors = true;
57 }
58
59 float boundary_fallback = include_edges_of_image_ ? 0.0f : 1.0f;
60 if (load_mask(outer_mask, texel + offset, size, boundary_fallback) == 0.0f) {
61 has_outer_non_masked_neighbors = true;
62 }
63
64 if (has_inner_non_masked_neighbors && has_outer_non_masked_neighbors) {
65 break;
66 }
67 }
68 }
69
70 bool is_inner_masked = load_mask(inner_mask, texel, size) > 0.0f;
71 bool is_outer_masked = load_mask(outer_mask, texel, size) > 0.0f;
72
73 bool is_inner_boundary = is_inner_masked && has_inner_non_masked_neighbors &&
74 (is_outer_masked || include_all_inner_edges_);
75 bool is_outer_boundary = is_outer_masked && !is_inner_masked &&
76 has_outer_non_masked_neighbors;
77
78 int2 inner_jump_flooding_value = initialize_jump_flooding_value(texel, is_inner_boundary);
79 int2 outer_jump_flooding_value = initialize_jump_flooding_value(texel, is_outer_boundary);
80
81 const size_t output_index = size_t(texel.y) * size.x + texel.x;
82 inner_boundary[output_index] = inner_jump_flooding_value;
83 outer_boundary[output_index] = outer_jump_flooding_value;
84 }
85 }
86 });
87}
88
89void DoubleEdgeMaskOperation::compute_gradient(const float *inner_mask_buffer,
90 const float *outer_mask_buffer,
91 MutableSpan<int2> flooded_inner_boundary,
92 MutableSpan<int2> flooded_outer_boundary,
93 float *output_mask)
94{
95 const int2 size = int2(this->get_width(), this->get_height());
96 threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
97 for (const int64_t y : sub_y_range) {
98 for (const int64_t x : IndexRange(size.x)) {
99 int2 texel = int2(x, y);
100 const size_t index = size_t(texel.y) * size.x + texel.x;
101
102 float inner_mask = inner_mask_buffer[index];
103 if (inner_mask != 0.0f) {
104 output_mask[index] = 1.0f;
105 continue;
106 }
107
108 float outer_mask = outer_mask_buffer[index];
109 if (outer_mask == 0.0f) {
110 output_mask[index] = 0.0f;
111 continue;
112 }
113
114 int2 inner_boundary_texel = flooded_inner_boundary[index];
115 int2 outer_boundary_texel = flooded_outer_boundary[index];
116 float distance_to_inner = math::distance(float2(texel), float2(inner_boundary_texel));
117 float distance_to_outer = math::distance(float2(texel), float2(outer_boundary_texel));
118
119 float gradient = distance_to_outer / (distance_to_outer + distance_to_inner);
120
121 output_mask[index] = gradient;
122 }
123 }
124 });
125}
126
127void DoubleEdgeMaskOperation::compute_double_edge_mask(const float *inner_mask,
128 const float *outer_mask,
129 float *output_mask)
130{
131 const int2 size = int2(this->get_width(), this->get_height());
132 Array<int2> inner_boundary(size_t(size.x) * size.y);
133 Array<int2> outer_boundary(size_t(size.x) * size.y);
134 compute_boundary(inner_mask, outer_mask, inner_boundary, outer_boundary);
135 Array<int2> flooded_inner_boundary = jump_flooding(inner_boundary, size);
136 Array<int2> flooded_outer_boundary = jump_flooding(outer_boundary, size);
137 compute_gradient(
138 inner_mask, outer_mask, flooded_inner_boundary, flooded_outer_boundary, output_mask);
139}
140
141DoubleEdgeMaskOperation::DoubleEdgeMaskOperation()
142{
143 this->add_input_socket(DataType::Value);
144 this->add_input_socket(DataType::Value);
145 this->add_output_socket(DataType::Value);
146 include_all_inner_edges_ = false;
147 include_edges_of_image_ = false;
148 flags_.can_be_constant = true;
149 is_output_rendered_ = false;
150}
151
152void DoubleEdgeMaskOperation::get_area_of_interest(int /*input_idx*/,
153 const rcti & /*output_area*/,
154 rcti &r_input_area)
155{
156 r_input_area = this->get_canvas();
157}
158
159void DoubleEdgeMaskOperation::update_memory_buffer(MemoryBuffer *output,
160 const rcti & /*area*/,
162{
163 if (!is_output_rendered_) {
164 MemoryBuffer *input_inner_mask = inputs[0];
165 MemoryBuffer *input_outer_mask = inputs[1];
166 if (input_inner_mask->is_a_single_elem() && input_outer_mask->is_a_single_elem()) {
167 output->clear();
168 is_output_rendered_ = true;
169 return;
170 }
171
172 /* Ensure full buffers to work with no strides. */
173 MemoryBuffer *inner_mask = input_inner_mask->is_a_single_elem() ? input_inner_mask->inflate() :
174 input_inner_mask;
175 MemoryBuffer *outer_mask = input_outer_mask->is_a_single_elem() ? input_outer_mask->inflate() :
176 input_outer_mask;
177
178 BLI_assert(output->get_width() == this->get_width());
179 BLI_assert(output->get_height() == this->get_height());
180 compute_double_edge_mask(
181 inner_mask->get_buffer(), outer_mask->get_buffer(), output->get_buffer());
182 is_output_rendered_ = true;
183
184 if (inner_mask != input_inner_mask) {
185 delete inner_mask;
186 }
187 if (outer_mask != input_outer_mask) {
188 delete outer_mask;
189 }
190 }
191}
192
193} // namespace blender::compositor
#define BLI_assert(a)
Definition BLI_assert.h:50
void compute_boundary(const float *inner_mask, const float *outer_mask, MutableSpan< int2 > inner_boundary, MutableSpan< int2 > outer_boundary)
a MemoryBuffer contains access to the data
float * get_buffer()
get the data of this MemoryBuffer
void clear()
clear the buffer. Make all pixels black transparent.
Array< int2 > jump_flooding(Span< int2 > input, int2 size)
static float load_mask(const float *input, int2 texel, int2 size)
T clamp(const T &a, const T &min, const T &max)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:95
VecBase< int32_t, 2 > int2