[gimp] app: bucket fill tool with a "paint-style" interaction.



commit e1c4050617f4efb3a3bfa889f2b016596d1ce762
Author: Jehan <jehan girinstud io>
Date:   Sun Nov 4 16:22:56 2018 +0100

    app: bucket fill tool with a "paint-style" interaction.
    
    Rather than just having a click interaction, let's allow to "paint" with
    the bucket fill. This is very useful for the new "line art" colorization
    since it tends to over-segment the drawing. Therefore being able to
    stroke through the canvas (rather than click, up, move, click, etc.)
    makes the process much simpler. This is also faster since we don't have
    to recompute the line art while a filling is in-progress.
    Note that this new behavior is not only for the line art mode, but also
    any other fill criterion, for which it can also be useful.
    
    Last change of behavior as a side effect: it is possible to cancel the
    tool changes the usual GIMP way (for instance by right clicking when
    releasing the mouse button).

 app/core/gimpdrawable-bucket-fill.c       | 190 ++++++++++----
 app/core/gimpdrawable-bucket-fill.h       |  35 ++-
 app/core/gimppickable-contiguous-region.c |  18 +-
 app/tools/gimpbucketfilltool.c            | 421 ++++++++++++++++++++++++------
 4 files changed, 526 insertions(+), 138 deletions(-)
---
diff --git a/app/core/gimpdrawable-bucket-fill.c b/app/core/gimpdrawable-bucket-fill.c
index b5e6d7d484..7bb12c18c7 100644
--- a/app/core/gimpdrawable-bucket-fill.c
+++ b/app/core/gimpdrawable-bucket-fill.c
@@ -58,29 +58,130 @@ gimp_drawable_bucket_fill (GimpDrawable         *drawable,
                            gboolean              diagonal_neighbors,
                            gdouble               seed_x,
                            gdouble               seed_y)
+{
+  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);
+  buffer = gimp_drawable_get_bucket_fill_buffer (drawable, line_art, options,
+                                                 fill_transparent, fill_criterion,
+                                                 threshold, sample_merged,
+                                                 diagonal_neighbors,
+                                                 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,
+                                      GimpFillOptions      *options,
+                                      gboolean              fill_transparent,
+                                      GimpSelectCriterion   fill_criterion,
+                                      gdouble               threshold,
+                                      gboolean              sample_merged,
+                                      gboolean              diagonal_neighbors,
+                                      gdouble               seed_x,
+                                      gdouble               seed_y,
+                                      GeglBuffer          **mask_buffer,
+                                      gdouble              *mask_x,
+                                      gdouble              *mask_y,
+                                      gint                 *mask_width,
+                                      gint                 *mask_height)
 {
   GimpImage    *image;
   GimpPickable *pickable;
   GeglBuffer   *buffer;
-  GeglBuffer   *mask_buffer;
+  GeglBuffer   *new_mask;
   gboolean      antialias;
   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_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));
+  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);
 
   image = gimp_item_get_image (GIMP_ITEM (drawable));
 
   if (! gimp_item_mask_intersect (GIMP_ITEM (drawable),
                                   &sel_x, &sel_y, &sel_width, &sel_height))
-    return;
+    return NULL;
 
-  gimp_set_busy (image->gimp);
+  if (mask_buffer && *mask_buffer &&
+      (fill_criterion == GIMP_SELECT_CRITERION_LINE_ART ||
+       threshold      == 0.0))
+    {
+      gfloat pixel;
 
+      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);
   if (sample_merged)
     pickable = GIMP_PICKABLE (image);
   else
