Commit e1c40506 authored by Jehan's avatar Jehan

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).
parent a3cda4ab
......@@ -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;
}
......@@ -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__ */
......@@ -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;
}
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment