[gimp] app: add some new feature to close line arts manually.
- From: Jehan <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp] app: add some new feature to close line arts manually.
- Date: Tue, 8 Feb 2022 22:45:45 +0000 (UTC)
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]