gimpdrawable-bucket-fill.c 15.4 KB
Newer Older
1
/* GIMP - The GNU Image Manipulation Program
2 3
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
7 8 9 10 11 12 13 14
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
15
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16 17 18 19
 */

#include "config.h"

20
#include <cairo.h>
21
#define GEGL_ITERATOR2_API
22
#include <gegl.h>
23
#include <gdk-pixbuf/gdk-pixbuf.h>
24

25
#include "libgimpbase/gimpbase.h"
26 27 28 29
#include "libgimpcolor/gimpcolor.h"

#include "core-types.h"

30
#include "gegl/gimp-gegl-apply-operation.h"
31 32
#include "gegl/gimp-gegl-mask.h"
#include "gegl/gimp-gegl-mask-combine.h"
33
#include "gegl/gimp-gegl-utils.h"
34

35 36
#include "operations/layer-modes/gimp-layer-modes.h"

37
#include "gimp.h"
38
#include "gimpchannel.h"
39 40
#include "gimpdrawable.h"
#include "gimpdrawable-bucket-fill.h"
41
#include "gimpfilloptions.h"
42
#include "gimpimage.h"
43 44
#include "gimppickable.h"
#include "gimppickable-contiguous-region.h"
45

46
#include "gimp-intl.h"
47 48 49 50


/*  public functions  */

51
void
Michael Natterer's avatar
Michael Natterer committed
52
gimp_drawable_bucket_fill (GimpDrawable         *drawable,
53
                           GeglBuffer           *line_art,
54
                           gfloat               *distmap,
55
                           GimpFillOptions      *options,
Michael Natterer's avatar
Michael Natterer committed
56 57 58 59
                           gboolean              fill_transparent,
                           GimpSelectCriterion   fill_criterion,
                           gdouble               threshold,
                           gboolean              sample_merged,
60
                           gboolean              diagonal_neighbors,
61 62
                           gfloat                line_art_stroke_threshold,
                           gint                  line_art_max_grow,
63 64
                           gint                  line_art_segment_max_length,
                           gint                  line_art_spline_max_length,
65 66
                           gdouble               seed_x,
                           gdouble               seed_y)
67 68 69 70 71 72 73 74 75 76 77 78 79
{
  GimpImage  *image;
  GeglBuffer *buffer;
  gdouble     mask_x;
  gdouble     mask_y;
  gint        width, height;

  g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
  g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
  g_return_if_fail (GIMP_IS_FILL_OPTIONS (options));

  image = gimp_item_get_image (GIMP_ITEM (drawable));
  gimp_set_busy (image->gimp);
80
  buffer = gimp_drawable_get_bucket_fill_buffer (drawable, line_art,
81
                                                 distmap, options,
82 83 84
                                                 fill_transparent, fill_criterion,
                                                 threshold, sample_merged,
                                                 diagonal_neighbors,
85 86
                                                 line_art_stroke_threshold,
                                                 line_art_max_grow,
87 88
                                                 line_art_segment_max_length,
                                                 line_art_spline_max_length,
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
                                                 seed_x, seed_y, NULL,
                                                 &mask_x, &mask_y, &width, &height);

  if (buffer)
    {
      /*  Apply it to the image  */
      gimp_drawable_apply_buffer (drawable, buffer,
                                  GEGL_RECTANGLE (0, 0, width, height),
                                  TRUE, C_("undo-type", "Bucket Fill"),
                                  gimp_context_get_opacity (GIMP_CONTEXT (options)),
                                  gimp_context_get_paint_mode (GIMP_CONTEXT (options)),
                                  GIMP_LAYER_COLOR_SPACE_AUTO,
                                  GIMP_LAYER_COLOR_SPACE_AUTO,
                                  gimp_layer_mode_get_paint_composite_mode (
                                                                            gimp_context_get_paint_mode (GIMP_CONTEXT (options))),
                                  NULL, (gint) mask_x, mask_y);
      g_object_unref (buffer);

      gimp_drawable_update (drawable, mask_x, mask_y, width, height);
    }
  gimp_unset_busy (image->gimp);
}

/**
 * gimp_drawable_get_bucket_fill_buffer:
 * @drawable: the @GimpDrawable to edit.
 * @line_art: optional pre-computed line art if @fill_criterion is
 *            GIMP_SELECT_CRITERION_LINE_ART.
 * @options:
 * @fill_transparent:
 * @fill_criterion:
 * @threshold:
 * @sample_merged:
 * @diagonal_neighbors:
 * @seed_x: X coordinate to start the fill.
 * @seed_y: Y coordinate to start the fill.
 * @mask_buffer: mask of the fill in-progress when in an interactive
 *               filling process. Set to NULL if you need a one-time
 *               fill.
 * @mask_x: returned x bound of @mask_buffer.
 * @mask_y: returned x bound of @mask_buffer.
 * @mask_width: returned width bound of @mask_buffer.
 * @mask_height: returned height bound of @mask_buffer.
 *
 * Creates the fill buffer for a bucket fill operation on @drawable,
 * without actually applying it (if you want to apply it directly as a
 * one-time operation, use gimp_drawable_bucket_fill() instead). If
 * @mask_buffer is not NULL, the intermediate fill mask will also be
 * returned. This fill mask can later be reused in successive calls to
 * gimp_drawable_get_bucket_fill_buffer() for interactive filling.
 *
 * Returns: a fill buffer which can be directly applied to @drawable, or
 *          used in a drawable filter as preview.
 */
