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