[gimp/gimp-2-10] app: improve line art bucket fill by filling unsignificant areas.



commit 5796cc46e4f690afc72cfb914a7db33180b982e9
Author: Jehan <jehan girinstud io>
Date:   Thu Feb 7 17:44:35 2019 +0100

    app: improve line art bucket fill by filling unsignificant areas.
    
    The line art imaginary segments/splines are not added when they create
    too small zones, unless when these are just too small ("unsignificant").
    Why the original algorithm keeps such micro-zones is because there may
    be such zones created when several splines or segments are leaving from
    a same key point (and we don't necessarily won't to forbid this). Also
    we had cases when using very spiky brushes (for the line art) would
    create many zones, and such micro-zones would appear just too often
    (whereas with very smooth lines, they are much rarer, if not totally
    absent most of the time).
    Also it is to be noted that the original paper would call these
    "unsignificant" indeed, but these are definitely significant for the
    artists. Therefore having to "fix" the filling afterwards (with a brush
    for instance) kind of defeat the whole purpose of this tool.
    
    I already had code which would special-case (fill) 1-pixel zones in the
    end, but bigger micro zones could appear (up to 4 pixels in the current
    code, but this could change). Also I don't want to use the "Remove
    Holes" (gimp:flood) operation as I want to make sure I remove only
    micro-holes created by the line art closure code (not micro-holes from
    original line arts in particular).
    
    This code takes care of this issue by filling the micro-holes with
    imaginary line art pixels, which may later be potentially bucket filled
    when water-filling the line art.
    
    (cherry picked from commit 72092fbdbc76ed400e9866ffd5d601aea8352e75)

 app/core/gimpdrawable-bucket-fill.c |  56 +++-----------------
 app/core/gimplineart.c              | 103 ++++++++++++++++++++++++++++++------
 2 files changed, 93 insertions(+), 66 deletions(-)