@@ -91,21 +192,29 @@ gimp_drawable_bucket_fill (GimpDrawable         *drawable,
   /*  Do a seed bucket fill...To do this, calculate a new
    *  contiguous region.
    */
-  mask_buffer = gimp_pickable_contiguous_region_by_seed (pickable,
-                                                         line_art,
-                                                         antialias,
-                                                         threshold,
-                                                         fill_transparent,
-                                                         fill_criterion,
-                                                         diagonal_neighbors,
-                                                         (gint) seed_x,
-                                                         (gint) seed_y);
-
-  gimp_gegl_mask_bounds (mask_buffer, &x, &y, &width, &height);
+  new_mask = gimp_pickable_contiguous_region_by_seed (pickable,
+                                                      line_art,
+                                                      antialias,
+                                                      threshold,
+                                                      fill_transparent,
+                                                      fill_criterion,
+                                                      diagonal_neighbors,
+                                                      (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);
   width  -= x;
   height -= y;
 
-  /*  If there is a selection, inersect the region bounds
+  /*  If there is a selection, intersect the region bounds
    *  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
@@ -127,13 +236,12 @@ gimp_drawable_bucket_fill (GimpDrawable         *drawable,
 
                                       &x, &y, &width, &height))
         {
+          if (! mask_buffer)
+            g_object_unref (new_mask);
           /*  The fill region and the selection are disjoint; bail.  */
-
-          g_object_unref (mask_buffer);
-
           gimp_unset_busy (image->gimp);
 
-          return;
+          return NULL;
         }
     }
 
@@ -172,28 +280,22 @@ gimp_drawable_bucket_fill (GimpDrawable         *drawable,
                                                             width, height),
                                             -x, -y);
 
-  gimp_gegl_apply_opacity (buffer, NULL, NULL, buffer,
-                           mask_buffer,
-                           -mask_offset_x,
-                           -mask_offset_y,
-                           1.0);
-  g_object_unref (mask_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, x, y);
-
-  g_object_unref (buffer);
-
-  gimp_drawable_update (drawable, x, y, width, height);
+  gimp_gegl_apply_opacity (buffer, NULL, NULL, buffer, new_mask,
+                           -mask_offset_x, -mask_offset_y, 1.0);
+
+  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);
 
   gimp_unset_busy (image->gimp);
+
+  return buffer;
 }
diff --git a/app/core/gimpdrawable-bucket-fill.h b/app/core/gimpdrawable-bucket-fill.h
index a614fb727d..f1d04c1ac6 100644
--- a/app/core/gimpdrawable-bucket-fill.h
+++ b/app/core/gimpdrawable-bucket-fill.h
@@ -19,16 +19,31 @@
 #define  __GIMP_DRAWABLE_BUCKET_FILL_H__
 
 
-void   gimp_drawable_bucket_fill (GimpDrawable        *drawable,
-                                  GeglBuffer          *line_art,
-                                  GimpFillOptions     *options,
-                                  gboolean             fill_transparent,
-                                  GimpSelectCriterion  fill_criterion,
-                                  gdouble              threshold,
-                                  gboolean             sample_merged,
-                                  gboolean             diagonal_neighbors,
-                                  gdouble              x,
-                                  gdouble              y);
+void         gimp_drawable_bucket_fill            (GimpDrawable         *drawable,
+                                                   GeglBuffer           *line_art,
+                                                   GimpFillOptions      *options,
+                                                   gboolean              fill_transparent,
+                                                   GimpSelectCriterion   fill_criterion,
+                                                   gdouble               threshold,
+                                                   gboolean              sample_merged,
+                                                   gboolean              diagonal_neighbors,
+                                                   gdouble               x,
+                                                   gdouble               y);
+GeglBuffer * gimp_drawable_get_bucket_fill_buffer (GimpDrawable         *drawable,
+                                                   GeglBuffer           *line_art,
+                                                   GimpFillOptions      *options,
+                                                   gboolean              fill_transparent,
+                                                   GimpSelectCriterion   fill_criterion,
+                                                   gdouble               threshold,
+                                                   gboolean              sample_merged,
+                                                   gboolean              diagonal_neighbors,
+                                                   gdouble               seed_x,
+                                                   gdouble               seed_y,
+                                                   GeglBuffer          **mask_buffer,
+                                                   gdouble              *mask_x,
+                                                   gdouble              *mask_y,
+                                                   gint                 *mask_width,
+                                                   gint                 *mask_height);
 
 
 #endif  /*  __GIMP_DRAWABLE_BUCKET_FILL_H__  */
diff --git a/app/core/gimppickable-contiguous-region.c b/app/core/gimppickable-contiguous-region.c
index ee71220295..cc9502923f 100644
--- a/app/core/gimppickable-contiguous-region.c
+++ b/app/core/gimppickable-contiguous-region.c
@@ -270,7 +270,18 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
 
   mask_buffer = gegl_buffer_new (&extent, babl_format ("Y float"));
 
