Blender V5.0
node_composite_inpaint.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"
10#include "BLI_math_numbers.hh"
11#include "BLI_math_vector.hh"
13
14#include "UI_resources.hh"
15
18#include "COM_node_operation.hh"
19#include "COM_utilities.hh"
20
22
23/* **************** Inpaint/ ******************** */
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::Int>("Size").default_value(0).min(0).description(
38 "The size of the inpaint in pixels");
39}
40
41using namespace blender::compositor;
42
44 public:
46
47 void execute() override
48 {
49 const Result &input = this->get_input("Image");
50 if (input.is_single_value() || this->get_max_distance() == 0) {
51 Result &output = this->get_result("Image");
52 output.share_data(input);
53 return;
54 }
55
56 Result inpainting_boundary = compute_inpainting_boundary();
57
58 /* Compute a jump flooding table to get the closest boundary pixel to each pixel. */
60 jump_flooding(context(), inpainting_boundary, flooded_boundary);
61 inpainting_boundary.release();
62
64 Result distance_to_boundary = context().create_result(ResultType::Float);
65 Result smoothing_radius = context().create_result(ResultType::Float);
67 flooded_boundary, filled_region, distance_to_boundary, smoothing_radius);
68 flooded_boundary.release();
69
70 Result smoothed_region = context().create_result(ResultType::Color);
72 context(), filled_region, smoothing_radius, smoothed_region, get_max_distance());
73 filled_region.release();
74 smoothing_radius.release();
75
76 compute_inpainting_region(smoothed_region, distance_to_boundary);
77 distance_to_boundary.release();
78 smoothed_region.release();
79 }
80
81 /* Compute an image that marks the boundary pixels of the inpainting region as seed pixels for
82 * the jump flooding algorithm. The inpainting region is the region composed of pixels that are
83 * not opaque. */
85 {
86 if (this->context().use_gpu()) {
88 }
89
91 }
92
94 {
95 gpu::Shader *shader = context().get_shader("compositor_inpaint_compute_boundary",
97 GPU_shader_bind(shader);
98
99 const Result &input = get_input("Image");
100 input.bind_as_texture(shader, "input_tx");
101
103 const Domain domain = compute_domain();
104 inpainting_boundary.allocate_texture(domain);
105 inpainting_boundary.bind_as_image(shader, "boundary_img");
106
108
109 input.unbind_as_texture();
110 inpainting_boundary.unbind_as_image();
112
113 return inpainting_boundary;
114 }
115
117 {
118 const Result &input = this->get_input("Image");
119
121 const Domain domain = this->compute_domain();
122 boundary.allocate_texture(domain);
123
124 /* The in-paint operation uses a jump flood algorithm to flood the region to be in-painted with
125 * the pixels at its boundary. The algorithms expects an input image whose values are those
126 * returned by the initialize_jump_flooding_value function, given the texel location and a
127 * boolean specifying if the pixel is a boundary one.
128 *
129 * Technically, we needn't restrict the output to just the boundary pixels, since the algorithm
130 * can still operate if the interior of the region was also included. However, the algorithm
131 * operates more accurately when the number of pixels to be flooded is minimum. */
132 parallel_for(domain.size, [&](const int2 texel) {
133 /* Identify if any of the 8 neighbors around the center pixel are transparent. */
134 bool has_transparent_neighbors = false;
135 for (int j = -1; j <= 1; j++) {
136 for (int i = -1; i <= 1; i++) {
137 int2 offset = int2(i, j);
138
139 /* Exempt the center pixel. */
140 if (offset != int2(0)) {
141 if (input.load_pixel_extended<float4>(texel + offset).w < 1.0f) {
142 has_transparent_neighbors = true;
143 break;
144 }
145 }
146 }
147 }
148
149 /* The pixels at the boundary are those that are opaque and have transparent neighbors. */
150 bool is_opaque = input.load_pixel<float4>(texel).w == 1.0f;
151 bool is_boundary_pixel = is_opaque && has_transparent_neighbors;
152
153 /* Encode the boundary information in the format expected by the jump flooding algorithm. */
154 int2 jump_flooding_value = initialize_jump_flooding_value(texel, is_boundary_pixel);
155
156 boundary.store_pixel(texel, jump_flooding_value);
157 });
158
159 return boundary;
160 }
161
162 /* Fill the inpainting region based on the jump flooding table and write the distance to the
163 * closest boundary pixel to an intermediate buffer. */
164 void fill_inpainting_region(const Result &flooded_boundary,
165 Result &filled_region,
166 Result &distance_to_boundary,
167 Result &smoothing_radius)
168 {
169 if (this->context().use_gpu()) {
171 flooded_boundary, filled_region, distance_to_boundary, smoothing_radius);
172 }
173 else {
175 flooded_boundary, filled_region, distance_to_boundary, smoothing_radius);
176 }
177 }
178
179 void fill_inpainting_region_gpu(const Result &flooded_boundary,
180 Result &filled_region,
181 Result &distance_to_boundary,
182 Result &smoothing_radius)
183 {
184 gpu::Shader *shader = context().get_shader("compositor_inpaint_fill_region");
185 GPU_shader_bind(shader);
186
187 GPU_shader_uniform_1i(shader, "max_distance", get_max_distance());
188
189 const Result &input = get_input("Image");
190 input.bind_as_texture(shader, "input_tx");
191
192 flooded_boundary.bind_as_texture(shader, "flooded_boundary_tx");
193
194 const Domain domain = compute_domain();
195 filled_region.allocate_texture(domain);
196 filled_region.bind_as_image(shader, "filled_region_img");
197
198 distance_to_boundary.allocate_texture(domain);
199 distance_to_boundary.bind_as_image(shader, "distance_to_boundary_img");
200
201 smoothing_radius.allocate_texture(domain);
202 smoothing_radius.bind_as_image(shader, "smoothing_radius_img");
203
205
206 input.unbind_as_texture();
207 flooded_boundary.unbind_as_texture();
208 filled_region.unbind_as_image();
209 distance_to_boundary.unbind_as_image();
210 smoothing_radius.unbind_as_image();
212 }
213
214 void fill_inpainting_region_cpu(const Result &flooded_boundary,
215 Result &filled_region,
216 Result &distance_to_boundary_image,
217 Result &smoothing_radius_image)
218 {
219 const int max_distance = this->get_max_distance();
220
221 const Result &input = this->get_input("Image");
222
223 const Domain domain = this->compute_domain();
224 filled_region.allocate_texture(domain);
225 distance_to_boundary_image.allocate_texture(domain);
226 smoothing_radius_image.allocate_texture(domain);
227
228 /* Fill the inpainting region by sampling the color of the nearest boundary pixel.
229 * Additionally, compute some information about the inpainting region, like the distance to the
230 * boundary, as well as the blur radius to use to smooth out that region. */
231 parallel_for(domain.size, [&](const int2 texel) {
232 float4 color = input.load_pixel<float4>(texel);
233
234 /* An opaque pixel, not part of the inpainting region. */
235 if (color.w == 1.0f) {
236 filled_region.store_pixel(texel, color);
237 smoothing_radius_image.store_pixel(texel, 0.0f);
238 distance_to_boundary_image.store_pixel(texel, 0.0f);
239 return;
240 }
241
242 int2 closest_boundary_texel = flooded_boundary.load_pixel<int2>(texel);
243 float distance_to_boundary = math::distance(float2(texel), float2(closest_boundary_texel));
244 distance_to_boundary_image.store_pixel(texel, distance_to_boundary);
245
246 /* We follow this shader by a blur shader that smooths out the inpainting region, where the
247 * blur radius is the radius of the circle that touches the boundary. We can imagine the blur
248 * window to be inscribed in that circle and thus the blur radius is the distance to the
249 * boundary divided by square root two. As a performance optimization, we limit the blurring
250 * to areas that will affect the inpainting region, that is, whose distance to boundary is
251 * less than double the inpainting distance. Additionally, we clamp to the distance to the
252 * inpainting distance since areas outside of the clamp range only indirectly affect the
253 * inpainting region due to blurring and thus needn't use higher blur radii. */
254 float blur_window_size = math::min(float(max_distance), distance_to_boundary) /
256 bool skip_smoothing = distance_to_boundary > (max_distance * 2.0f);
257 float smoothing_radius = skip_smoothing ? 0.0f : blur_window_size;
258 smoothing_radius_image.store_pixel(texel, smoothing_radius);
259
260 /* Mix the boundary color with the original color using its alpha because semi-transparent
261 * areas are considered to be partially inpainted. */
262 float4 boundary_color = input.load_pixel<float4>(closest_boundary_texel);
263 filled_region.store_pixel(texel, math::interpolate(boundary_color, color, color.w));
264 });
265 }
266
267 /* Compute the inpainting region by mixing the smoothed inpainted region with the original input
268 * up to the inpainting distance. */
269 void compute_inpainting_region(const Result &inpainted_region,
270 const Result &distance_to_boundary)
271 {
272 if (this->context().use_gpu()) {
273 this->compute_inpainting_region_gpu(inpainted_region, distance_to_boundary);
274 }
275 else {
276 this->compute_inpainting_region_cpu(inpainted_region, distance_to_boundary);
277 }
278 }
279
280 void compute_inpainting_region_gpu(const Result &inpainted_region,
281 const Result &distance_to_boundary)
282 {
283 gpu::Shader *shader = context().get_shader("compositor_inpaint_compute_region");
284 GPU_shader_bind(shader);
285
286 GPU_shader_uniform_1i(shader, "max_distance", get_max_distance());
287
288 const Result &input = get_input("Image");
289 input.bind_as_texture(shader, "input_tx");
290
291 inpainted_region.bind_as_texture(shader, "inpainted_region_tx");
292 distance_to_boundary.bind_as_texture(shader, "distance_to_boundary_tx");
293
294 const Domain domain = compute_domain();
295 Result &output = get_result("Image");
296 output.allocate_texture(domain);
297 output.bind_as_image(shader, "output_img");
298
300
301 input.unbind_as_texture();
302 inpainted_region.unbind_as_texture();
303 distance_to_boundary.unbind_as_texture();
304 output.unbind_as_image();
306 }
307
308 void compute_inpainting_region_cpu(const Result &inpainted_region,
309 const Result &distance_to_boundary_image)
310 {
311 const int max_distance = this->get_max_distance();
312
313 const Result &input = this->get_input("Image");
314
315 const Domain domain = this->compute_domain();
316 Result &output = this->get_result("Image");
317 output.allocate_texture(domain);
318
319 parallel_for(domain.size, [&](const int2 texel) {
320 float4 color = input.load_pixel<float4>(texel);
321
322 /* An opaque pixel, not part of the inpainting region, write the original color. */
323 if (color.w == 1.0f) {
324 output.store_pixel(texel, color);
325 return;
326 }
327
328 float distance_to_boundary = distance_to_boundary_image.load_pixel<float>(texel);
329
330 /* Further than the inpainting distance, not part of the inpainting region, write the
331 * original color. */
332 if (distance_to_boundary > max_distance) {
333 output.store_pixel(texel, color);
334 return;
335 }
336
337 /* Mix the inpainted color with the original color using its alpha because semi-transparent
338 * areas are considered to be partially inpainted. */
339 float4 inpainted_color = inpainted_region.load_pixel<float4>(texel);
340 output.store_pixel(
341 texel, float4(math::interpolate(inpainted_color.xyz(), color.xyz(), color.w), 1.0f));
342 });
343 }
344
346 {
347 return math::max(0, this->get_input("Size").get_single_value_default(0));
348 }
349};
350
352{
353 return new InpaintOperation(context, node);
354}
355
356} // namespace blender::nodes::node_composite_inpaint_cc
357
359{
361
362 static blender::bke::bNodeType ntype;
363
364 cmp_node_type_base(&ntype, "CompositorNodeInpaint", CMP_NODE_INPAINT);
365 ntype.ui_name = "Inpaint";
366 ntype.ui_description = "Extend borders of an image into transparent or masked regions";
367 ntype.enum_name_legacy = "INPAINT";
369 ntype.declare = file_ns::cmp_node_inpaint_declare;
370 ntype.get_compositor_operation = file_ns::get_compositor_operation;
371
373}
#define NODE_CLASS_OP_FILTER
Definition BKE_node.hh:451
#define CMP_NODE_INPAINT
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_uniform_1i(blender::gpu::Shader *sh, const char *name, int value)
void GPU_shader_unbind()
#define NOD_REGISTER_NODE(REGISTER_FUNC)
Result create_result(ResultType type, ResultPrecision precision)
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 store_pixel(const int2 &texel, const T &pixel_value)
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
void fill_inpainting_region(const Result &flooded_boundary, Result &filled_region, Result &distance_to_boundary, Result &smoothing_radius)
void compute_inpainting_region_gpu(const Result &inpainted_region, const Result &distance_to_boundary)
void compute_inpainting_region_cpu(const Result &inpainted_region, const Result &distance_to_boundary_image)
void fill_inpainting_region_cpu(const Result &flooded_boundary, Result &filled_region, Result &distance_to_boundary_image, Result &smoothing_radius_image)
void fill_inpainting_region_gpu(const Result &flooded_boundary, Result &filled_region, Result &distance_to_boundary, Result &smoothing_radius)
void compute_inpainting_region(const Result &inpainted_region, const Result &distance_to_boundary)
#define input
#define output
VecBase< float, 4 > float4
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
int2 initialize_jump_flooding_value(const int2 &texel, const bool is_seed)
void symmetric_separable_blur_variable_size(Context &context, const Result &input, const Result &radius, Result &output, const int weights_resolution=128, const int filter_type=R_FILTER_GAUSS)
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)
T min(const T &a, const T &b)
T interpolate(const T &a, const T &b, const FactorT &t)
T max(const T &a, const T &b)
static void cmp_node_inpaint_declare(NodeDeclarationBuilder &b)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
static void register_node_type_cmp_inpaint()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
VecBase< T, 3 > xyz() const
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)