[gimp/gimp-2-10] app: replace gegl:watershed-transform with custom algorithm.



commit e5bea42b6f7018faee40bd251b652b9f7b7df9b9
Author: Jehan <jehan girinstud io>
Date:   Wed Nov 21 15:52:51 2018 +0100

    app: replace gegl:watershed-transform with custom algorithm.
    
    We don't really need to flow every line art pixel and this new
    implementation is simpler (because we don't actually need over-featured
    watershedding), and a lot lot faster, making the line art bucket fill
    now very reactive.
    For this, I am keeping the computed distance map, as well as local
    thickness map around to be used when flooding the line art pixels
    (basically I try to flood half the stroke thickness).
    
    Note that there are still some issues with this new implementation as it
    doesn't properly flood yet created (i.e. invisible) splines and
    segments, and in particular the ones between 2 colored sections. I am
    going to fix this next.
    
    (cherry picked from commit 3467acf096fab265d55e2882b91b870da5db975b)

 app/core/gimpchannel-select.c             |   2 +-
 app/core/gimpdrawable-bucket-fill.c       |   9 +-
 app/core/gimpdrawable-bucket-fill.h       |   4 +
 app/core/gimplineart.c                    |  68 ++++---
 app/core/gimplineart.h                    |  32 +--
 app/core/gimppickable-contiguous-region.c | 327 +++++++++++++++++++++++++-----
 app/core/gimppickable-contiguous-region.h |  12 +-
 app/pdb/drawable-edit-cmds.c              |   2 +-
 app/tools/gimpbucketfilltool.c            |  31 ++-
 app/tools/gimpfuzzyselecttool.c           |   2 +-
 pdb/groups/drawable_edit.pdb              |   2 +-
 11 files changed, 383 insertions(+), 108 deletions(-)