-  if (x >= extent.x && x < (extent.x + extent.width) &&
+  if (smart_line_art && start_col[0])
+    {
+      /* As a special exception, if you fill over a line art pixel, only
+       * fill the pixel and exit
+       */
+      start_col[0] = 1.0;
+      gegl_buffer_set (mask_buffer, GEGL_RECTANGLE (x, y, 1, 1),
+                       0, babl_format ("Y float"), start_col,
+                       GEGL_AUTO_ROWSTRIDE);
+      smart_line_art = FALSE;
+    }
+  else if (x >= extent.x && x < (extent.x + extent.width) &&
       y >= extent.y && y < (extent.y + extent.height))
     {
       GIMP_TIMER_START();
@@ -364,10 +375,9 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable        *pickable,
       g_object_unref (priomap);
 
       GIMP_TIMER_END("watershed line art");
-
-      if (free_line_art)
-        g_object_unref (src_buffer);
     }
+  if (free_line_art)
+    g_object_unref (src_buffer);
 
   return mask_buffer;
 }
diff --git a/app/tools/gimpbucketfilltool.c b/app/tools/gimpbucketfilltool.c
index 670a7cbb8e..63ffea9fca 100644
--- a/app/tools/gimpbucketfilltool.c
+++ b/app/tools/gimpbucketfilltool.c
@@ -29,6 +29,7 @@
 #include "core/gimpcancelable.h"
 #include "core/gimpdrawable-bucket-fill.h"
 #include "core/gimpdrawable-edit.h"
+#include "core/gimpdrawablefilter.h"
 #include "core/gimperror.h"
 #include "core/gimpfilloptions.h"
 #include "core/gimpimage.h"
@@ -37,8 +38,14 @@
 #include "core/gimp-parallel.h"
 #include "core/gimppickable.h"
 #include "core/gimppickable-contiguous-region.h"
+#include "core/gimpprogress.h"
+#include "core/gimpprojection.h"
 #include "core/gimpwaitable.h"
 
+#include "gegl/gimp-gegl-nodes.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
 #include "widgets/gimphelp-ids.h"
 #include "widgets/gimpwidgets-utils.h"
 
@@ -53,35 +60,72 @@
 
 struct _GimpBucketFillToolPrivate
 {
-  GimpAsync  *async;
-  GeglBuffer *line_art;
-  GWeakRef    cached_image;
-  GWeakRef    cached_drawable;
+  GimpAsync          *async;
+  GeglBuffer         *line_art;
+  GWeakRef            cached_image;
+  GWeakRef            cached_drawable;
+
+  gboolean            fill_in_progress;
+  gboolean            compute_line_art_after_fill;
+  GeglBuffer         *fill_buffer;
+  GeglBuffer         *fill_mask;
+
+  /* For preview */
+  GeglNode           *graph;
+  GeglNode           *fill_node;
+  GeglNode           *offset_node;
+
+  GimpDrawableFilter *filter;
 };
 
 /*  local function prototypes  */
 