GeglBuffer *
gimp_drawable_get_bucket_fill_buffer (GimpDrawable         *drawable,
                                      GeglBuffer           *line_art,
146
                                      gfloat               *distmap,
147 148 149 150 151 152
                                      GimpFillOptions      *options,
                                      gboolean              fill_transparent,
                                      GimpSelectCriterion   fill_criterion,
                                      gdouble               threshold,
                                      gboolean              sample_merged,
                                      gboolean              diagonal_neighbors,
153
                                      gfloat                stroke_threshold,
154
                                      gint                  max_grow,
155 156
                                      gint                  segment_max_length,
                                      gint                  spline_max_length,
157 158 159 160 161 162 163
                                      gdouble               seed_x,
                                      gdouble               seed_y,
                                      GeglBuffer          **mask_buffer,
                                      gdouble              *mask_x,
                                      gdouble              *mask_y,
                                      gint                 *mask_width,
                                      gint                 *mask_height)
164
{
165 166 167
  GimpImage    *image;
  GimpPickable *pickable;
  GeglBuffer   *buffer;
168
  GeglBuffer   *new_mask;
169
  gboolean      antialias;
170
  gint          x, y, width, height;
171 172
  gint          mask_offset_x = 0;
  gint          mask_offset_y = 0;
173
  gint          sel_x, sel_y, sel_width, sel_height;
174

175 176 177
  g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
  g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
  g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), NULL);
178

179
  image = gimp_item_get_image (GIMP_ITEM (drawable));
180

181 182
  if (! gimp_item_mask_intersect (GIMP_ITEM (drawable),
                                  &sel_x, &sel_y, &sel_width, &sel_height))
183
    return NULL;
184

185 186 187 188 189
  if (mask_buffer && *mask_buffer &&
      (fill_criterion == GIMP_SELECT_CRITERION_LINE_ART ||
       threshold      == 0.0))
    {
      gfloat pixel;
190

191 192 193 194 195 196 197 198 199 200
      gegl_buffer_sample (*mask_buffer, seed_x, seed_y, NULL, &pixel,
                          babl_format ("Y float"),
                          GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);

      if (pixel != 0.0)
        /* Already selected. This seed won't change the selection. */
        return NULL;
    }

  gimp_set_busy (image->gimp);
201 202 203 204 205
  if (sample_merged)
    pickable = GIMP_PICKABLE (image);
  else
    pickable = GIMP_PICKABLE (drawable);

206 207
  antialias = gimp_fill_options_get_antialias (options);

208
  /*  Do a seed bucket fill...To do this, calculate a new
209
   *  contiguous region.
210
   */
211
  new_mask = gimp_pickable_contiguous_region_by_seed (pickable,
212
                                                      line_art, distmap,
213 214 215 216 217
                                                      antialias,
                                                      threshold,
                                                      fill_transparent,
                                                      fill_criterion,
                                                      diagonal_neighbors,
218
                                                      stroke_threshold,
219
                                                      max_grow,
220 221
                                                      segment_max_length,
                                                      spline_max_length,
222 223 224 225 226 227 228 229 230 231 232 233
                                                      (gint) seed_x,
                                                      (gint) seed_y);
  if (mask_buffer && *mask_buffer)
    {
      gimp_gegl_mask_combine_buffer (new_mask, *mask_buffer,
                                     GIMP_CHANNEL_OP_ADD, 0, 0);
      g_object_unref (*mask_buffer);
    }
  if (mask_buffer)
    *mask_buffer = new_mask;

  gimp_gegl_mask_bounds (new_mask, &x, &y, &width, &height);
234 235
  width  -= x;
  height -= y;
236

237
  /*  If there is a selection, intersect the region bounds
238 239 240 241 242 243
   *  with the selection bounds, to avoid processing areas
   *  that are going to be masked out anyway.  The actual
   *  intersection of the fill region with the mask data
   *  happens when combining the fill buffer, in
   *  gimp_drawable_apply_buffer().
   */
244
  if (! gimp_channel_is_empty (gimp_image_get_mask (image)))
245
    {
246 247
      gint off_x = 0;
      gint off_y = 0;
248

249
      if (sample_merged)
250
        gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
251

252
      if (! gimp_rectangle_intersect (x, y, width, height,
253

254 255
                                      sel_x + off_x, sel_y + off_y,
                                      sel_width,     sel_height,
256

257
                                      &x, &y, &width, &height))
258
        {
259 260
          if (! mask_buffer)
            g_object_unref (new_mask);
261
          /*  The fill region and the selection are disjoint; bail.  */
262 263
          gimp_unset_busy (image->gimp);

264
          return NULL;
265
        }
266
    }