---
diff --git a/app/core/gimpdrawable-bucket-fill.c b/app/core/gimpdrawable-bucket-fill.c
index d2519b5fb2..a5b3f62483 100644
--- a/app/core/gimpdrawable-bucket-fill.c
+++ b/app/core/gimpdrawable-bucket-fill.c
@@ -332,14 +332,13 @@ gimp_drawable_get_line_art_fill_buffer (GimpDrawable     *drawable,
                                         gint             *mask_width,
                                         gint             *mask_height)
 {
-  GeglBufferIterator *gi;
-  GimpImage    *image;
-  GeglBuffer   *buffer;
-  GeglBuffer   *new_mask;
-  gint          x, y, width, height;
-  gint          mask_offset_x = 0;
-  gint          mask_offset_y = 0;
-  gint          sel_x, sel_y, sel_width, sel_height;
+  GimpImage  *image;
+  GeglBuffer *buffer;
+  GeglBuffer *new_mask;
+  gint        x, y, width, height;
+  gint        mask_offset_x = 0;
+  gint        mask_offset_y = 0;
+  gint        sel_x, sel_y, sel_width, sel_height;
 
   g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
   g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
@@ -446,47 +445,6 @@ gimp_drawable_get_line_art_fill_buffer (GimpDrawable     *drawable,
       mask_offset_y = y;
     }
 
-  /* The smart colorization leaves some very irritating unselected
-   * pixels in some edge cases. Just flood any isolated pixel inside
-   * the final mask.
-   */
-  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++;
-          }
-    }
-
   buffer = gimp_fill_options_create_buffer (options, drawable,
                                             GEGL_RECTANGLE (0, 0,
                                                             width, height),
diff --git a/app/core/gimplineart.c b/app/core/gimplineart.c
index 8259bc2ff5..d1d2d83cc5 100644
--- a/app/core/gimplineart.c
+++ b/app/core/gimplineart.c
@@ -214,6 +214,7 @@ static gint            gimp_number_of_transitions               (GArray
                                                                  GeglBuffer             *buffer);
 static gboolean        gimp_lineart_curve_creates_region        (GeglBuffer             *mask,
                                                                  GArray                 *pixels,
+                                                                 GList                 **fill_pixels,
                                                                  int                     lower_size_limit,
                                                                  int                     upper_size_limit);
 static GArray        * gimp_lineart_line_segment_until_hit      (const GeglBuffer       *buffer,
@@ -221,6 +222,9 @@ static GArray        * gimp_lineart_line_segment_until_hit      (const GeglBuffe
                                                                  GimpVector2             direction,
                                                                  int                     size);
 static gfloat        * gimp_lineart_estimate_strokes_radii      (GeglBuffer             *mask);
+static void            gimp_line_art_simple_fill                (GeglBuffer             *buffer,
+                                                                 gint                    x,
+                                                                 gint                    y);
 
 /* Some callback-type functions. */
 
@@ -901,6 +905,8 @@ gimp_line_art_close (GeglBuffer  *buffer,
       gfloat     *smoothed_curvatures;
       gfloat      threshold;
       gfloat      clamped_threshold;
+      GList      *fill_pixels = NULL;
+      GList      *iter;
 
       normals             = g_new0 (gfloat, width * height * 2);
       curvatures          = g_new0 (gfloat, width * height);
@@ -984,6 +990,7 @@ gimp_line_art_close (GeglBuffer  *buffer,
 
                   if (transitions == 2 &&
                       ! gimp_lineart_curve_creates_region (closed, discrete_curve,
+                                                           &fill_pixels,
                                                            created_regions_significant_area,
                                                            created_regions_minimum_area - 1))
                     {
@@ -1041,7 +1048,7 @@ gimp_line_art_close (GeglBuffer  *buffer,
                                                                          segment_max_length);
 
                   if (segment->len &&
-                      ! gimp_lineart_curve_creates_region (closed, segment,
+                      ! gimp_lineart_curve_creates_region (closed, segment, &fill_pixels,
                                                            created_regions_significant_area,
                                                            created_regions_minimum_area - 1))
                     {
@@ -1067,6 +1074,21 @@ gimp_line_art_close (GeglBuffer  *buffer,
             }
         }
 
+      for (iter = fill_pixels; iter; iter = iter->next)
+        {
+          Pixel *p = iter->data;
+
+          /* XXX A best approach would be to generalize
+           * gimp_drawable_bucket_fill() to work on any buffer (the code
+           * is already mostly there) rather than reimplementing a naive
+           * bucket fill.
+           * This is mostly a quick'n dirty first implementation which I
+           * will improve later.
+           */
+          gimp_line_art_simple_fill (closed, (gint) p->x, (gint) p->y);
+        }
+
+      g_list_free_full (fill_pixels, g_free);
       g_free (normals);
       g_free (curvatures);
       g_free (smoothed_curvatures);
@@ -1767,11 +1789,13 @@ gimp_number_of_transitions (GArray     *pixels,
 static gboolean
 gimp_lineart_curve_creates_region (GeglBuffer *mask,
                                    GArray     *pixels,
+                                   GList     **fill_pixels,
                                    int         lower_size_limit,
                                    int         upper_size_limit)
 {
   const glong max_edgel_count = 2 * (upper_size_limit + 1);
   Pixel      *p = (Pixel*) pixels->data;
+  GList      *fp = NULL;
   gint        i;
 
   /* Mark pixels */
@@ -1817,35 +1841,54 @@ gimp_lineart_curve_creates_region (GeglBuffer *mask,
               e.direction = direction;
 
               count = gimp_edgel_track_mark (mask, e, max_edgel_count);
-              if ((count != -1) && (count <= max_edgel_count)              &&
-                  ((area = -1 * gimp_edgel_region_area (mask, e)) >= lower_size_limit) &&
-                  (area <= upper_size_limit))
+              if ((count != -1) && (count <= max_edgel_count))
                 {
-                  gint j;
+                  area = -1 * gimp_edgel_region_area (mask, e);
 
-                  /* Remove marks */
-                  for (j = 0; j < pixels->len; j++)
+                  if (area >= lower_size_limit && area <= upper_size_limit)
                     {
-                      Pixel p2 = g_array_index (pixels, Pixel, j);
+                      gint j;
 
-                      if (p2.x >= 0 && p2.x < gegl_buffer_get_width (mask) &&
-                          p2.y >= 0 && p2.y < gegl_buffer_get_height (mask))
+                      /* Remove marks */
+                      for (j = 0; j < pixels->len; j++)
                         {
-                          guchar val;
+                          Pixel p2 = g_array_index (pixels, Pixel, j);
 
-                          gegl_buffer_sample (mask, (gint) p2.x, (gint) p2.y, NULL, &val,
-                                              NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
-                          val &= 1;
-                          gegl_buffer_set (mask, GEGL_RECTANGLE ((gint) p2.x, (gint) p2.y, 1, 1), 0,
-                                           NULL, &val, GEGL_AUTO_ROWSTRIDE);
+                          if (p2.x >= 0 && p2.x < gegl_buffer_get_width (mask) &&
+                              p2.y >= 0 && p2.y < gegl_buffer_get_height (mask))
+                            {
+                              guchar val;
+
+                              gegl_buffer_sample (mask, (gint) p2.x, (gint) p2.y, NULL, &val,
+                                                  NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+                              val &= 1;
+                              gegl_buffer_set (mask, GEGL_RECTANGLE ((gint) p2.x, (gint) p2.y, 1, 1), 0,
+                                               NULL, &val, GEGL_AUTO_ROWSTRIDE);
+                            }
                         }
+                      g_list_free_full (fp, g_free);
+
+                      return TRUE;
+                    }
+                  else if (area < lower_size_limit)
+                    {
+                      Pixel *np = g_new (Pixel, 1);
+
+                      np->x = direction == XPlusDirection ? p.x + 1 : (direction == XMinusDirection ? p.x - 
1 : p.x);
+                      np->y = direction == YPlusDirection ? p.y + 1 : (direction == YMinusDirection ? p.y - 
1 : p.y);
+
+                      if (np->x >= 0 && np->x < gegl_buffer_get_width (mask) &&
+                          np->y >= 0 && np->y < gegl_buffer_get_height (mask))
+                        fp = g_list_prepend (fp, np);
+                      else
+                        g_free (np);
                     }
-                  return TRUE;
                 }
             }
         }
     }
 
+  *fill_pixels = g_list_concat (*fill_pixels, fp);
   /* Remove marks */
   for (i = 0; i < pixels->len; i++)
     {
@@ -2059,6 +2102,32 @@ gimp_lineart_estimate_strokes_radii (GeglBuffer *mask)
   return thickness;
 }
 
+static void
+gimp_line_art_simple_fill (GeglBuffer *buffer,
+                           gint        x,
+                           gint        y)
+{
+  guchar val;
+
+  if (x < 0 || x >= gegl_buffer_get_width (buffer) ||
+      y < 0 || y >= gegl_buffer_get_height (buffer))
+    return;
+
+  gegl_buffer_sample (buffer, x, y, NULL, &val,
+                      NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+
+  if (! val)
+    {
+      val = 1;
+      gegl_buffer_set (buffer, GEGL_RECTANGLE (x, y, 1, 1), 0,
+                       NULL, &val, GEGL_AUTO_ROWSTRIDE);
+      gimp_line_art_simple_fill (buffer, x + 1, y);
+      gimp_line_art_simple_fill (buffer, x - 1, y);
+      gimp_line_art_simple_fill (buffer, x, y + 1);
+      gimp_line_art_simple_fill (buffer, x, y - 1);
+    }
+}
+
 static guint
 visited_hash_fun (Pixel *key)
 {


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