[gimp] app: bucket fill tool with a "paint-style" interaction.
- From: Jehan <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp] app: bucket fill tool with a "paint-style" interaction.
- Date: Wed, 14 Nov 2018 12:42:44 +0000 (UTC)
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]