[gimp/gimp-2-10] app: implement second step for line art selection/filling.



commit 239be8ecc84216c183a7e19a07cdcfb390d12e6e
Author: Jehan <jehan girinstud io>
Date:   Thu Oct 11 15:32:19 2018 +0200

    app: implement second step for line art selection/filling.
    
    When filling colors in line arts, you don't want to leave space between
    the strokes and the color, which usually happen with any of the current
    selection methods.
    A "KISS" trick is usually to grow your selection a few pixels before
    filling (adding an additional step in colorization process), which
    obviously does not handle all cases (depending on drawing style and
    stroke size, you may need to grow more or less) as it doesn't take into
    account actual stroke geometry.
    
    Instead, I label the selection and the "rest" differently and leave the
    pixel strokes unlabelled. Then I let these unlabelled pixels be flooded
    by the "gegl:watershed-transform" operation.
    Note that this second step is different from the second step from the
    GREYC research paper, as they use their own watershed algorithm taking
    color spots as sources to color the whole image at once. This is a
    different workflow from the one using bucket fill with a single color
    source.
    
    (cherry picked from commit 8502b4e7431761c487107ffd49022b2ccd3585ff)

 app/core/gimppickable-contiguous-region.c | 150 +++++++++++++++++++++++++++++-
 1 file changed, 146 insertions(+), 4 deletions(-)
---
diff --git a/app/core/gimppickable-contiguous-region.c b/app/core/gimppickable-contiguous-region.c
index ae6c5d14a5..4929648219 100644
--- a/app/core/gimppickable-contiguous-region.c
+++ b/app/core/gimppickable-contiguous-region.c
@@ -115,7 +115,7 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
   gint           n_components;
   gboolean       has_alpha;
   gfloat         start_col[MAX_CHANNELS];
-  gboolean       free_src_buffer = FALSE;
+  gboolean       smart_line_art = FALSE;
 
   g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
 
@@ -182,7 +182,7 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
                                        TRUE,
                                        /*segments_max_length*/
                                        20);
-      free_src_buffer    = TRUE;
+      smart_line_art     = TRUE;
       antialias          = FALSE;
       threshold          = 0.0;
       select_transparent = FALSE;
@@ -214,8 +214,150 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
 
       GIMP_TIMER_END("foo");
     }
-  if (free_src_buffer)
-    g_object_unref (src_buffer);
+
+  if (smart_line_art)
+    {
+      /* 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.
+       */
+      GeglBufferIterator *gi;
+      GeglBuffer         *labels;
+      GeglBuffer         *distmap;
+      GeglBuffer         *tmp;
+      GeglNode           *graph;
+      GeglNode           *input;
+      GeglNode           *aux;
+      GeglNode           *op;
+      GeglNode           *sink;
+
+      GIMP_TIMER_START();
+      labels = gegl_buffer_new (&extent, babl_format ("YA u32"));
+
+      gi = gegl_buffer_iterator_new (src_buffer, NULL, 0, NULL,
+                                     GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 3);
+      gegl_buffer_iterator_add (gi, mask_buffer, NULL, 0,
+                                babl_format ("Y float"),
+                                GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+      gegl_buffer_iterator_add (gi, labels, NULL, 0,
+                                babl_format ("YA u32"),
+                                GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+
+      while (gegl_buffer_iterator_next (gi))
+        {
+          guchar  *lineart = (guchar*) gi->items[0].data;
+          gfloat  *mask    = (gfloat*) gi->items[1].data;
+          guint32 *label   = (guint32*) gi->items[2].data;
+          gint    k;
+
+          for (k = 0; k < gi->length; k++)
+            {
+              if (*mask)
+                {
+                  label[0] = G_MAXUINT32;
+                  label[1] = 1;
+                }
+              else if (*lineart)
+                {
+                  label[0] = 0;
+                  label[1] = 0;
+                }
+              else
+                {
+                  label[0] = 0;
+                  label[1] = 1;
+                }
+              lineart++;
+              mask++;
+              label += 2;
+            }
+        }
+      g_object_unref (src_buffer);
+
+      /* Compute a distance map for the labels. */
+      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:distance-transform",
+                                 "metric", GEGL_DISTANCE_METRIC_EUCLIDEAN,
+                                 "normalize", FALSE,
+                                 NULL);
+      sink  = gegl_node_new_child (graph,
+                                   "operation", "gegl:buffer-sink",
+                                   "buffer", &distmap,
+                                   NULL);
+      gegl_node_connect_to (input, "output",
+                            op, "input");
+      gegl_node_connect_to (op, "output",
+                            sink, "input");
+      gegl_node_process (sink);
+      g_object_unref (graph);
+
+      /* gegl:distance-transform returns distances as float. I want them as
+       * unsigned ints without converting (i.e. not assuming pixel values).
+       * Let's just loop through the map).
+       */
+      tmp = gegl_buffer_new (gegl_buffer_get_extent (distmap),
+                             babl_format ("Y u8"));
+      gi = gegl_buffer_iterator_new (distmap, NULL, 0, NULL,
+                                     GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
+      gegl_buffer_iterator_add (gi, tmp, NULL, 0, NULL,
+                                GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+      while (gegl_buffer_iterator_next (gi))
+        {
+          float  *data = (float*) gi->items[0].data;
+          guint8 *out = (guint8*) gi->items[1].data;
+          gint    k;
+
+          for (k = 0; k < gi->length; k++)
+            {
+              *out = (guint8) G_MAXUINT32 - MIN (round (*data), G_MAXUINT8);
+              data++;
+              out++;
+            }
+        }
+      g_object_unref (distmap);
+      distmap = tmp;
+
+      /* Watershed the labels. */
+      graph = gegl_node_new ();
+      input = gegl_node_new_child (graph,
+                                   "operation", "gegl:buffer-source",
+                                   "buffer", labels,
+                                   NULL);
+      aux = gegl_node_new_child (graph,
+                                 "operation", "gegl:buffer-source",
+                                 "buffer", distmap,
+                                 NULL);
+      op  = gegl_node_new_child (graph,
+                                 "operation", "gegl:watershed-transform",
+                                 NULL);
+      sink  = gegl_node_new_child (graph,
+                                   "operation", "gegl:buffer-sink",
+                                   "buffer", &tmp,
+                                   NULL);
+      gegl_node_connect_to (input, "output",
+                            op, "input");
+      gegl_node_connect_to (aux, "output",
+                            op, "aux");
+      gegl_node_connect_to (op, "output",
+                            sink, "input");
+      gegl_node_process (sink);
+      g_object_unref (graph);
+      g_object_unref (distmap);
+
+      gegl_buffer_copy (tmp, NULL, GEGL_ABYSS_NONE, mask_buffer, NULL);
+      g_object_unref (tmp);
+      g_object_unref (labels);
+
+      GIMP_TIMER_END("watershed line art");
+    }
 
   return mask_buffer;
 }


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