1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimpheal.c
5 * Copyright (C) Jean-Yves Couleaud <cjyves@free.fr>
6 * Copyright (C) 2013 Loren Merritt
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22 #include "config.h"
23
24 #include <stdint.h>
25 #include <string.h>
26
27 #include <gdk-pixbuf/gdk-pixbuf.h>
28 #include <gegl.h>
29
30 #include "libgimpbase/gimpbase.h"
31 #include "libgimpmath/gimpmath.h"
32
33 #include "paint-types.h"
34
35 #include "gegl/gimp-gegl-apply-operation.h"
36 #include "gegl/gimp-gegl-loops.h"
37
38 #include "core/gimpbrush.h"
39 #include "core/gimpdrawable.h"
40 #include "core/gimpdynamics.h"
41 #include "core/gimperror.h"
42 #include "core/gimpimage.h"
43 #include "core/gimppickable.h"
44 #include "core/gimptempbuf.h"
45
46 #include "gimpheal.h"
47 #include "gimpsourceoptions.h"
48
49 #include "gimp-intl.h"
50
51
52
53 /* NOTES
54 *
55 * The method used here is similar to the lighting invariant correction
56 * method but slightly different: we do not divide the RGB components,
57 * but subtract them I2 = I0 - I1, where I0 is the sample image to be
58 * corrected, I1 is the reference pattern. Then we solve DeltaI=0
59 * (Laplace) with I2 Dirichlet conditions at the borders of the
60 * mask. The solver is a red/black checker Gauss-Seidel with over-relaxation.
61 * It could benefit from a multi-grid evaluation of an initial solution
62 * before the main iteration loop.
63 *
64 * I reduced the convergence criteria to 0.1% (0.001) as we are
65 * dealing here with RGB integer components, more is overkill.
66 *
67 * Jean-Yves Couleaud cjyves@free.fr
68 */
69
70 static gboolean gimp_heal_start (GimpPaintCore *paint_core,
71 GimpDrawable *drawable,
72 GimpPaintOptions *paint_options,
73 const GimpCoords *coords,
74 GError **error);
75 static GeglBuffer * gimp_heal_get_paint_buffer (GimpPaintCore *core,
76 GimpDrawable *drawable,
77 GimpPaintOptions *paint_options,
78 GimpLayerMode paint_mode,
79 const GimpCoords *coords,
80 gint *paint_buffer_x,
81 gint *paint_buffer_y,
82 gint *paint_width,
83 gint *paint_height);
84
85 static void gimp_heal_motion (GimpSourceCore *source_core,
86 GimpDrawable *drawable,
87 GimpPaintOptions *paint_options,
88 const GimpCoords *coords,
89 GeglNode *op,
90 gdouble opacity,
91 GimpPickable *src_pickable,
92 GeglBuffer *src_buffer,
93 GeglRectangle *src_rect,
94 gint src_offset_x,
95 gint src_offset_y,
96 GeglBuffer *paint_buffer,
97 gint paint_buffer_x,
98 gint paint_buffer_y,
99 gint paint_area_offset_x,
100 gint paint_area_offset_y,
101 gint paint_area_width,
102 gint paint_area_height);
103
104
G_DEFINE_TYPE(GimpHeal,gimp_heal,GIMP_TYPE_SOURCE_CORE)105 G_DEFINE_TYPE (GimpHeal, gimp_heal, GIMP_TYPE_SOURCE_CORE)
106
107 #define parent_class gimp_heal_parent_class
108
109
110 void
111 gimp_heal_register (Gimp *gimp,
112 GimpPaintRegisterCallback callback)
113 {
114 (* callback) (gimp,
115 GIMP_TYPE_HEAL,
116 GIMP_TYPE_SOURCE_OPTIONS,
117 "gimp-heal",
118 _("Healing"),
119 "gimp-tool-heal");
120 }
121
122 static void
gimp_heal_class_init(GimpHealClass * klass)123 gimp_heal_class_init (GimpHealClass *klass)
124 {
125 GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
126 GimpSourceCoreClass *source_core_class = GIMP_SOURCE_CORE_CLASS (klass);
127
128 paint_core_class->start = gimp_heal_start;
129 paint_core_class->get_paint_buffer = gimp_heal_get_paint_buffer;
130
131 source_core_class->motion = gimp_heal_motion;
132 }
133
134 static void
gimp_heal_init(GimpHeal * heal)135 gimp_heal_init (GimpHeal *heal)
136 {
137 }
138
139 static gboolean
gimp_heal_start(GimpPaintCore * paint_core,GimpDrawable * drawable,GimpPaintOptions * paint_options,const GimpCoords * coords,GError ** error)140 gimp_heal_start (GimpPaintCore *paint_core,
141 GimpDrawable *drawable,
142 GimpPaintOptions *paint_options,
143 const GimpCoords *coords,
144 GError **error)
145 {
146 GimpSourceCore *source_core = GIMP_SOURCE_CORE (paint_core);
147
148 if (! GIMP_PAINT_CORE_CLASS (parent_class)->start (paint_core, drawable,
149 paint_options, coords,
150 error))
151 {
152 return FALSE;
153 }
154
155 if (! source_core->set_source && gimp_drawable_is_indexed (drawable))
156 {
157 g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
158 _("Healing does not operate on indexed layers."));
159 return FALSE;
160 }
161
162 return TRUE;
163 }
164
165 static GeglBuffer *
gimp_heal_get_paint_buffer(GimpPaintCore * core,GimpDrawable * drawable,GimpPaintOptions * paint_options,GimpLayerMode paint_mode,const GimpCoords * coords,gint * paint_buffer_x,gint * paint_buffer_y,gint * paint_width,gint * paint_height)166 gimp_heal_get_paint_buffer (GimpPaintCore *core,
167 GimpDrawable *drawable,
168 GimpPaintOptions *paint_options,
169 GimpLayerMode paint_mode,
170 const GimpCoords *coords,
171 gint *paint_buffer_x,
172 gint *paint_buffer_y,
173 gint *paint_width,
174 gint *paint_height)
175 {
176 return GIMP_PAINT_CORE_CLASS (parent_class)->get_paint_buffer (core,
177 drawable,
178 paint_options,
179 GIMP_LAYER_MODE_NORMAL,
180 coords,
181 paint_buffer_x,
182 paint_buffer_y,
183 paint_width,
184 paint_height);
185 }
186
187 /* Subtract bottom from top and store in result as a float
188 */
189 static void
gimp_heal_sub(GeglBuffer * top_buffer,const GeglRectangle * top_rect,GeglBuffer * bottom_buffer,const GeglRectangle * bottom_rect,GeglBuffer * result_buffer,const GeglRectangle * result_rect)190 gimp_heal_sub (GeglBuffer *top_buffer,
191 const GeglRectangle *top_rect,
192 GeglBuffer *bottom_buffer,
193 const GeglRectangle *bottom_rect,
194 GeglBuffer *result_buffer,
195 const GeglRectangle *result_rect)
196 {
197 GeglBufferIterator *iter;
198 const Babl *format = gegl_buffer_get_format (top_buffer);
199 gint n_components = babl_format_get_n_components (format);
200
201 if (n_components == 2)
202 format = babl_format ("Y'A float");
203 else if (n_components == 4)
204 format = babl_format ("R'G'B'A float");
205 else
206 g_return_if_reached ();
207
208 iter = gegl_buffer_iterator_new (top_buffer, top_rect, 0, format,
209 GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 3);
210
211 gegl_buffer_iterator_add (iter, bottom_buffer, bottom_rect, 0, format,
212 GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
213
214 gegl_buffer_iterator_add (iter, result_buffer, result_rect, 0,
215 babl_format_n (babl_type ("float"), n_components),
216 GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
217
218 while (gegl_buffer_iterator_next (iter))
219 {
220 gfloat *t = iter->items[0].data;
221 gfloat *b = iter->items[1].data;
222 gfloat *r = iter->items[2].data;
223 gint length = iter->length * n_components;
224
225 while (length--)
226 *r++ = *t++ - *b++;
227 }
228 }
229
230 /* Add first to second and store in result
231 */
232 static void
gimp_heal_add(GeglBuffer * first_buffer,const GeglRectangle * first_rect,GeglBuffer * second_buffer,const GeglRectangle * second_rect,GeglBuffer * result_buffer,const GeglRectangle * result_rect)233 gimp_heal_add (GeglBuffer *first_buffer,
234 const GeglRectangle *first_rect,
235 GeglBuffer *second_buffer,
236 const GeglRectangle *second_rect,
237 GeglBuffer *result_buffer,
238 const GeglRectangle *result_rect)
239 {
240 GeglBufferIterator *iter;
241 const Babl *format = gegl_buffer_get_format (result_buffer);
242 gint n_components = babl_format_get_n_components (format);
243
244 if (n_components == 2)
245 format = babl_format ("Y'A float");
246 else if (n_components == 4)
247 format = babl_format ("R'G'B'A float");
248 else
249 g_return_if_reached ();
250
251 iter = gegl_buffer_iterator_new (first_buffer, first_rect, 0,
252 babl_format_n (babl_type ("float"),
253 n_components),
254 GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 3);
255
256 gegl_buffer_iterator_add (iter, second_buffer, second_rect, 0, format,
257 GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
258
259 gegl_buffer_iterator_add (iter, result_buffer, result_rect, 0, format,
260 GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
261
262 while (gegl_buffer_iterator_next (iter))
263 {
264 gfloat *f = iter->items[0].data;
265 gfloat *s = iter->items[1].data;
266 gfloat *r = iter->items[2].data;
267 gint length = iter->length * n_components;
268
269 while (length--)
270 *r++ = *f++ + *s++;
271 }
272 }
273
274 #if defined(__SSE__) && defined(__GNUC__) && __GNUC__ >= 4
275 static float
gimp_heal_laplace_iteration_sse(gfloat * pixels,gfloat * Adiag,gint * Aidx,gfloat w,gint nmask)276 gimp_heal_laplace_iteration_sse (gfloat *pixels,
277 gfloat *Adiag,
278 gint *Aidx,
279 gfloat w,
280 gint nmask)
281 {
282 typedef float v4sf __attribute__((vector_size(16)));
283 gint i;
284 v4sf wv = { w, w, w, w };
285 v4sf err = { 0, 0, 0, 0 };
286 union { v4sf v; float f[4]; } erru;
287
288 #define Xv(j) (*(v4sf*)&pixels[Aidx[i * 5 + j]])
289
290 for (i = 0; i < nmask; i++)
291 {
292 v4sf a = { Adiag[i], Adiag[i], Adiag[i], Adiag[i] };
293 v4sf diff = a * Xv(0) - wv * (Xv(1) + Xv(2) + Xv(3) + Xv(4));
294
295 Xv(0) -= diff;
296 err += diff * diff;
297 }
298
299 erru.v = err;
300
301 return erru.f[0] + erru.f[1] + erru.f[2] + erru.f[3];
302 }
303 #endif
304
305 /* Perform one iteration of Gauss-Seidel, and return the sum squared residual.
306 */
307 static float
gimp_heal_laplace_iteration(gfloat * pixels,gfloat * Adiag,gint * Aidx,gfloat w,gint nmask,gint depth)308 gimp_heal_laplace_iteration (gfloat *pixels,
309 gfloat *Adiag,
310 gint *Aidx,
311 gfloat w,
312 gint nmask,
313 gint depth)
314 {
315 gint i, k;
316 gfloat err = 0;
317
318 #if defined(__SSE__) && defined(__GNUC__) && __GNUC__ >= 4
319 if (depth == 4)
320 return gimp_heal_laplace_iteration_sse (pixels, Adiag, Aidx, w, nmask);
321 #endif
322
323 for (i = 0; i < nmask; i++)
324 {
325 gint j0 = Aidx[i * 5 + 0];
326 gint j1 = Aidx[i * 5 + 1];
327 gint j2 = Aidx[i * 5 + 2];
328 gint j3 = Aidx[i * 5 + 3];
329 gint j4 = Aidx[i * 5 + 4];
330 gfloat a = Adiag[i];
331
332 for (k = 0; k < depth; k++)
333 {
334 gfloat diff = (a * pixels[j0 + k] -
335 w * (pixels[j1 + k] +
336 pixels[j2 + k] +
337 pixels[j3 + k] +
338 pixels[j4 + k]));
339
340 pixels[j0 + k] -= diff;
341 err += diff * diff;
342 }
343 }
344
345 return err;
346 }
347
348 /* Solve the laplace equation for pixels and store the result in-place.
349 */
350 static void
gimp_heal_laplace_loop(gfloat * pixels,gint height,gint depth,gint width,guchar * mask)351 gimp_heal_laplace_loop (gfloat *pixels,
352 gint height,
353 gint depth,
354 gint width,
355 guchar *mask)
356 {
357 /* Tolerate a total deviation-from-smoothness of 0.1 LSBs at 8bit depth. */
358 #define EPSILON (0.1/255)
359 #define MAX_ITER 500
360
361 gint i, j, iter, parity, nmask, zero;
362 gfloat *Adiag;
363 gint *Aidx;
364 gfloat w;
365
366 Adiag = g_new (gfloat, width * height);
367 Aidx = g_new (gint, 5 * width * height);
368
369 /* All off-diagonal elements of A are either -1 or 0. We could store it as a
370 * general-purpose sparse matrix, but that adds some unnecessary overhead to
371 * the inner loop. Instead, assume exactly 4 off-diagonal elements in each
372 * row, all of which have value -1. Any row that in fact wants less than 4
373 * coefs can put them in a dummy column to be multiplied by an empty pixel.
374 */
375 zero = depth * width * height;
376 memset (pixels + zero, 0, depth * sizeof (gfloat));
377
378 /* Construct the system of equations.
379 * Arrange Aidx in checkerboard order, so that a single linear pass over that
380 * array results updating all of the red cells and then all of the black cells.
381 */
382 nmask = 0;
383 for (parity = 0; parity < 2; parity++)
384 for (i = 0; i < height; i++)
385 for (j = (i&1)^parity; j < width; j+=2)
386 if (mask[j + i * width])
387 {
388 #define A_NEIGHBOR(o,di,dj) \
389 if ((dj<0 && j==0) || (dj>0 && j==width-1) || (di<0 && i==0) || (di>0 && i==height-1)) \
390 Aidx[o + nmask * 5] = zero; \
391 else \
392 Aidx[o + nmask * 5] = ((i + di) * width + (j + dj)) * depth;
393
394 /* Omit Dirichlet conditions for any neighbors off the
395 * edge of the canvas.
396 */
397 Adiag[nmask] = 4 - (i==0) - (j==0) - (i==height-1) - (j==width-1);
398 A_NEIGHBOR (0, 0, 0);
399 A_NEIGHBOR (1, 0, 1);
400 A_NEIGHBOR (2, 1, 0);
401 A_NEIGHBOR (3, 0, -1);
402 A_NEIGHBOR (4, -1, 0);
403 nmask++;
404 }
405
406 /* Empirically optimal over-relaxation factor. (Benchmarked on
407 * round brushes, at least. I don't know whether aspect ratio
408 * affects it.)
409 */
410 w = 2.0 - 1.0 / (0.1575 * sqrt (nmask) + 0.8);
411 w *= 0.25;
412 for (i = 0; i < nmask; i++)
413 Adiag[i] *= w;
414
415 /* Gauss-Seidel with successive over-relaxation */
416 for (iter = 0; iter < MAX_ITER; iter++)
417 {
418 gfloat err = gimp_heal_laplace_iteration (pixels, Adiag, Aidx,
419 w, nmask, depth);
420 if (err < EPSILON * EPSILON * w * w)
421 break;
422 }
423
424 g_free (Adiag);
425 g_free (Aidx);
426 }
427
428 /* Original Algorithm Design:
429 *
430 * T. Georgiev, "Photoshop Healing Brush: a Tool for Seamless Cloning
431 * http://www.tgeorgiev.net/Photoshop_Healing.pdf
432 */
433 static void
gimp_heal(GeglBuffer * src_buffer,const GeglRectangle * src_rect,GeglBuffer * dest_buffer,const GeglRectangle * dest_rect,GeglBuffer * mask_buffer,const GeglRectangle * mask_rect)434 gimp_heal (GeglBuffer *src_buffer,
435 const GeglRectangle *src_rect,
436 GeglBuffer *dest_buffer,
437 const GeglRectangle *dest_rect,
438 GeglBuffer *mask_buffer,
439 const GeglRectangle *mask_rect)
440 {
441 const Babl *src_format;
442 const Babl *dest_format;
443 gint src_components;
444 gint dest_components;
445 gint width;
446 gint height;
447 gfloat *diff, *diff_alloc;
448 GeglBuffer *diff_buffer;
449 guchar *mask;
450
451 src_format = gegl_buffer_get_format (src_buffer);
452 dest_format = gegl_buffer_get_format (dest_buffer);
453
454 src_components = babl_format_get_n_components (src_format);
455 dest_components = babl_format_get_n_components (dest_format);
456
457 width = gegl_buffer_get_width (src_buffer);
458 height = gegl_buffer_get_height (src_buffer);
459
460 g_return_if_fail (src_components == dest_components);
461
462 diff_alloc = g_new (gfloat, 4 + (width * height + 1) * src_components);
463 diff = (gfloat*)(((uintptr_t)diff_alloc + 15) & ~15);
464
465 diff_buffer =
466 gegl_buffer_linear_new_from_data (diff,
467 babl_format_n (babl_type ("float"),
468 src_components),
469 GEGL_RECTANGLE (0, 0, width, height),
470 GEGL_AUTO_ROWSTRIDE,
471 (GDestroyNotify) g_free, diff_alloc);
472
473 /* subtract pattern from image and store the result as a float in diff */
474 gimp_heal_sub (dest_buffer, dest_rect,
475 src_buffer, src_rect,
476 diff_buffer, GEGL_RECTANGLE (0, 0, width, height));
477
478 mask = g_new (guchar, mask_rect->width * mask_rect->height);
479
480 gegl_buffer_get (mask_buffer, mask_rect, 1.0, babl_format ("Y u8"),
481 mask, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
482
483 gimp_heal_laplace_loop (diff, height, src_components, width, mask);
484
485 g_free (mask);
486
487 /* add solution to original image and store in dest */
488 gimp_heal_add (diff_buffer, GEGL_RECTANGLE (0, 0, width, height),
489 src_buffer, src_rect,
490 dest_buffer, dest_rect);
491
492 g_object_unref (diff_buffer);
493 }
494
495 static void
gimp_heal_motion(GimpSourceCore * source_core,GimpDrawable * drawable,GimpPaintOptions * paint_options,const GimpCoords * coords,GeglNode * op,gdouble opacity,GimpPickable * src_pickable,GeglBuffer * src_buffer,GeglRectangle * src_rect,gint src_offset_x,gint src_offset_y,GeglBuffer * paint_buffer,gint paint_buffer_x,gint paint_buffer_y,gint paint_area_offset_x,gint paint_area_offset_y,gint paint_area_width,gint paint_area_height)496 gimp_heal_motion (GimpSourceCore *source_core,
497 GimpDrawable *drawable,
498 GimpPaintOptions *paint_options,
499 const GimpCoords *coords,
500 GeglNode *op,
501 gdouble opacity,
502 GimpPickable *src_pickable,
503 GeglBuffer *src_buffer,
504 GeglRectangle *src_rect,
505 gint src_offset_x,
506 gint src_offset_y,
507 GeglBuffer *paint_buffer,
508 gint paint_buffer_x,
509 gint paint_buffer_y,
510 gint paint_area_offset_x,
511 gint paint_area_offset_y,
512 gint paint_area_width,
513 gint paint_area_height)
514 {
515 GimpPaintCore *paint_core = GIMP_PAINT_CORE (source_core);
516 GimpContext *context = GIMP_CONTEXT (paint_options);
517 GimpSourceOptions *src_options = GIMP_SOURCE_OPTIONS (paint_options);
518 GimpDynamics *dynamics = GIMP_BRUSH_CORE (paint_core)->dynamics;
519 GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
520 GeglBuffer *src_copy;
521 GeglBuffer *mask_buffer;
522 GimpPickable *dest_pickable;
523 const GimpTempBuf *mask_buf;
524 gdouble fade_point;
525 gdouble force;
526 gint mask_off_x;
527 gint mask_off_y;
528 gint dest_pickable_off_x;
529 gint dest_pickable_off_y;
530
531 fade_point = gimp_paint_options_get_fade (paint_options, image,
532 paint_core->pixel_dist);
533
534 if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
535 force = gimp_dynamics_get_linear_value (dynamics,
536 GIMP_DYNAMICS_OUTPUT_FORCE,
537 coords,
538 paint_options,
539 fade_point);
540 else
541 force = paint_options->brush_force;
542
543 mask_buf = gimp_brush_core_get_brush_mask (GIMP_BRUSH_CORE (source_core),
544 coords,
545 GIMP_BRUSH_HARD,
546 force);
547
548 if (! mask_buf)
549 return;
550
551 /* check that all buffers are of the same size */
552 if (src_rect->width != gegl_buffer_get_width (paint_buffer) ||
553 src_rect->height != gegl_buffer_get_height (paint_buffer))
554 {
555 /* this generally means that the source point has hit the edge
556 * of the layer, so it is not an error and we should not
557 * complain, just don't do anything
558 */
559 return;
560 }
561
562 /* heal should work in perceptual space, use R'G'B' instead of RGB */
563 src_copy = gegl_buffer_new (GEGL_RECTANGLE (paint_area_offset_x,
564 paint_area_offset_y,
565 src_rect->width,
566 src_rect->height),
567 babl_format ("R'G'B'A float"));
568
569 if (! op)
570 {
571 gimp_gegl_buffer_copy (src_buffer, src_rect, GEGL_ABYSS_NONE,
572 src_copy, gegl_buffer_get_extent (src_copy));
573 }
574 else
575 {
576 gimp_gegl_apply_operation (src_buffer, NULL, NULL, op,
577 src_copy, gegl_buffer_get_extent (src_copy),
578 FALSE);
579 }
580
581 if (src_options->sample_merged)
582 {
583 dest_pickable = GIMP_PICKABLE (image);
584
585 gimp_item_get_offset (GIMP_ITEM (drawable),
586 &dest_pickable_off_x,
587 &dest_pickable_off_y);
588 }
589 else
590 {
591 dest_pickable = GIMP_PICKABLE (drawable);
592
593 dest_pickable_off_x = 0;
594 dest_pickable_off_y = 0;
595 }
596
597 gimp_gegl_buffer_copy (gimp_pickable_get_buffer (dest_pickable),
598 GEGL_RECTANGLE (paint_buffer_x + dest_pickable_off_x,
599 paint_buffer_y + dest_pickable_off_y,
600 gegl_buffer_get_width (paint_buffer),
601 gegl_buffer_get_height (paint_buffer)),
602 GEGL_ABYSS_NONE,
603 paint_buffer,
604 GEGL_RECTANGLE (paint_area_offset_x,
605 paint_area_offset_y,
606 paint_area_width,
607 paint_area_height));
608
609 mask_buffer = gimp_temp_buf_create_buffer ((GimpTempBuf *) mask_buf);
610
611 /* find the offset of the brush mask's rect */
612 {
613 gint x = (gint) floor (coords->x) - (gegl_buffer_get_width (mask_buffer) >> 1);
614 gint y = (gint) floor (coords->y) - (gegl_buffer_get_height (mask_buffer) >> 1);
615
616 mask_off_x = (x < 0) ? -x : 0;
617 mask_off_y = (y < 0) ? -y : 0;
618 }
619
620 gimp_heal (src_copy, gegl_buffer_get_extent (src_copy),
621 paint_buffer,
622 GEGL_RECTANGLE (paint_area_offset_x,
623 paint_area_offset_y,
624 paint_area_width,
625 paint_area_height),
626 mask_buffer,
627 GEGL_RECTANGLE (mask_off_x, mask_off_y,
628 paint_area_width,
629 paint_area_height));
630
631 g_object_unref (src_copy);
632 g_object_unref (mask_buffer);
633
634 /* replace the canvas with our healed data */
635 gimp_brush_core_replace_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
636 coords,
637 MIN (opacity, GIMP_OPACITY_OPAQUE),
638 gimp_context_get_opacity (context),
639 gimp_paint_options_get_brush_mode (paint_options),
640 force,
641 GIMP_PAINT_INCREMENTAL);
642 }
643