gimpdrawable-bucket-fill.c 14.8 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
                           gdouble               seed_x,
                           gdouble               seed_y)
65 66 67 68 69 70 71 72 73 74 75 76 77
{
  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);
78
  buffer = gimp_drawable_get_bucket_fill_buffer (drawable, line_art,
79
                                                 distmap, options,
80 81 82
                                                 fill_transparent, fill_criterion,
                                                 threshold, sample_merged,
                                                 diagonal_neighbors,
83 84
                                                 line_art_stroke_threshold,
                                                 line_art_max_grow,
85 86 87 88 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
                                                 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,
142
                                      gfloat               *distmap,
143 144 145 146 147 148
                                      GimpFillOptions      *options,
                                      gboolean              fill_transparent,
                                      GimpSelectCriterion   fill_criterion,
                                      gdouble               threshold,
                                      gboolean              sample_merged,
                                      gboolean              diagonal_neighbors,
149
                                      gfloat                stroke_threshold,
150
                                      gint                  max_grow,
151 152 153 154 155 156 157
                                      gdouble               seed_x,
                                      gdouble               seed_y,
                                      GeglBuffer          **mask_buffer,
                                      gdouble              *mask_x,
                                      gdouble              *mask_y,
                                      gint                 *mask_width,
                                      gint                 *mask_height)
158
{
159 160 161
  GimpImage    *image;
  GimpPickable *pickable;
  GeglBuffer   *buffer;
162
  GeglBuffer   *new_mask;
163
  gboolean      antialias;
164
  gint          x, y, width, height;
165 166
  gint          mask_offset_x = 0;
  gint          mask_offset_y = 0;
167
  gint          sel_x, sel_y, sel_width, sel_height;
168

169 170 171
  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);
172

173
  image = gimp_item_get_image (GIMP_ITEM (drawable));
174

175 176
  if (! gimp_item_mask_intersect (GIMP_ITEM (drawable),
                                  &sel_x, &sel_y, &sel_width, &sel_height))
177
    return NULL;
178

179 180 181 182 183
  if (mask_buffer && *mask_buffer &&
      (fill_criterion == GIMP_SELECT_CRITERION_LINE_ART ||
       threshold      == 0.0))
    {
      gfloat pixel;
184

185 186 187 188 189 190 191 192 193 194
      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);
195 196 197 198 199
  if (sample_merged)
    pickable = GIMP_PICKABLE (image);
  else
    pickable = GIMP_PICKABLE (drawable);

200 201
  antialias = gimp_fill_options_get_antialias (options);

202
  /*  Do a seed bucket fill...To do this, calculate a new
203
   *  contiguous region.
204
   */
205
  new_mask = gimp_pickable_contiguous_region_by_seed (pickable,
206
                                                      line_art, distmap,
207 208 209 210 211
                                                      antialias,
                                                      threshold,
                                                      fill_transparent,
                                                      fill_criterion,
                                                      diagonal_neighbors,
212
                                                      stroke_threshold,
213
                                                      max_grow,
214 215 216 217 218 219 220 221 222 223 224 225
                                                      (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);
226 227
  width  -= x;
  height -= y;
228

229
  /*  If there is a selection, intersect the region bounds
230 231 232 233 234 235
   *  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().
   */
236
  if (! gimp_channel_is_empty (gimp_image_get_mask (image)))
237
    {
238 239
      gint off_x = 0;
      gint off_y = 0;
240

241
      if (sample_merged)
242
        gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
243

244
      if (! gimp_rectangle_intersect (x, y, width, height,
245

246 247
                                      sel_x + off_x, sel_y + off_y,
                                      sel_width,     sel_height,
248

249
                                      &x, &y, &width, &height))
250
        {
251 252
          if (! mask_buffer)
            g_object_unref (new_mask);
253
          /*  The fill region and the selection are disjoint; bail.  */
254 255
          gimp_unset_busy (image->gimp);

256
          return NULL;
257
        }
258
    }
259

260 261 262
  /*  make sure we handle the mask correctly if it was sample-merged  */
  if (sample_merged)
    {
263
      GimpItem *item = GIMP_ITEM (drawable);
264
      gint      off_x, off_y;
265

266 267
      /*  Limit the channel bounds to the drawable's extents  */
      gimp_item_get_offset (item, &off_x, &off_y);
268

269 270 271 272 273 274 275
      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);
276

277 278
      mask_offset_x = x;
      mask_offset_y = y;
279

280
     /*  translate mask bounds to drawable coords  */
281 282
      x -= off_x;
      y -= off_y;
283
    }
284
  else
285
    {
286 287
      mask_offset_x = x;
      mask_offset_y = y;
288
    }
289

290 291 292 293 294 295 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
  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++;
              }
        }
    }

336 337
  buffer = gimp_fill_options_create_buffer (options, drawable,
                                            GEGL_RECTANGLE (0, 0,
338 339
                                                            width, height),
                                            -x, -y);
340

341 342 343
  gimp_gegl_apply_opacity (buffer, NULL, NULL, buffer, new_mask,
                           -mask_offset_x, -mask_offset_y, 1.0);

344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
  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);
    }

372 373 374 375 376 377 378 379 380 381 382
  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);
383

384
  gimp_unset_busy (image->gimp);
385 386

  return buffer;
387
}