[gimp] app: add some new feature to close line arts manually.



commit e7668c03bdb00c57b25dfb29f3b3e338eb2c27f7
Author: Jehan <jehan girinstud io>
Date:   Tue Feb 8 21:01:11 2022 +0100

    app: add some new feature to close line arts manually.
    
    The line art algorithm is useful but not always accurate enough and
    sometimes it can even be counter-productive to fast painters.
    
    A technique of advanced painters which Aryeom uses and teaches when she
    wants to close an area without actually closing the line art (i.e. the
    non-closed line is a stylistic choice) is to close with a brush the area
    on the color layer. It has also a great advantage over the line art
    "smart" closing algorithm: you control the brush style and the exact
    shape of the closure (therefore something you'd usually have to redo
    with a closure made by an algorithm as you would likely not find it
    pretty enough).
    This new feature takes this technique into account. Basically rather
    than relying on the closure algorithm, you would close yourself and the
    tool is able to recognize closure pixels by the color proximity with the
    fill color.
    
    Final point is that this additional step is made after line art
    computation i.e. in particular the target drawable is not added to the
    sources for line art logics. This allows to stay fast otherwise the line
    art would have to recompute itself after each fill.
    
    This also shows why the previous commit of moving the line art object
    was necessary, because a painter would likely want to move regularly
    from bucket fill to a brush tool to create area closures and we want to
    avoid recomputation every time.

 app/core/gimpdrawable-bucket-fill.c        | 42 ++++++++++++++++
 app/core/gimpdrawable-bucket-fill.h        |  2 +
 app/core/gimppickable-contiguous-region.cc | 80 +++++++++++++++++++++++++++---
 app/core/gimppickable-contiguous-region.h  |  5 ++
 app/tools/gimpbucketfilloptions.c          | 49 +++++++++++++++++-
 app/tools/gimpbucketfilloptions.h          |  3 ++
 app/tools/gimpbucketfilltool.c             |  2 +
 7 files changed, 176 insertions(+), 7 deletions(-)
---
diff --git a/app/core/gimpdrawable-bucket-fill.c b/app/core/gimpdrawable-bucket-fill.c
index 3c45e3ad28..319887cc82 100644
--- a/app/core/gimpdrawable-bucket-fill.c
+++ b/app/core/gimpdrawable-bucket-fill.c
@@ -39,6 +39,7 @@
 #include "gimpdrawable-bucket-fill.h"
 #include "gimpfilloptions.h"
 #include "gimpimage.h"
+#include "gimplineart.h"
 #include "gimppickable.h"
 #include "gimppickable-contiguous-region.h"
 