-static void gimp_bucket_fill_tool_constructed (GObject *object);
-static void gimp_bucket_fill_tool_finalize (GObject *object);
-
-static gboolean gimp_bucket_fill_tool_initialize   (GimpTool              *tool,
-                                                    GimpDisplay           *display,
-                                                    GError               **error);
-static void   gimp_bucket_fill_tool_button_release (GimpTool              *tool,
-                                                    const GimpCoords      *coords,
-                                                    guint32                time,
-                                                    GdkModifierType        state,
-                                                    GimpButtonReleaseType  release_type,
-                                                    GimpDisplay           *display);
-static void   gimp_bucket_fill_tool_modifier_key   (GimpTool              *tool,
-                                                    GdkModifierType        key,
-                                                    gboolean               press,
-                                                    GdkModifierType        state,
-                                                    GimpDisplay           *display);
-static void   gimp_bucket_fill_tool_cursor_update  (GimpTool              *tool,
-                                                    const GimpCoords      *coords,
-                                                    GdkModifierType        state,
-                                                    GimpDisplay           *display);
+static void     gimp_bucket_fill_tool_constructed      (GObject               *object);
+static void     gimp_bucket_fill_tool_finalize         (GObject               *object);
+
+static gboolean gimp_bucket_fill_tool_initialize       (GimpTool              *tool,
+                                                        GimpDisplay           *display,
+                                                        GError               **error);
+
+static void     gimp_bucket_fill_tool_start            (GimpBucketFillTool    *tool,
+                                                        const GimpCoords      *coords,
+                                                        GimpDisplay           *display);
+static void     gimp_bucket_fill_tool_preview          (GimpBucketFillTool    *tool,
+                                                        const GimpCoords      *coords,
+                                                        GimpDisplay           *display,
+                                                        GimpFillOptions       *fill_options);
+static void     gimp_bucket_fill_tool_commit           (GimpBucketFillTool    *tool);
+static void     gimp_bucket_fill_tool_halt             (GimpBucketFillTool    *tool);
+static void     gimp_bucket_fill_tool_filter_flush     (GimpDrawableFilter    *filter,
+                                                        GimpTool              *tool);
+static void     gimp_bucket_fill_tool_create_graph     (GimpBucketFillTool    *tool);
+
+static void     gimp_bucket_fill_tool_button_press     (GimpTool              *tool,
+                                                        const GimpCoords      *coords,
+                                                        guint32                time,
+                                                        GdkModifierType        state,
+                                                        GimpButtonPressType    press_type,
+                                                        GimpDisplay           *display);
+static void     gimp_bucket_fill_tool_motion           (GimpTool              *tool,
+                                                        const GimpCoords      *coords,
+                                                        guint32                time,
+                                                        GdkModifierType        state,
+                                                        GimpDisplay           *display);
+static void     gimp_bucket_fill_tool_button_release   (GimpTool              *tool,
+                                                        const GimpCoords      *coords,
+                                                        guint32                time,
+                                                        GdkModifierType        state,
+                                                        GimpButtonReleaseType  release_type,
+                                                        GimpDisplay           *display);
+static void     gimp_bucket_fill_tool_modifier_key     (GimpTool              *tool,
+                                                        GdkModifierType        key,
+                                                        gboolean               press,
+                                                        GdkModifierType        state,
+                                                        GimpDisplay           *display);
+static void     gimp_bucket_fill_tool_cursor_update    (GimpTool              *tool,
+                                                        const GimpCoords      *coords,
+                                                        GdkModifierType        state,
+                                                        GimpDisplay           *display);
 
 static void     gimp_bucket_fill_compute_line_art      (GimpBucketFillTool    *tool);
 static gboolean gimp_bucket_fill_tool_connect_handlers (gpointer               data);
@@ -137,6 +181,8 @@ gimp_bucket_fill_tool_class_init (GimpBucketFillToolClass *klass)
   object_class->finalize     = gimp_bucket_fill_tool_finalize;
 
   tool_class->initialize     = gimp_bucket_fill_tool_initialize;
+  tool_class->button_press   = gimp_bucket_fill_tool_button_press;
+  tool_class->motion         = gimp_bucket_fill_tool_motion;
   tool_class->button_release = gimp_bucket_fill_tool_button_release;
   tool_class->modifier_key   = gimp_bucket_fill_tool_modifier_key;
   tool_class->cursor_update  = gimp_bucket_fill_tool_cursor_update;