---
diff --git a/app/core/gimpchannel-select.c b/app/core/gimpchannel-select.c
index 892dbb203e..6abe01f960 100644
--- a/app/core/gimpchannel-select.c
+++ b/app/core/gimpchannel-select.c
@@ -516,7 +516,7 @@ gimp_channel_select_fuzzy (GimpChannel         *channel,
     pickable = GIMP_PICKABLE (drawable);
 
   add_on = gimp_pickable_contiguous_region_by_seed (pickable,
-                                                    NULL,
+                                                    NULL, NULL, NULL,
                                                     antialias,
                                                     threshold,
                                                     select_transparent,
diff --git a/app/core/gimpdrawable-bucket-fill.c b/app/core/gimpdrawable-bucket-fill.c
index 4aebb63535..7025018ff8 100644
--- a/app/core/gimpdrawable-bucket-fill.c
+++ b/app/core/gimpdrawable-bucket-fill.c
@@ -50,6 +50,8 @@
 void
 gimp_drawable_bucket_fill (GimpDrawable         *drawable,
                            GeglBuffer           *line_art,
+                           gfloat               *distmap,
+                           gfloat               *thickmap,
                            GimpFillOptions      *options,
                            gboolean              fill_transparent,
                            GimpSelectCriterion   fill_criterion,
@@ -72,7 +74,8 @@ gimp_drawable_bucket_fill (GimpDrawable         *drawable,
 
   image = gimp_item_get_image (GIMP_ITEM (drawable));
   gimp_set_busy (image->gimp);
-  buffer = gimp_drawable_get_bucket_fill_buffer (drawable, line_art, options,
+  buffer = gimp_drawable_get_bucket_fill_buffer (drawable, line_art,
+                                                 distmap, thickmap, options,
                                                  fill_transparent, fill_criterion,
                                                  threshold, sample_merged,
                                                  diagonal_neighbors,
@@ -134,6 +137,8 @@ gimp_drawable_bucket_fill (GimpDrawable         *drawable,
 GeglBuffer *
 gimp_drawable_get_bucket_fill_buffer (GimpDrawable         *drawable,
                                       GeglBuffer           *line_art,
+                                      gfloat               *distmap,
+                                      gfloat               *thickmap,
                                       GimpFillOptions      *options,
                                       gboolean              fill_transparent,
                                       GimpSelectCriterion   fill_criterion,
@@ -196,7 +201,7 @@ gimp_drawable_get_bucket_fill_buffer (GimpDrawable         *drawable,
    *  contiguous region.
    */
   new_mask = gimp_pickable_contiguous_region_by_seed (pickable,
-                                                      line_art,
+                                                      line_art, distmap, thickmap,
                                                       antialias,
                                                       threshold,
                                                       fill_transparent,
diff --git a/app/core/gimpdrawable-bucket-fill.h b/app/core/gimpdrawable-bucket-fill.h
index 9cb566f699..7b6cae9888 100644
--- a/app/core/gimpdrawable-bucket-fill.h
+++ b/app/core/gimpdrawable-bucket-fill.h
@@ -21,6 +21,8 @@
 
 void         gimp_drawable_bucket_fill            (GimpDrawable         *drawable,
                                                    GeglBuffer           *line_art,
+                                                   gfloat               *distmap,
+                                                   gfloat               *thickmap,
                                                    GimpFillOptions      *options,
                                                    gboolean              fill_transparent,
                                                    GimpSelectCriterion   fill_criterion,
@@ -32,6 +34,8 @@ void         gimp_drawable_bucket_fill            (GimpDrawable         *drawabl
                                                    gdouble               y);
 GeglBuffer * gimp_drawable_get_bucket_fill_buffer (GimpDrawable         *drawable,
                                                    GeglBuffer           *line_art,
+                                                   gfloat               *distmap,
+                                                   gfloat               *thickmap,
                                                    GimpFillOptions      *options,
                                                    gboolean              fill_transparent,
                                                    GimpSelectCriterion   fill_criterion,
diff --git a/app/core/gimplineart.c b/app/core/gimplineart.c
index 640cb9f7b1..c8373eb87c 100644
--- a/app/core/gimplineart.c
+++ b/app/core/gimplineart.c
@@ -105,7 +105,8 @@ static GArray     * gimp_lineart_line_segment_until_hit      (const GeglBuffer
                                                               Pixel                   start,
                                                               GimpVector2             direction,
                                                               int                     size);
-static gfloat     * gimp_lineart_estimate_strokes_radii      (GeglBuffer             *mask);
+static gfloat     * gimp_lineart_estimate_strokes_radii      (GeglBuffer             *mask,
+                                                              gfloat                **lineart_distmap);
 
 /* Some callback-type functions. */
 
@@ -163,7 +164,7 @@ static void       gimp_edgelset_next8             (const GeglBuffer  *buffer,
 
 /**
  * gimp_lineart_close:
- * @line_art: the input #GeglBuffer.
+ * @buffer: the input #GeglBuffer.
  * @select_transparent: whether we binarize the alpha channel or the
  *                      luminosity.
  * @stroke_threshold: [0-1] threshold value for detecting stroke pixels
@@ -187,8 +188,10 @@ static void       gimp_edgelset_next8             (const GeglBuffer  *buffer,
  * @segments_max_length: the maximum length for creating segments
  *                       between end points. Unlike splines, segments
  *                       are straight lines.
+ * @lineart_distmap: a distance map of the line art pixels.
+ * @lineart_radii: a map of estimated radii of line art border pixels.
  *
- * Creates a binarized version of the strokes of @line_art, detected either
+ * Creates a binarized version of the strokes of @buffer, detected either
  * with luminosity (light means background) or alpha values depending on
  * @select_transparent. This binary version of the strokes will have closed
  * regions allowing adequate selection of "nearly closed regions".
@@ -201,24 +204,28 @@ static void       gimp_edgelset_next8             (const GeglBuffer  *buffer,
  * Fourey, David Tschumperlé, David Revoy.
  *
  * Returns: a new #GeglBuffer of format "Y u8" representing the
- *          binarized @line_art. A value of
+ *          binarized @line_art. If @lineart_radii and @lineart_distmap
+ *          are not #NULL, newly allocated float buffer are returned,
+ *          which can be used for overflowing created masks later.
  */
 GeglBuffer *
-gimp_lineart_close (GeglBuffer          *line_art,
-                    gboolean             select_transparent,
-                    gfloat               stroke_threshold,
-                    gint                 minimal_lineart_area,
-                    gint                 normal_estimate_mask_size,
-                    gfloat               end_point_rate,
-                    gint                 spline_max_length,
-                    gfloat               spline_max_angle,
-                    gint                 end_point_connectivity,
-                    gfloat               spline_roundness,
-                    gboolean             allow_self_intersections,
-                    gint                 created_regions_significant_area,
-                    gint                 created_regions_minimum_area,
-                    gboolean             small_segments_from_spline_sources,
-                    gint                 segments_max_length)
+gimp_lineart_close (GeglBuffer  *buffer,
+                    gboolean     select_transparent,
+                    gfloat       stroke_threshold,
+                    gint         minimal_lineart_area,
+                    gint         normal_estimate_mask_size,
+                    gfloat       end_point_rate,
+                    gint         spline_max_length,
+                    gfloat       spline_max_angle,
+                    gint         end_point_connectivity,
+                    gfloat       spline_roundness,
+                    gboolean     allow_self_intersections,
+                    gint         created_regions_significant_area,
+                    gint         created_regions_minimum_area,
+                    gboolean     small_segments_from_spline_sources,
+                    gint         segments_max_length,
+                    gfloat     **lineart_distmap,
+                    gfloat     **lineart_radii)
 {
   const Babl         *gray_format;
   gfloat             *normals;
@@ -236,8 +243,8 @@ gimp_lineart_close (GeglBuffer          *line_art,
   guchar              max_value = 0;
   gfloat              threshold;
   gfloat              clamped_threshold;
-  gint                width  = gegl_buffer_get_width (line_art);
-  gint                height = gegl_buffer_get_height (line_art);
+  gint                width  = gegl_buffer_get_width (buffer);
+  gint                height = gegl_buffer_get_height (buffer);
   gint                i;
 
   normals             = g_new0 (gfloat, width * height * 2);
@@ -252,9 +259,9 @@ gimp_lineart_close (GeglBuffer          *line_art,
     gray_format = babl_format ("Y' u8");
 
   /* Transform the line art from any format to gray. */
-  strokes = gegl_buffer_new (gegl_buffer_get_extent (line_art),
+  strokes = gegl_buffer_new (gegl_buffer_get_extent (buffer),
                              gray_format);
-  gegl_buffer_copy (line_art, NULL, GEGL_ABYSS_NONE, strokes, NULL);
+  gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, strokes, NULL);
   gegl_buffer_set_format (strokes, babl_format ("Y' u8"));
 
   if (! select_transparent)
@@ -306,7 +313,7 @@ gimp_lineart_close (GeglBuffer          *line_art,
                                            smoothed_curvatures,
                                            normal_estimate_mask_size);
 
-  radii = gimp_lineart_estimate_strokes_radii (strokes);
+  radii = gimp_lineart_estimate_strokes_radii (strokes, lineart_distmap);
   threshold = 1.0f - end_point_rate;
   clamped_threshold = MAX (0.25f, threshold);
   for (i = 0; i < width; i++)
@@ -321,7 +328,10 @@ gimp_lineart_close (GeglBuffer          *line_art,
             curvatures[i + j * width] = 0.0;
         }
     }
-  g_free (radii);
+  if (lineart_radii)
+    *lineart_radii = radii;
+  else
+    g_free (radii);
 
   keypoints = gimp_lineart_curvature_extremums (curvatures, smoothed_curvatures,
                                                 width, height);
@@ -1266,7 +1276,8 @@ gimp_lineart_line_segment_until_hit (const GeglBuffer *mask,
 }
 
 static gfloat *
-gimp_lineart_estimate_strokes_radii (GeglBuffer *mask)
+gimp_lineart_estimate_strokes_radii (GeglBuffer  *mask,
+                                     gfloat     **lineart_distmap)
 {
   GeglBufferIterator *gi;
   gfloat             *dist;
@@ -1411,7 +1422,10 @@ gimp_lineart_estimate_strokes_radii (GeglBuffer *mask)
           }
     }
 
-  g_free (dist);
+  if (lineart_distmap)
+    *lineart_distmap = dist;
+  else
+    g_free (dist);
   g_object_unref (distmap);
 
   return thickness;
diff --git a/app/core/gimplineart.h b/app/core/gimplineart.h
index 54ee9e546b..8fa43f78da 100644
--- a/app/core/gimplineart.h
+++ b/app/core/gimplineart.h
@@ -22,21 +22,23 @@
 #define __GIMP_LINEART__
 
 
-GeglBuffer * gimp_lineart_close (GeglBuffer          *line_art,
-                                 gboolean             select_transparent,
-                                 gfloat               stroke_threshold,
-                                 gint                 minimal_lineart_area,
-                                 gint                 normal_estimate_mask_size,
-                                 gfloat               end_point_rate,
-                                 gint                 spline_max_length,
-                                 gfloat               spline_max_angle,
-                                 gint                 end_point_connectivity,
-                                 gfloat               spline_roundness,
-                                 gboolean             allow_self_intersections,
-                                 gint                 created_regions_significant_area,
-                                 gint                 created_regions_minimum_area,
-                                 gboolean             small_segments_from_spline_sources,
-                                 gint                 segments_max_length);
+GeglBuffer * gimp_lineart_close (GeglBuffer  *buffer,
+                                 gboolean     select_transparent,
+                                 gfloat       stroke_threshold,
+                                 gint         minimal_lineart_area,
+                                 gint         normal_estimate_mask_size,
+                                 gfloat       end_point_rate,
+                                 gint         spline_max_length,
+                                 gfloat       spline_max_angle,
+                                 gint         end_point_connectivity,
+                                 gfloat       spline_roundness,
+                                 gboolean     allow_self_intersections,
+                                 gint         created_regions_significant_area,
+                                 gint         created_regions_minimum_area,
+                                 gboolean     small_segments_from_spline_sources,
+                                 gint         segments_max_length,
+                                 gfloat     **lineart_distmap,
+                                 gfloat     **lineart_radii);
 
 
 #endif /* __GIMP_LINEART__ */
diff --git a/app/core/gimppickable-contiguous-region.c b/app/core/gimppickable-contiguous-region.c
index 7e6265864f..da0165a391 100644
--- a/app/core/gimppickable-contiguous-region.c
+++ b/app/core/gimppickable-contiguous-region.c
@@ -45,6 +45,13 @@ typedef struct
   gfloat      stroke_threshold;
 } LineArtData;
 
+typedef struct
+{
+  gint   x;
+  gint   y;
+  gfloat dist;
+} BorderPixel;
+
 
 /*  local function prototypes  */
 
@@ -105,10 +112,20 @@ static void     find_contiguous_region    (GeglBuffer          *src_buffer,
                                            gint                 y,
                                            const gfloat        *col);
 
-static LineArtData * line_art_data_new    (GeglBuffer          *buffer,
-                                           gboolean             select_transparent,
-                                           gfloat               stroke_threshold);
-static void          line_art_data_free   (LineArtData         *data);
+static LineArtData   * line_art_data_new    (GeglBuffer          *buffer,
+                                             gboolean             select_transparent,
+                                             gfloat               stroke_threshold);
+static void            line_art_data_free   (LineArtData         *data);
+static GimpPickableLineArtAsyncResult *
+                       line_art_result_new  (GeglBuffer          *line_art,
+                                             gfloat              *distmap,
+                                             gfloat              *thickmap);
+static void            line_art_result_free (GimpPickableLineArtAsyncResult
+                                                                 *data);
+static void            line_art_queue_pixel (GQueue              *queue,
+                                             gint                 x,
+                                             gint                 y,
+                                             gfloat               dist);
 
 
 /*  public functions  */
@@ -118,6 +135,8 @@ gimp_pickable_contiguous_region_prepare_line_art_async_func (GimpAsync   *async,
                                                              LineArtData *data)
 {
   GeglBuffer *lineart;
+  gfloat     *distmap;
+  gfloat     *thickmap;
   gboolean    has_alpha;
   gboolean    select_transparent = FALSE;
 
@@ -200,23 +219,29 @@ gimp_pickable_contiguous_region_prepare_line_art_async_func (GimpAsync   *async,
                                 /*small_segments_from_spline_sources,*/
                                 TRUE,
                                 /*segments_max_length*/
-                                20);
+                                20,
+                                &distmap, &thickmap);
 
   GIMP_TIMER_END("close line-art");
 
-  gimp_async_finish_full (async, lineart, g_object_unref);
+  gimp_async_finish_full (async,
+                          line_art_result_new (lineart, distmap, thickmap),
+                          (GDestroyNotify) line_art_result_free);
 
   line_art_data_free (data);
 }
 
 GeglBuffer *
-gimp_pickable_contiguous_region_prepare_line_art (GimpPickable *pickable,
-                                                  gboolean      select_transparent,
-                                                  gfloat        stroke_threshold)
+gimp_pickable_contiguous_region_prepare_line_art (GimpPickable  *pickable,
+                                                  gboolean       select_transparent,
+                                                  gfloat         stroke_threshold,
+                                                  gfloat       **distmap,
+                                                  gfloat       **thickmap)
 {
-  GimpAsync   *async;
-  LineArtData *data;
-  GeglBuffer  *lineart;
+  GimpAsync                      *async;
+  LineArtData                    *data;
+  GimpPickableLineArtAsyncResult *result;
+  GeglBuffer                     *lineart;
 
   g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
 
@@ -229,7 +254,13 @@ gimp_pickable_contiguous_region_prepare_line_art (GimpPickable *pickable,
 
   gimp_pickable_contiguous_region_prepare_line_art_async_func (async, data);
 
-  lineart = g_object_ref (gimp_async_get_result (async));
+  result = gimp_async_get_result (async);
+
+  lineart   = g_object_ref (result->line_art);
+  *distmap  = result->distmap;
+  *thickmap = result->thickmap;
+  result->distmap  = NULL;
+  result->thickmap = NULL;
 
   g_object_unref (async);
 
@@ -271,6 +302,8 @@ gimp_pickable_contiguous_region_prepare_line_art_async (GimpPickable *pickable,
 GeglBuffer *
 gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
                                          GeglBuffer          *line_art,
+                                         gfloat              *distmap,
+                                         gfloat              *thickmap,
                                          gboolean             antialias,
                                          gfloat               threshold,
                                          gboolean             select_transparent,
@@ -287,7 +320,6 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
   gint           n_components;
   gboolean       has_alpha;
   gfloat         start_col[MAX_CHANNELS];
-  gfloat         flag           = 2.0;
   gboolean       smart_line_art = FALSE;
   gboolean       free_line_art  = FALSE;
 
@@ -295,6 +327,9 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
 
   if (select_criterion == GIMP_SELECT_CRITERION_LINE_ART)
     {
+      g_return_val_if_fail ((line_art && distmap && thickmap) ||
+                            (! line_art && ! distmap && ! thickmap),
+                            NULL);
       if (line_art == NULL)
         {
           /* It is much better experience to pre-compute the line art,
@@ -302,7 +337,8 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
            * selecting/filling through a PDB call).
            */
           line_art      = gimp_pickable_contiguous_region_prepare_line_art (pickable, select_transparent,
-                                                                            stroke_threshold);
+                                                                            stroke_threshold,
+                                                                            &distmap, &thickmap);
           free_line_art = TRUE;
         }
 
@@ -381,59 +417,198 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
       /* The last step of the line art algorithm is to make sure that
        * selections does not leave "holes" between its borders and the
        * line arts, while not stepping over as well.
-       * To achieve this, I label differently the selection and the rest
-       * and leave the stroke pixels unlabelled, then I let these
-       * unknown pixels be labelled by flooding through watershed.
+       * I used to run the "gegl:watershed-transform" operation to flood
+       * the stroke pixels, but for such simple need, this simple code
+       * is so much faster while producing better results.
        */
-      GeglBufferIterator *gi;
-      GeglNode           *graph;
-      GeglNode           *input;
-      GeglNode           *op;
+      gfloat *mask;
+      GQueue *queue  = g_queue_new ();
+      gint    width  = gegl_buffer_get_width (line_art);
+      gint    height = gegl_buffer_get_height (line_art);
+      gint    nx, ny;
 
       GIMP_TIMER_START();
 
-      /* Flag the unselected line art pixels. */
-      gi = gegl_buffer_iterator_new (src_buffer, NULL, 0, NULL,
-                                     GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
-      gegl_buffer_iterator_add (gi, mask_buffer, NULL, 0,
-                                babl_format ("Y float"),
-                                GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE);
-
-      while (gegl_buffer_iterator_next (gi))
+      mask = g_new (gfloat, width * height);
+      gegl_buffer_get (mask_buffer, NULL, 1.0, NULL,
+                       mask, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+      for (y = 0; y < height; y++)
+        for (x = 0; x < width; x++)
+          {
+            gfloat thickness = thickmap[x + y * width];
+
+            if (thickness > 0.0)
+              {
+                if (x > 0)
+                  {
+                    nx = x - 1;
+                    if (y > 0)
+                      {
+                        ny = y - 1;
+                        if (mask[nx + ny * width] != 0.0)
+                          {
+                            line_art_queue_pixel (queue, x, y, thickness);
+                            continue;
+                          }
+                      }
+                    ny = y;
+                    if (mask[nx + ny * width] != 0.0)
+                      {
+                        line_art_queue_pixel (queue, x, y, thickness);
+                        continue;
+                      }
+                    if (y < height - 1)
+                      {
+                        ny = y + 1;
+                        if (mask[nx + ny * width] != 0.0)
+                          {
+                            line_art_queue_pixel (queue, x, y, thickness);
+                            continue;
+                          }
+                      }
+                  }
+                if (x < width - 1)
+                  {
+                    nx = x + 1;
+                    if (y > 0)
+                      {
+                        ny = y - 1;
+                        if (mask[nx + ny * width] != 0.0)
+                          {
+                            line_art_queue_pixel (queue, x, y, thickness);
+                            continue;
+                          }
+                      }
+                    ny = y;
+                    if (mask[nx + ny * width] != 0.0)
+                      {
+                        line_art_queue_pixel (queue, x, y, thickness);
+                        continue;
+                      }
+                    if (y < height - 1)
+                      {
+                        ny = y + 1;
+                        if (mask[nx + ny * width] != 0.0)
+                          {
+                            line_art_queue_pixel (queue, x, y, thickness);
+                            continue;
+                          }
+                      }
+                  }
+                nx = x;
+                if (y > 0)
+                  {
+                    ny = y - 1;
+                    if (mask[nx + ny * width] != 0.0)
+                      {
+                        line_art_queue_pixel (queue, x, y, thickness);
+                        continue;
+                      }
+                  }
+                if (y < height - 1)
+                  {
+                    ny = y + 1;
+                    if (mask[nx + ny * width] != 0.0)
+                      {
+                        line_art_queue_pixel (queue, x, y, thickness);
+                        continue;
+                      }
+                  }
+              }
+          }
+
+      while (! g_queue_is_empty (queue))
         {
-          guchar *lineart = (guchar*) gi->items[0].data;
-          gfloat *mask    = (gfloat*) gi->items[1].data;
-          gint    k;
+          BorderPixel *c = g_queue_pop_head (queue);
 
-          for (k = 0; k < gi->length; k++)
+          if (mask[c->x + c->y * width] != 1.0)
             {
-              if (*lineart && ! *mask)
-                *mask = flag;
-              lineart++;
-              mask++;
+              mask[c->x + c->y * width] = 1.0;
+              if (c->x > 0)
+                {
+                  nx = c->x - 1;
+                  if (c->y > 0)
+                    {
+                      ny = c->y - 1;
+                      if (mask[nx + ny * width] == 0.0 &&
+                          distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
+                          distmap[nx + ny * width] < c->dist)
+                        line_art_queue_pixel (queue, nx, ny, c->dist);
+                    }
+                  ny = c->y;
+                  if (mask[nx + ny * width] == 0.0 &&
+                      distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
+                      distmap[nx + ny * width] < c->dist)
+                    line_art_queue_pixel (queue, nx, ny, c->dist);
+                  if (c->y < height - 1)
+                    {
+                      ny = c->y - 1;
+                      if (mask[nx + ny * width] == 0.0 &&
+                          distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
+                          distmap[nx + ny * width] < c->dist)
+                        line_art_queue_pixel (queue, nx, ny, c->dist);
+                    }
+                }
+              if (c->x < width - 1)
+                {
+                  nx = c->x + 1;
+                  if (c->y > 0)
+                    {
+                      ny = c->y - 1;
+                      if (mask[nx + ny * width] == 0.0 &&
+                          distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
+                          distmap[nx + ny * width] < c->dist)
+                        line_art_queue_pixel (queue, nx, ny, c->dist);
+                    }
+                  ny = c->y;
+                  if (mask[nx + ny * width] == 0.0 &&
+                      distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
+                      distmap[nx + ny * width] < c->dist)
+                    line_art_queue_pixel (queue, nx, ny, c->dist);
+                  if (c->y < height - 1)
+                    {
+                      ny = c->y - 1;
+                      if (mask[nx + ny * width] == 0.0 &&
+                          distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
+                          distmap[nx + ny * width] < c->dist)
+                        line_art_queue_pixel (queue, nx, ny, c->dist);
+                    }
+                }
+              nx = c->x;
+              if (c->y > 0)
+                {
+                  ny = c->y - 1;
+                  if (mask[nx + ny * width] == 0.0 &&
+                      distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
+                      distmap[nx + ny * width] < c->dist)
+                    line_art_queue_pixel (queue, nx, ny, c->dist);
+                }
+              if (c->y < height - 1)
+                {
+                  ny = c->y + 1;
+                  if (mask[nx + ny * width] == 0.0 &&
+                      distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
+                      distmap[nx + ny * width] < c->dist)
+                    line_art_queue_pixel (queue, nx, ny, c->dist);
+                }
             }
+          g_free (c);
         }
-
-      /* Watershed the line art. */
-      graph = gegl_node_new ();
-      input = gegl_node_new_child (graph,
-                                   "operation", "gegl:buffer-source",
-                                   "buffer", mask_buffer,
-                                   NULL);
-      op  = gegl_node_new_child (graph,
-                                 "operation", "gegl:watershed-transform",
-                                 "flag-component", 0,
-                                 "flag", &flag,
-                                 NULL);
-      gegl_node_connect_to (input, "output",
-                            op, "input");
-      gegl_node_blit_buffer (op, mask_buffer, NULL, 0, GEGL_ABYSS_NONE);
-      g_object_unref (graph);
+      g_queue_free (queue);
+      gegl_buffer_set (mask_buffer, gegl_buffer_get_extent (mask_buffer),
+                       0, NULL, mask, GEGL_AUTO_ROWSTRIDE);
+      g_free (mask);
 
       GIMP_TIMER_END("watershed line art");
+
+      if (free_line_art)
+        {
+          g_object_unref (src_buffer);
+          g_free (distmap);
+          g_free (thickmap);
+        }
     }
-  if (free_line_art)
-    g_object_unref (src_buffer);
 
   return mask_buffer;
 }
@@ -999,3 +1174,43 @@ line_art_data_free (LineArtData *data)
 
   g_slice_free (LineArtData, data);
 }
+
+static GimpPickableLineArtAsyncResult *
+line_art_result_new (GeglBuffer *line_art,
+                     gfloat     *distmap,
+                     gfloat     *thickmap)
+{
+  GimpPickableLineArtAsyncResult *data;
+
+  data = g_slice_new (GimpPickableLineArtAsyncResult);
+  data->line_art = line_art;
+  data->distmap  = distmap;
+  data->thickmap = thickmap;
+
+  return data;
+}
+
+static void
+line_art_result_free (GimpPickableLineArtAsyncResult *data)
+{
+  g_object_unref (data->line_art);
+  g_clear_pointer (&data->distmap, g_free);
+  g_clear_pointer (&data->thickmap, g_free);
+
+  g_slice_free (GimpPickableLineArtAsyncResult, data);
+}
+
+static void
+line_art_queue_pixel (GQueue *queue,
+                      gint    x,
+                      gint    y,
+                      gfloat  dist)
+{
+  BorderPixel *p = g_new (BorderPixel, 1);
+
+  p->x = x;
+  p->y = y;
+  p->dist = dist;
+
+  g_queue_push_head (queue, p);
+}
diff --git a/app/core/gimppickable-contiguous-region.h b/app/core/gimppickable-contiguous-region.h
index 95543a59e5..8c2f61e82d 100644
--- a/app/core/gimppickable-contiguous-region.h
+++ b/app/core/gimppickable-contiguous-region.h
@@ -18,10 +18,18 @@
 #ifndef __GIMP_PICKABLE_CONTIGUOUS_REGION_H__
 #define __GIMP_PICKABLE_CONTIGUOUS_REGION_H__
 
+typedef struct
+{
+  GeglBuffer *line_art;
+  gfloat     *distmap;
+  gfloat     *thickmap;
+} GimpPickableLineArtAsyncResult;
 
 GeglBuffer * gimp_pickable_contiguous_region_prepare_line_art       (GimpPickable        *pickable,
                                                                      gboolean             select_transparent,
-                                                                     gfloat               stroke_threshold);
+                                                                     gfloat               stroke_threshold,
+                                                                     gfloat             **distmap,
+                                                                     gfloat             **thickmap);
 GimpAsync  * gimp_pickable_contiguous_region_prepare_line_art_async (GimpPickable        *pickable,
                                                                      gboolean             select_transparent,
                                                                      gfloat               stroke_threshold,
@@ -29,6 +37,8 @@ GimpAsync  * gimp_pickable_contiguous_region_prepare_line_art_async (GimpPickabl
 
 GeglBuffer * gimp_pickable_contiguous_region_by_seed                (GimpPickable        *pickable,
                                                                      GeglBuffer          *line_art,
+                                                                     gfloat              *distmap,
+                                                                     gfloat              *thickmap,
                                                                      gboolean             antialias,
                                                                      gfloat               threshold,
                                                                      gboolean             select_transparent,
diff --git a/app/pdb/drawable-edit-cmds.c b/app/pdb/drawable-edit-cmds.c
index 3c5e307bd7..5a88a1deb4 100644
--- a/app/pdb/drawable-edit-cmds.c
+++ b/app/pdb/drawable-edit-cmds.c
@@ -165,7 +165,7 @@ drawable_edit_bucket_fill_invoker (GimpProcedure         *procedure,
           if (gimp_fill_options_set_by_fill_type (options, context,
                                                   fill_type, error))
             {
-              gimp_drawable_bucket_fill (drawable, NULL, options,
+              gimp_drawable_bucket_fill (drawable, NULL, NULL, NULL, options,
                                          GIMP_PDB_CONTEXT (context)->sample_transparent,
                                          GIMP_PDB_CONTEXT (context)->sample_criterion,
                                          GIMP_PDB_CONTEXT (context)->sample_threshold,
diff --git a/app/tools/gimpbucketfilltool.c b/app/tools/gimpbucketfilltool.c
index eeab1b7473..8aaafaca08 100644
--- a/app/tools/gimpbucketfilltool.c
+++ b/app/tools/gimpbucketfilltool.c
@@ -62,6 +62,8 @@ struct _GimpBucketFillToolPrivate
 {
   GimpAsync          *async;
   GeglBuffer         *line_art;
+  gfloat             *distmap;
+  gfloat             *thickmap;
   GWeakRef            cached_image;
   GWeakRef            cached_drawable;
 
@@ -244,6 +246,8 @@ gimp_bucket_fill_tool_finalize (GObject *object)
     }
 
   g_clear_object (&tool->priv->line_art);
+  g_clear_pointer (&tool->priv->distmap, g_free);
+  g_clear_pointer (&tool->priv->thickmap, g_free);
 
   if (image)
     {
@@ -366,6 +370,8 @@ gimp_bucket_fill_tool_preview (GimpBucketFillTool *tool,
     {
       GeglBuffer *fill     = NULL;
       GeglBuffer *line_art = NULL;
+      gfloat     *distmap  = NULL;
+      gfloat     *thickmap = NULL;
       gdouble     x        = coords->x;
       gdouble     y        = coords->y;
 
@@ -380,10 +386,14 @@ gimp_bucket_fill_tool_preview (GimpBucketFillTool *tool,
         }
 
       if (options->fill_criterion == GIMP_SELECT_CRITERION_LINE_ART)
-        line_art = g_object_ref (tool->priv->line_art);
+        {
+          line_art = g_object_ref (tool->priv->line_art);
+          distmap  = tool->priv->distmap;
+          thickmap = tool->priv->thickmap;
+        }
 
       fill = gimp_drawable_get_bucket_fill_buffer (drawable,
-                                                   line_art,
+                                                   line_art, distmap, thickmap,
                                                    fill_options,
                                                    options->fill_transparent,
                                                    options->fill_criterion,
@@ -699,7 +709,17 @@ gimp_bucket_fill_compute_line_art_cb (GimpAsync          *async,
     return;
 
   if (gimp_async_is_finished (async))
-    tool->priv->line_art = g_object_ref (gimp_async_get_result (async));
+    {
+      GimpPickableLineArtAsyncResult *result;
+
+      result = gimp_async_get_result (async);
+
+      tool->priv->line_art = g_object_ref (result->line_art);
+      tool->priv->distmap  = result->distmap;
+      tool->priv->thickmap = result->thickmap;
+      result->distmap  = NULL;
+      result->thickmap = NULL;
+    }
 
   g_clear_object (&tool->priv->async);
 }
@@ -722,6 +742,9 @@ gimp_bucket_fill_compute_line_art (GimpBucketFillTool *tool)
     }
 
   g_clear_object (&tool->priv->line_art);
+  g_clear_pointer (&tool->priv->distmap, g_free);
+  g_clear_pointer (&tool->priv->thickmap, g_free);
+
   if (options->fill_criterion == GIMP_SELECT_CRITERION_LINE_ART)
     {
       GimpPickable *pickable = NULL;
@@ -825,6 +848,8 @@ gimp_bucket_fill_tool_image_changed (GimpContext        *context,
       GimpImage *prev_drawable = g_weak_ref_get (&tool->priv->cached_drawable);
 
       g_clear_object (&tool->priv->line_art);
+      g_clear_pointer (&tool->priv->distmap, g_free);
+      g_clear_pointer (&tool->priv->thickmap, g_free);
 
       if (prev_image)
         {
diff --git a/app/tools/gimpfuzzyselecttool.c b/app/tools/gimpfuzzyselecttool.c
index 2f43b406c5..e28dc71f47 100644
--- a/app/tools/gimpfuzzyselecttool.c
+++ b/app/tools/gimpfuzzyselecttool.c
@@ -122,7 +122,7 @@ gimp_fuzzy_select_tool_get_mask (GimpRegionSelectTool *region_select,
       pickable = GIMP_PICKABLE (image);
     }
 
-  return gimp_pickable_contiguous_region_by_seed (pickable, NULL,
+  return gimp_pickable_contiguous_region_by_seed (pickable, NULL, NULL, NULL,
                                                   sel_options->antialias,
                                                   options->threshold / 255.0,
                                                   options->select_transparent,
diff --git a/pdb/groups/drawable_edit.pdb b/pdb/groups/drawable_edit.pdb
index 9687d9989f..193a735ec0 100644
--- a/pdb/groups/drawable_edit.pdb
+++ b/pdb/groups/drawable_edit.pdb
@@ -169,7 +169,7 @@ HELP
       if (gimp_fill_options_set_by_fill_type (options, context,
                                               fill_type, error))
         {
-          gimp_drawable_bucket_fill (drawable, NULL, options,
+          gimp_drawable_bucket_fill (drawable, NULL, NULL, NULL, options,
                                      GIMP_PDB_CONTEXT (context)->sample_transparent,
                                      GIMP_PDB_CONTEXT (context)->sample_criterion,
                                      GIMP_PDB_CONTEXT (context)->sample_threshold,



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]