@@ -315,6 +316,9 @@ gimp_drawable_get_bucket_fill_buffer (GimpDrawable         *drawable,
  * @line_art: the #GimpLineArt computed as fill source.
  * @options: the #GimpFillOptions.
  * @sample_merged:
+ * @fill_color_as_line_art: do we add pixels in @drawable filled with
+ *                          fill color to the line art?
+ * @fill_color_threshold: threshold value to determine fill color.
  * @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
@@ -331,6 +335,12 @@ gimp_drawable_get_bucket_fill_buffer (GimpDrawable         *drawable,
  * returned. This fill mask can later be reused in successive calls to
  * gimp_drawable_get_bucket_fill_buffer() for interactive filling.
  *
+ * The @fill_color_as_line_art option is a special feature where we
+ * consider pixels in @drawable already in the fill color as part of the
+ * line art. This is a post-process, i.e. that this is not taken into
+ * account while @line_art is computed, making this a fast addition
+ * processing allowing to close some area manually.
+ *
  * Returns: a fill buffer which can be directly applied to @drawable, or
  *          used in a drawable filter as preview.
  */
@@ -339,6 +349,8 @@ gimp_drawable_get_line_art_fill_buffer (GimpDrawable     *drawable,
                                         GimpLineArt      *line_art,
                                         GimpFillOptions  *options,
                                         gboolean          sample_merged,
+                                        gboolean          fill_color_as_line_art,
+                                        gdouble           fill_color_threshold,
                                         gdouble           seed_x,
                                         gdouble           seed_y,
                                         GeglBuffer      **mask_buffer,
@@ -350,6 +362,10 @@ gimp_drawable_get_line_art_fill_buffer (GimpDrawable     *drawable,
   GimpImage  *image;
   GeglBuffer *buffer;
   GeglBuffer *new_mask;
+  GeglBuffer *fill_buffer   = NULL;
+  GimpRGB     fill_color;
+  gint        fill_offset_x = 0;
+  gint        fill_offset_y = 0;
   gint        x, y, width, height;
   gint        mask_offset_x = 0;
   gint        mask_offset_y = 0;
@@ -384,7 +400,33 @@ gimp_drawable_get_line_art_fill_buffer (GimpDrawable     *drawable,
   /*  Do a seed bucket fill...To do this, calculate a new
    *  contiguous region.
    */
+  if (fill_color_as_line_art)
+    {
+      GimpPickable *pickable = gimp_line_art_get_input (line_art);
+
+      /* This cannot be a pattern fill. */
+      g_return_val_if_fail (gimp_fill_options_get_style (options) == GIMP_FILL_STYLE_SOLID,
+                            NULL);
+      /* Meaningful only in above/below layer cases. */
+      g_return_val_if_fail (GIMP_IS_DRAWABLE (pickable), NULL);
+
+      /* Fill options foreground color is the expected color (can be
+       * actual fg or bg in the user context).
+       */
+      gimp_context_get_foreground (GIMP_CONTEXT (options), &fill_color);
+
+      fill_buffer   = gimp_drawable_get_buffer (drawable);
+      fill_offset_x = gimp_item_get_offset_x (GIMP_ITEM (drawable)) -
+                      gimp_item_get_offset_x (GIMP_ITEM (pickable));
+      fill_offset_y = gimp_item_get_offset_y (GIMP_ITEM (drawable)) -
+                      gimp_item_get_offset_y (GIMP_ITEM (pickable));
+    }
   new_mask = gimp_pickable_contiguous_region_by_line_art (NULL, line_art,
+                                                          fill_buffer,
+                                                          &fill_color,
+                                                          fill_color_threshold,
+                                                          fill_offset_x,
+                                                          fill_offset_y,
                                                           (gint) seed_x,
                                                           (gint) seed_y);
   if (mask_buffer && *mask_buffer)
diff --git a/app/core/gimpdrawable-bucket-fill.h b/app/core/gimpdrawable-bucket-fill.h
index 148de97b5f..0339d6c70f 100644
--- a/app/core/gimpdrawable-bucket-fill.h
+++ b/app/core/gimpdrawable-bucket-fill.h
@@ -48,6 +48,8 @@ GeglBuffer * gimp_drawable_get_line_art_fill_buffer (GimpDrawable         *drawa
                                                      GimpLineArt          *line_art,
                                                      GimpFillOptions      *options,
                                                      gboolean              sample_merged,
+                                                     gboolean              fill_color_as_line_art,
+                                                     gdouble               fill_color_threshold,
                                                      gdouble               seed_x,
                                                      gdouble               seed_y,
                                                      GeglBuffer          **mask_buffer,
diff --git a/app/core/gimppickable-contiguous-region.cc b/app/core/gimppickable-contiguous-region.cc
index 37fbbca5e8..7f9984e240 100644
--- a/app/core/gimppickable-contiguous-region.cc
+++ b/app/core/gimppickable-contiguous-region.cc
@@ -32,6 +32,7 @@ extern "C"
 #include "core-types.h"
 
 #include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-utils.h"
 
 #include "gimp-parallel.h"
 #include "gimp-utils.h" /* GIMP_TIMER */
@@ -287,18 +288,24 @@ gimp_pickable_contiguous_region_by_color (GimpPickable        *pickable,
 }
 
 GeglBuffer *
-gimp_pickable_contiguous_region_by_line_art (GimpPickable *pickable,
-                                             GimpLineArt  *line_art,
-                                             gint          x,
-                                             gint          y)
+gimp_pickable_contiguous_region_by_line_art (GimpPickable  *pickable,
+                                             GimpLineArt   *line_art,
+                                             GeglBuffer    *fill_buffer,
+                                             const GimpRGB *fill_color,
+                                             gfloat         fill_threshold,
+                                             gint           fill_offset_x,
+                                             gint           fill_offset_y,
+                                             gint           x,
+                                             gint           y)
 {
   GeglBuffer    *src_buffer;
   GeglBuffer    *mask_buffer;
   const Babl    *format  = babl_format ("Y float");
   gfloat        *distmap = NULL;
   GeglRectangle  extent;
-  gboolean       free_line_art = FALSE;
-  gboolean       filled        = FALSE;
+  gboolean       free_line_art   = FALSE;
+  gboolean       free_src_buffer = FALSE;
+  gboolean       filled          = FALSE;
   guchar         start_col;
 
   g_return_val_if_fail (GIMP_IS_PICKABLE (pickable) || GIMP_IS_LINE_ART (line_art), NULL);
@@ -317,6 +324,65 @@ gimp_pickable_contiguous_region_by_line_art (GimpPickable *pickable,
   src_buffer = gimp_line_art_get (line_art, &distmap);
   g_return_val_if_fail (src_buffer && distmap, NULL);
 
+  if (fill_buffer != NULL)
+    {
+      GeglBufferIterator  *gi;
+      const Babl          *fill_format;
+      const GeglRectangle *fill_extent;
+      gfloat               fill_col[MAX_CHANNELS];
+      gboolean             has_alpha;
+      gint                 n_components;
+
+      fill_extent = gegl_buffer_get_extent (fill_buffer);
+      fill_format = choose_format (fill_buffer,
+                                   GIMP_SELECT_CRITERION_COMPOSITE,
+                                   &n_components, &has_alpha);
+      gimp_rgba_get_pixel (fill_color, fill_format, fill_col);
+
+      src_buffer = gimp_gegl_buffer_dup (src_buffer);
+      gi = gegl_buffer_iterator_new (src_buffer, NULL, 0, NULL,
+                                     GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 2);
+      gegl_buffer_iterator_add (gi, fill_buffer,
+                                GEGL_RECTANGLE (-fill_offset_x, -fill_offset_y,
+                                                fill_extent->width + fill_offset_x,
+                                                fill_extent->height + fill_offset_y),
+                                0, fill_format, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+      while (gegl_buffer_iterator_next (gi))
+        {
+          guchar       *data = (guchar*) gi->items[0].data;
+          const gfloat *fill = (const gfloat *) gi->items[1].data;
+          gint          k;
+
+          for (k = 0; k < gi->length; k++)
+            {
+              /* Only consider if the line art source hasn't filled yet,
+               * and also if this the fill target has full opacity.
+               */
+              if (! *data &&
+                  ( ! has_alpha ||
+                   fill[n_components - 1] == 1.0))
+                {
+                  gfloat diff;
+
+                  diff = pixel_difference (fill, fill_col,
+                                           FALSE,
+                                           fill_threshold,
+                                           n_components, has_alpha, FALSE,
+                                           GIMP_SELECT_CRITERION_COMPOSITE);
+
+                  /* Make the additional fill pixel a closure pixel. */
+                  if (diff == 1.0)
+                    *data = 2;
+                }
+
+              data++;
+              fill += n_components;
+            }
+        }
+
+      free_src_buffer = TRUE;
+    }
+
   gegl_buffer_sample (src_buffer, x, y, NULL, &start_col, NULL,
                       GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
 
@@ -616,6 +682,8 @@ gimp_pickable_contiguous_region_by_line_art (GimpPickable *pickable,
     }
   if (free_line_art)
     g_clear_object (&line_art);
+  if (free_src_buffer)
+    g_object_unref (src_buffer);
 
   return mask_buffer;
 }
diff --git a/app/core/gimppickable-contiguous-region.h b/app/core/gimppickable-contiguous-region.h
index 26cbe7476a..2b823075a6 100644
--- a/app/core/gimppickable-contiguous-region.h
+++ b/app/core/gimppickable-contiguous-region.h
@@ -37,6 +37,11 @@ GeglBuffer * gimp_pickable_contiguous_region_by_color               (GimpPickabl
 
 GeglBuffer * gimp_pickable_contiguous_region_by_line_art            (GimpPickable        *pickable,
                                                                      GimpLineArt         *line_art,
+                                                                     GeglBuffer          *fill_buffer,
+                                                                     const GimpRGB       *fill_color,
+                                                                     gfloat               fill_threshold,
+                                                                     gint                 fill_offset_x,
+                                                                     gint                 fill_offset_y,
                                                                      gint                 x,
                                                                      gint                 y);
 
diff --git a/app/tools/gimpbucketfilloptions.c b/app/tools/gimpbucketfilloptions.c
index 9c915e1b8b..d89380712f 100644
--- a/app/tools/gimpbucketfilloptions.c
+++ b/app/tools/gimpbucketfilloptions.c
@@ -60,7 +60,9 @@ enum
   PROP_LINE_ART_THRESHOLD,
   PROP_LINE_ART_MAX_GROW,
   PROP_LINE_ART_MAX_GAP_LENGTH,
-  PROP_FILL_CRITERION
+  PROP_FILL_CRITERION,
+  PROP_FILL_COLOR_AS_LINE_ART,
+  PROP_FILL_COLOR_AS_LINE_ART_THRESHOLD,
 };
 
 struct _GimpBucketFillOptionsPrivate
@@ -70,6 +72,7 @@ struct _GimpBucketFillOptionsPrivate
 
   GtkWidget *similar_color_frame;
   GtkWidget *line_art_frame;
+  GtkWidget *fill_as_line_art_frame;
 };
 
 static void   gimp_bucket_fill_options_config_iface_init (GimpConfigInterface *config_iface);
@@ -184,6 +187,20 @@ gimp_bucket_fill_options_class_init (GimpBucketFillOptionsClass *klass)
                          GIMP_LINE_ART_SOURCE_SAMPLE_MERGED,
                          GIMP_PARAM_STATIC_STRINGS);
 
+  GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_FILL_COLOR_AS_LINE_ART,
+                            "fill-color-as-line-art",
+                            _("Allow closing lines in selected layer"),
+                            _("Consider pixels of selected layer and filled with the fill color as line art 
closure"),
+                            FALSE,
+                            GIMP_PARAM_STATIC_STRINGS);
+
+  GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_FILL_COLOR_AS_LINE_ART_THRESHOLD,
+                           "fill-color-as-line-art-threshold",
+                           _("Threshold"),
+                           _("Maximum color difference"),
+                           0.0, 255.0, 15.0,
+                           GIMP_PARAM_STATIC_STRINGS);
+
   GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_LINE_ART_THRESHOLD,
                            "line-art-threshold",
                            _("Line art detection threshold"),
@@ -240,6 +257,7 @@ gimp_bucket_fill_options_set_property (GObject      *object,
     {
     case PROP_FILL_MODE:
       options->fill_mode = g_value_get_enum (value);
+      gimp_bucket_fill_options_update_area (options);
       break;
     case PROP_FILL_AREA:
       options->fill_area = g_value_get_enum (value);
@@ -268,6 +286,7 @@ gimp_bucket_fill_options_set_property (GObject      *object,
       break;
     case PROP_LINE_ART_SOURCE:
       options->line_art_source = g_value_get_enum (value);
+      gimp_bucket_fill_options_update_area (options);
       break;
     case PROP_LINE_ART_THRESHOLD:
       options->line_art_threshold = g_value_get_double (value);
@@ -281,6 +300,12 @@ gimp_bucket_fill_options_set_property (GObject      *object,
     case PROP_FILL_CRITERION:
       options->fill_criterion = g_value_get_enum (value);
       break;
+    case PROP_FILL_COLOR_AS_LINE_ART:
+      options->fill_as_line_art = g_value_get_boolean (value);
+      break;
+    case PROP_FILL_COLOR_AS_LINE_ART_THRESHOLD:
+      options->fill_as_line_art_threshold = g_value_get_double (value);
+      break;
 
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -340,6 +365,12 @@ gimp_bucket_fill_options_get_property (GObject    *object,
     case PROP_FILL_CRITERION:
       g_value_set_enum (value, options->fill_criterion);
       break;
+    case PROP_FILL_COLOR_AS_LINE_ART:
+      g_value_set_boolean (value, options->fill_as_line_art);
+      break;
+    case PROP_FILL_COLOR_AS_LINE_ART_THRESHOLD:
+      g_value_set_double (value, options->fill_as_line_art_threshold);
+      break;
 
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -375,6 +406,13 @@ gimp_bucket_fill_options_update_area (GimpBucketFillOptions *options)
     case GIMP_BUCKET_FILL_LINE_ART:
       gtk_widget_hide (options->priv->similar_color_frame);
       gtk_widget_show (options->priv->line_art_frame);
+      if ((options->fill_mode == GIMP_BUCKET_FILL_FG ||
+           options->fill_mode == GIMP_BUCKET_FILL_BG) &&
+          (options->line_art_source == GIMP_LINE_ART_SOURCE_LOWER_LAYER ||
+           options->line_art_source == GIMP_LINE_ART_SOURCE_UPPER_LAYER))
+        gtk_widget_show (options->priv->fill_as_line_art_frame);
+      else
+        gtk_widget_hide (options->priv->fill_as_line_art_frame);
       break;
     case GIMP_BUCKET_FILL_SIMILAR_COLORS:
       gtk_widget_show (options->priv->similar_color_frame);
@@ -496,6 +534,15 @@ gimp_bucket_fill_options_gui (GimpToolOptions *tool_options)
   gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (combo), _("Source"));
   gtk_box_pack_start (GTK_BOX (box2), combo, FALSE, FALSE, 0);
 
+  /*  Line Art: fill as line art  */
+  scale = gimp_prop_spin_scale_new (config, "fill-color-as-line-art-threshold", NULL,
+                                    1.0, 16.0, 1);
+
+  frame = gimp_prop_expanding_frame_new (config, "fill-color-as-line-art", NULL,
+                                         scale, NULL);
+  gtk_box_pack_start (GTK_BOX (box2), frame, FALSE, FALSE, 0);
+  options->priv->fill_as_line_art_frame = frame;
+
   /*  the fill transparent areas toggle  */
   widget = gimp_prop_check_button_new (config, "fill-transparent", NULL);
   gtk_box_pack_start (GTK_BOX (box2), widget, FALSE, FALSE, 0);
diff --git a/app/tools/gimpbucketfilloptions.h b/app/tools/gimpbucketfilloptions.h
index 8c001fb2d3..30bb594cbb 100644
--- a/app/tools/gimpbucketfilloptions.h
+++ b/app/tools/gimpbucketfilloptions.h
@@ -54,6 +54,9 @@ struct _GimpBucketFillOptions
   gint                          line_art_max_grow;
   gint                          line_art_max_gap_length;
 
+  gboolean                      fill_as_line_art;
+  gdouble                       fill_as_line_art_threshold;
+
   GimpSelectCriterion           fill_criterion;
 
   GimpBucketFillOptionsPrivate *priv;
diff --git a/app/tools/gimpbucketfilltool.c b/app/tools/gimpbucketfilltool.c
index 53a6b01b20..0f1916722a 100644
--- a/app/tools/gimpbucketfilltool.c
+++ b/app/tools/gimpbucketfilltool.c
@@ -455,6 +455,8 @@ gimp_bucket_fill_tool_preview (GimpBucketFillTool *tool,
                                                          fill_options,
                                                          options->line_art_source ==
                                                          GIMP_LINE_ART_SOURCE_SAMPLE_MERGED,
+                                                         options->fill_as_line_art,
+                                                         options->fill_as_line_art_threshold / 255.0,
                                                          x, y,
                                                          &tool->priv->fill_mask,
                                                          &x, &y, NULL, NULL);


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