267

268 269 270
  /*  make sure we handle the mask correctly if it was sample-merged  */
  if (sample_merged)
    {
271
      GimpItem *item = GIMP_ITEM (drawable);
272
      gint      off_x, off_y;
273

274 275
      /*  Limit the channel bounds to the drawable's extents  */
      gimp_item_get_offset (item, &off_x, &off_y);
276

277 278 279 280 281 282 283
      gimp_rectangle_intersect (x, y, width, height,

                                off_x, off_y,
                                gimp_item_get_width (item),
                                gimp_item_get_height (item),

                                &x, &y, &width, &height);
284

285 286
      mask_offset_x = x;
      mask_offset_y = y;
287

288
     /*  translate mask bounds to drawable coords  */
289 290
      x -= off_x;
      y -= off_y;
291
    }
292
  else
293
    {
294 295
      mask_offset_x = x;
      mask_offset_y = y;
296
    }
297

298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
  if (fill_criterion == GIMP_SELECT_CRITERION_LINE_ART)
    {
      /* The smart colorization leaves some very irritating unselected
       * pixels in some edge cases. Just flood any isolated pixel inside
       * the final mask.
       */
      GeglBufferIterator *gi;

      gi = gegl_buffer_iterator_new (new_mask, GEGL_RECTANGLE (x, y, width, height),
                                     0, NULL, GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 5);
      gegl_buffer_iterator_add (gi, new_mask, GEGL_RECTANGLE (x, y - 1, width, height),
                                0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_WHITE);
      gegl_buffer_iterator_add (gi, new_mask, GEGL_RECTANGLE (x, y + 1, width, height),
                                0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_WHITE);
      gegl_buffer_iterator_add (gi, new_mask, GEGL_RECTANGLE (x - 1, y, width, height),
                                0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_WHITE);
      gegl_buffer_iterator_add (gi, new_mask, GEGL_RECTANGLE (x + 1, y, width, height),
                                0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_WHITE);
      while (gegl_buffer_iterator_next (gi))
        {
          gfloat *m      = (gfloat*) gi->items[0].data;
          gfloat *py     = (gfloat*) gi->items[1].data;
          gfloat *ny     = (gfloat*) gi->items[2].data;
          gfloat *px     = (gfloat*) gi->items[3].data;
          gfloat *nx     = (gfloat*) gi->items[4].data;
          gint    startx = gi->items[0].roi.x;
          gint    starty = gi->items[0].roi.y;
          gint    endy   = starty + gi->items[0].roi.height;
          gint    endx   = startx + gi->items[0].roi.width;
          gint    i;
          gint    j;

          for (j = starty; j < endy; j++)
            for (i = startx; i < endx; i++)
              {
                if (! *m && *py && *ny && *px && *nx)
                  *m = 1.0;
                m++;
                py++;
                ny++;
                px++;
                nx++;
              }
        }
    }

344 345
  buffer = gimp_fill_options_create_buffer (options, drawable,
                                            GEGL_RECTANGLE (0, 0,
346 347
                                                            width, height),
                                            -x, -y);
348

349 350 351
  gimp_gegl_apply_opacity (buffer, NULL, NULL, buffer, new_mask,
                           -mask_offset_x, -mask_offset_y, 1.0);

352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
  if (fill_criterion == GIMP_SELECT_CRITERION_LINE_ART && antialias)
    {
      /* Antialias for the line art algorithm is not applied during mask
       * creation because it is not based on individual pixel colors.
       * Instead we just want to apply it on the borders of the mask at
       * the end (since the mask can evolve, we don't want to actually
       * touch it, but only the intermediate results).
       */
      GeglNode   *graph;
      GeglNode   *input;
      GeglNode   *op;

      graph = gegl_node_new ();
      input = gegl_node_new_child (graph,
                                   "operation", "gegl:buffer-source",
                                   "buffer", buffer,
                                   NULL);
      op  = gegl_node_new_child (graph,
                                 "operation", "gegl:gaussian-blur",
                                 "std-dev-x", 0.5,
                                 "std-dev-y", 0.5,
                                 NULL);
      gegl_node_connect_to (input, "output", op, "input");
      gegl_node_blit_buffer (op, buffer, NULL, 0,
                             GEGL_ABYSS_NONE);
      g_object_unref (graph);
    }

380 381 382 383 384 385 386 387 388 389 390
  if (mask_x)
    *mask_x = x;
  if (mask_y)
    *mask_y = y;
  if (mask_width)
    *mask_width = width;
  if (mask_height)
    *mask_height = height;

  if (! mask_buffer)
    g_object_unref (new_mask);
391

392
  gimp_unset_busy (image->gimp);
393 394

  return buffer;
395
}