@@ -214,19 +260,8 @@ gimp_bucket_fill_tool_initialize (GimpTool     *tool,
                                   GimpDisplay  *display,
                                   GError      **error)
 {
-  GimpBucketFillTool    *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
-  GimpBucketFillOptions *options     = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (bucket_tool);
-  GimpImage             *image       = gimp_display_get_image (display);
-  GimpDrawable          *drawable    = gimp_image_get_active_drawable (image);
-
-  if (options->fill_criterion == GIMP_SELECT_CRITERION_LINE_ART)
-    {
-      GimpImage    *prev_image = g_weak_ref_get (&bucket_tool->priv->cached_image);
-      GimpDrawable *prev_drawable = g_weak_ref_get (&bucket_tool->priv->cached_drawable);
-      g_return_val_if_fail (image == prev_image && drawable == prev_drawable, FALSE);
-      g_object_unref (prev_drawable);
-      g_object_unref (prev_image);
-    }
+  GimpImage    *image    = gimp_display_get_image (display);
+  GimpDrawable *drawable = gimp_image_get_active_drawable (image);
 
   if (! GIMP_TOOL_CLASS (parent_class)->initialize (tool, display, error))
     {
@@ -258,19 +293,199 @@ gimp_bucket_fill_tool_initialize (GimpTool     *tool,
 }
 
 static void
-gimp_bucket_fill_tool_button_release (GimpTool              *tool,
-                                      const GimpCoords      *coords,
-                                      guint32                time,
-                                      GdkModifierType        state,
-                                      GimpButtonReleaseType  release_type,
-                                      GimpDisplay           *display)
+gimp_bucket_fill_tool_start (GimpBucketFillTool *tool,
+                             const GimpCoords   *coords,
+                             GimpDisplay        *display)
+{
+  GimpBucketFillOptions *options  = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+  GimpContext           *context  = GIMP_CONTEXT (options);
+  GimpImage             *image    = gimp_display_get_image (display);
+  GimpDrawable          *drawable = gimp_image_get_active_drawable (image);
+
+  g_return_if_fail (! tool->priv->filter);
+
+  tool->priv->fill_in_progress = TRUE;
+
+  GIMP_TOOL (tool)->display  = display;
+  GIMP_TOOL (tool)->drawable = drawable;
+
+  gimp_bucket_fill_tool_create_graph (tool);
+
+  tool->priv->filter = gimp_drawable_filter_new (drawable, _("Bucket fill"),
+                                                 tool->priv->graph,
+                                                 GIMP_ICON_TOOL_BUCKET_FILL);
+
+  gimp_drawable_filter_set_region (tool->priv->filter, GIMP_FILTER_REGION_DRAWABLE);
+
+  /* We only set these here, and don't need to update it since we assume
+   * the settings can't change while the fill started.
+   */
+  gimp_drawable_filter_set_mode (tool->priv->filter,
+                                 gimp_context_get_paint_mode (context),
+                                 GIMP_LAYER_COLOR_SPACE_AUTO,
+                                 GIMP_LAYER_COLOR_SPACE_AUTO,
+                                 gimp_layer_mode_get_paint_composite_mode (gimp_context_get_paint_mode 
(context)));
+  gimp_drawable_filter_set_opacity (tool->priv->filter,
+                                    gimp_context_get_opacity (context));
+
+  g_signal_connect (tool->priv->filter, "flush",
+                    G_CALLBACK (gimp_bucket_fill_tool_filter_flush),
+                    tool);
+
+  if (options->fill_criterion == GIMP_SELECT_CRITERION_LINE_ART &&
+      tool->priv->async)
+    {
+      gimp_waitable_wait (GIMP_WAITABLE (tool->priv->async));
+      g_object_unref (tool->priv->async);
+      tool->priv->async = NULL;
+    }
+}
+
+static void
+gimp_bucket_fill_tool_preview (GimpBucketFillTool *tool,
+                               const GimpCoords   *coords,
+                               GimpDisplay        *display,
+                               GimpFillOptions    *fill_options)
+{
+  GimpBucketFillOptions *options  = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+  GimpImage             *image    = gimp_display_get_image (display);
+  GimpDrawable          *drawable = gimp_image_get_active_drawable (image);
+
+  if (tool->priv->filter)
+    {
+      GeglBuffer *fill     = NULL;
+      GeglBuffer *line_art = NULL;
+      gdouble     x        = coords->x;
+      gdouble     y        = coords->y;
+
+      if (! options->sample_merged)
+        {
+          gint off_x, off_y;
+
+          gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+          x -= (gdouble) off_x;
+          y -= (gdouble) off_y;
+        }
+
+      if (options->fill_criterion == GIMP_SELECT_CRITERION_LINE_ART)
+        line_art = g_object_ref (tool->priv->line_art);
+
+      fill = gimp_drawable_get_bucket_fill_buffer (drawable,
+                                                   line_art,
+                                                   fill_options,
+                                                   options->fill_transparent,
+                                                   options->fill_criterion,
+                                                   options->threshold / 255.0,
+                                                   options->sample_merged,
+                                                   options->diagonal_neighbors,
+                                                   x, y, &tool->priv->fill_mask,
+                                                   &x, &y, NULL, NULL);
+      if (line_art)
+        g_object_unref (line_art);
+
+      if (fill)
+        {
+          gegl_node_set (tool->priv->fill_node,
+                         "buffer", fill,
+                         NULL);
+          gegl_node_set (tool->priv->offset_node,
+                         "x", x,
+                         "y", y,
+                         NULL);
+          gimp_drawable_filter_apply (tool->priv->filter, NULL);
+          g_object_unref (fill);
+        }
+    }
+}
+
+static void
+gimp_bucket_fill_tool_commit (GimpBucketFillTool *tool)
+{
+  if (tool->priv->filter)
+    {
+      gimp_drawable_filter_commit (tool->priv->filter,
+                                   GIMP_PROGRESS (tool), FALSE);
+      gimp_image_flush (gimp_display_get_image (GIMP_TOOL (tool)->display));
+
+      if (tool->priv->compute_line_art_after_fill)
+        gimp_bucket_fill_compute_line_art (tool);
+    }
+}
+
+static void
+gimp_bucket_fill_tool_halt (GimpBucketFillTool *tool)
+{
+  if (tool->priv->graph)
+    {
+      g_clear_object (&tool->priv->graph);
+      tool->priv->fill_node      = NULL;
+      tool->priv->offset_node    = NULL;
+    }
+  if (tool->priv->filter)
+    {
+      gimp_drawable_filter_abort (tool->priv->filter);
+      g_clear_object (&tool->priv->filter);
+    }
+  g_clear_object (&tool->priv->fill_mask);
+
+  tool->priv->fill_in_progress            = FALSE;
+  tool->priv->compute_line_art_after_fill = FALSE;
+
+  GIMP_TOOL (tool)->display  = NULL;
+  GIMP_TOOL (tool)->drawable = NULL;
+}
+
+static void
+gimp_bucket_fill_tool_filter_flush (GimpDrawableFilter *filter,
+                                    GimpTool           *tool)
+{
+  GimpImage *image = gimp_display_get_image (tool->display);
+
+  gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+static void
+gimp_bucket_fill_tool_create_graph (GimpBucketFillTool *tool)
+{
+  GeglNode *graph;
+  GeglNode *output;
+  GeglNode *fill_node;
+  GeglNode *offset_node;
+
+  g_return_if_fail (! tool->priv->graph     &&
+                    ! tool->priv->fill_node &&
+                    ! tool->priv->offset_node);
+
+  graph = gegl_node_new ();
+
+  fill_node = gegl_node_new_child (graph,
+                                   "operation", "gegl:buffer-source",
+                                   NULL);
+  offset_node = gegl_node_new_child (graph,
+                                     "operation", "gegl:translate",
+                                     NULL);
+  output = gegl_node_get_output_proxy (graph, "output");
+  gegl_node_link_many (fill_node, offset_node, output, NULL);
+
+  tool->priv->graph       = graph;
+  tool->priv->fill_node   = fill_node;
+  tool->priv->offset_node = offset_node;
+}
+
+static void
+gimp_bucket_fill_tool_button_press (GimpTool            *tool,
+                                    const GimpCoords    *coords,
+                                    guint32              time,
+                                    GdkModifierType      state,
+                                    GimpButtonPressType  press_type,
+                                    GimpDisplay         *display)
 {
   GimpBucketFillTool    *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
   GimpBucketFillOptions *options     = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
   GimpImage             *image       = gimp_display_get_image (display);
 
-  if ((release_type == GIMP_BUTTON_RELEASE_CLICK ||
-       release_type == GIMP_BUTTON_RELEASE_NO_MOTION) &&
+  if (press_type == GIMP_BUTTON_PRESS_NORMAL &&
       gimp_image_coords_in_active_pickable (image, coords,
                                             options->sample_merged, TRUE))
     {
@@ -298,38 +513,62 @@ gimp_bucket_fill_tool_button_release (GimpTool              *tool,
             }
           else
             {
-              GeglBuffer *line_art;
-              gint        x = coords->x;
-              gint        y = coords->y;
-
-              if (! options->sample_merged)
-                {
-                  gint off_x, off_y;
-
-                  gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
-
-                  x -= off_x;
-                  y -= off_y;
-                }
-
-              gimp_waitable_wait (GIMP_WAITABLE (bucket_tool->priv->async));
-              line_art = g_object_ref (bucket_tool->priv->line_art);
-              g_object_unref (bucket_tool->priv->async);
-              bucket_tool->priv->async = NULL;
-
-              gimp_drawable_bucket_fill (drawable,
-                                         line_art,
-                                         fill_options,
-                                         options->fill_transparent,
-                                         options->fill_criterion,
-                                         options->threshold / 255.0,
-                                         options->sample_merged,
-                                         options->diagonal_neighbors,
-                                         x, y);
-              g_object_unref (line_art);
+              gimp_bucket_fill_tool_start (bucket_tool, coords, display);
+              gimp_bucket_fill_tool_preview (bucket_tool, coords, display, fill_options);
             }
+        }
+      else
+        {
+          gimp_message_literal (display->gimp, G_OBJECT (display),
+                                GIMP_MESSAGE_WARNING, error->message);
+          g_clear_error (&error);
+        }
 
-          gimp_image_flush (image);
+      g_object_unref (fill_options);
+    }
+
+  GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
+                                                press_type, display);
+}
+
+static void
+gimp_bucket_fill_tool_motion (GimpTool         *tool,
+                              const GimpCoords *coords,
+                              guint32           time,
+                              GdkModifierType   state,
+                              GimpDisplay      *display)
+{
+  GimpBucketFillTool    *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
+  GimpBucketFillOptions *options     = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
+  GimpImage             *image       = gimp_display_get_image (display);
+
+  GIMP_TOOL_CLASS (parent_class)->motion (tool, coords, time, state, display);
+
+  if (gimp_image_coords_in_active_pickable (image, coords,
+                                            options->sample_merged, TRUE) &&
+      /* Fill selection only needs to happen once. */
+      ! options->fill_selection)
+    {
+      GimpContext     *context  = GIMP_CONTEXT (options);
+      GimpFillOptions *fill_options;
+      GError          *error = NULL;
+
+      g_return_if_fail (bucket_tool->priv->fill_in_progress);
+
+      fill_options = gimp_fill_options_new (image->gimp, NULL, FALSE);
+
+      if (gimp_fill_options_set_by_fill_mode (fill_options, context,
+                                              options->fill_mode,
+                                              &error))
+        {
+          gimp_fill_options_set_antialias (fill_options, options->antialias);
+
+          gimp_context_set_opacity (GIMP_CONTEXT (fill_options),
+                                    gimp_context_get_opacity (context));
+          gimp_context_set_paint_mode (GIMP_CONTEXT (fill_options),
+                                       gimp_context_get_paint_mode (context));
+
+          gimp_bucket_fill_tool_preview (bucket_tool, coords, display, fill_options);
         }
       else
         {
@@ -340,12 +579,28 @@ gimp_bucket_fill_tool_button_release (GimpTool              *tool,
 
       g_object_unref (fill_options);
     }
+}
+
+static void
+gimp_bucket_fill_tool_button_release (GimpTool              *tool,
+                                      const GimpCoords      *coords,
+                                      guint32                time,
+                                      GdkModifierType        state,
+                                      GimpButtonReleaseType  release_type,
+                                      GimpDisplay           *display)
+{
+  GimpBucketFillTool *bucket_tool = GIMP_BUCKET_FILL_TOOL (tool);
+  gboolean            commit;
+
+  commit = (release_type != GIMP_BUTTON_RELEASE_CANCEL);
+
+  if (commit)
+    gimp_bucket_fill_tool_commit (bucket_tool);
+
+  gimp_bucket_fill_tool_halt (bucket_tool);
 
   GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
                                                   release_type, display);
-
-  tool->display  = NULL;
-  tool->drawable = NULL;
 }
 
 static void
@@ -454,7 +709,7 @@ gimp_bucket_fill_compute_line_art_async  (GimpAsync      *async,
 }
 
 static void
-gimp_bucket_fill_compute_line_art_cb (GimpAsync                *async,
+gimp_bucket_fill_compute_line_art_cb (GimpAsync          *async,
                                       GimpBucketFillTool *tool)
 {
   if (gimp_async_is_canceled (async))
@@ -469,6 +724,12 @@ gimp_bucket_fill_compute_line_art (GimpBucketFillTool *tool)
 {
   GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
 
+  if (tool->priv->fill_in_progress)
+    {
+      tool->priv->compute_line_art_after_fill = TRUE;
+      return;
+    }
+
   g_clear_object (&tool->priv->line_art);
   if (options->fill_criterion == GIMP_SELECT_CRITERION_LINE_ART)
     {



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