Commit f246f404 authored by Jehan's avatar Jehan

app: compute line art in advance.

Right now, this is mostly meaningless as it is still done sequentially.
But I am mostly preparing the field to pre-compute the line art as
background thread.
parent b9de1076
......@@ -516,6 +516,7 @@ gimp_channel_select_fuzzy (GimpChannel *channel,
pickable = GIMP_PICKABLE (drawable);
add_on = gimp_pickable_contiguous_region_by_seed (pickable,
NULL,
antialias,
threshold,
select_transparent,
......
......@@ -49,6 +49,7 @@
void
gimp_drawable_bucket_fill (GimpDrawable *drawable,
GeglBuffer *line_art,
GimpFillOptions *options,
gboolean fill_transparent,
GimpSelectCriterion fill_criterion,
......@@ -91,6 +92,7 @@ gimp_drawable_bucket_fill (GimpDrawable *drawable,
* contiguous region.
*/
mask_buffer = gimp_pickable_contiguous_region_by_seed (pickable,
line_art,
antialias,
threshold,
fill_transparent,
......
......@@ -20,6 +20,7 @@
void gimp_drawable_bucket_fill (GimpDrawable *drawable,
GeglBuffer *line_art,
GimpFillOptions *options,
gboolean fill_transparent,
GimpSelectCriterion fill_criterion,
......
......@@ -99,8 +99,103 @@ static void find_contiguous_region (GeglBuffer *src_buffer,
/* public functions */
GeglBuffer *
gimp_pickable_contiguous_region_prepare_line_art (GimpPickable *pickable,
gboolean select_transparent)
{
GeglBuffer *lineart;
gboolean has_alpha;
g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
gimp_pickable_flush (pickable);
lineart = gimp_pickable_get_buffer (pickable);
has_alpha = babl_format_has_alpha (gegl_buffer_get_format (lineart));
if (! has_alpha)
{
if (select_transparent)
{
/* don't select transparent regions if there are no fully
* transparent pixels.
*/
GeglBufferIterator *gi;
select_transparent = FALSE;
gi = gegl_buffer_iterator_new (lineart, NULL, 0, babl_format ("A u8"),
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 3);
while (gegl_buffer_iterator_next (gi))
{
guint8 *p = (guint8*) gi->items[0].data;
gint k;
for (k = 0; k < gi->length; k++)
{
if (! *p)
{
select_transparent = TRUE;
break;
}
p++;
}
if (select_transparent)
break;
}
if (select_transparent)
gegl_buffer_iterator_stop (gi);
}
}
else
{
select_transparent = FALSE;
}
/* For smart selection, we generate a binarized image with close
* regions, then run a composite selection with no threshold on
* this intermediate buffer.
*/
GIMP_TIMER_START();
lineart = gimp_lineart_close (lineart,
select_transparent,
/*contour_detection_level,*/
0.92,
/* erosion, */
-1,
/*minimal_lineart_area,*/
5,
/*normal_estimate_mask_size,*/
5,
/*end_point_rate,*/
0.85,
/*spline_max_length,*/
60,
/*spline_max_angle,*/
90.0,
/*end_point_connectivity,*/
2,
/*spline_roundness,*/
1.0,
/*allow_self_intersections,*/
TRUE,
/*created_regions_significant_area,*/
4,
/*created_regions_minimum_area,*/
100,
/*small_segments_from_spline_sources,*/
TRUE,
/*segments_max_length*/
20);
GIMP_TIMER_END("close line-art");
return lineart;
}
GeglBuffer *
gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
GeglBuffer *line_art,
gboolean antialias,
gfloat threshold,
gboolean select_transparent,
......@@ -116,89 +211,61 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
gint n_components;
gboolean has_alpha;
gfloat start_col[MAX_CHANNELS];
gfloat flag = 2.0;
gboolean smart_line_art = FALSE;
gfloat flag = 2.0;
gboolean free_line_art = FALSE;
g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
gimp_pickable_flush (pickable);
src_buffer = gimp_pickable_get_buffer (pickable);
format = choose_format (src_buffer, select_criterion,
&n_components, &has_alpha);
gegl_buffer_sample (src_buffer, x, y, NULL, start_col, format,
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
if (has_alpha)
if (select_criterion == GIMP_SELECT_CRITERION_LINE_ART)
{
if (select_transparent)
if (line_art == NULL)
{
/* don't select transparent regions if the start pixel isn't
* fully transparent
/* It is much better experience to pre-compute the line art,
* but it may not be always possible (for instance when
* selecting/filling through a PDB call).
*/
if (start_col[n_components - 1] > 0)
select_transparent = FALSE;
line_art = gimp_pickable_contiguous_region_prepare_line_art (pickable, select_transparent);
free_line_art = TRUE;
}
}
else
{
select_transparent = FALSE;
}
if (select_criterion == GIMP_SELECT_CRITERION_LINE_ART)
{
/* For smart selection, we generate a binarized image with close
* regions, then run a composite selection with no threshold on
* this intermediate buffer.
*/
GIMP_TIMER_START();
src_buffer = gimp_lineart_close (src_buffer,
select_transparent,
/*contour_detection_level,*/
0.92,
/* erosion, */
-1,
/*minimal_lineart_area,*/
5,
/*normal_estimate_mask_size,*/
5,
/*end_point_rate,*/
0.85,
/*spline_max_length,*/
60,
/*spline_max_angle,*/
90.0,
/*end_point_connectivity,*/
2,
/*spline_roundness,*/
1.0,
/*allow_self_intersections,*/
TRUE,
/*created_regions_significant_area,*/
4,
/*created_regions_minimum_area,*/
100,
/*small_segments_from_spline_sources,*/
TRUE,
/*segments_max_length*/
20);
src_buffer = line_art;
smart_line_art = TRUE;
antialias = FALSE;
threshold = 0.0;
select_transparent = FALSE;
select_criterion = GIMP_SELECT_CRITERION_COMPOSITE;
diagonal_neighbors = FALSE;
}
else
{
gimp_pickable_flush (pickable);
src_buffer = gimp_pickable_get_buffer (pickable);
format = choose_format (src_buffer, select_criterion,
&n_components, &has_alpha);
gegl_buffer_sample (src_buffer, x, y, NULL, start_col, format,
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
GIMP_TIMER_END("close line-art");
if (has_alpha)
{
if (select_transparent)
{
/* don't select transparent regions if the start pixel isn't
* fully transparent
*/
if (start_col[n_components - 1] > 0)
select_transparent = FALSE;
}
}
else
{
select_transparent = FALSE;
}
}
format = choose_format (src_buffer, select_criterion,
&n_components, &has_alpha);
gegl_buffer_sample (src_buffer, x, y, NULL, start_col, format,
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
extent = *gegl_buffer_get_extent (src_buffer);
mask_buffer = gegl_buffer_new (&extent, babl_format ("Y float"));
......@@ -272,7 +339,6 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
prio++;
}
}
g_object_unref (src_buffer);
/* Watershed the line art. */
graph = gegl_node_new ();
......@@ -298,6 +364,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);
}
return mask_buffer;
......
......@@ -19,21 +19,24 @@
#define __GIMP_PICKABLE_CONTIGUOUS_REGION_H__
GeglBuffer * gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
gboolean antialias,
gfloat threshold,
gboolean select_transparent,
GimpSelectCriterion select_criterion,
gboolean diagonal_neighbors,
gint x,
gint y);
GeglBuffer * gimp_pickable_contiguous_region_prepare_line_art (GimpPickable *pickable,
gboolean select_transparent);
GeglBuffer * gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
GeglBuffer *line_art,
gboolean antialias,
gfloat threshold,
gboolean select_transparent,
GimpSelectCriterion select_criterion,
gboolean diagonal_neighbors,
gint x,
gint y);
GeglBuffer * gimp_pickable_contiguous_region_by_color (GimpPickable *pickable,
gboolean antialias,
gfloat threshold,
gboolean select_transparent,
GimpSelectCriterion select_criterion,
const GimpRGB *color);
GeglBuffer * gimp_pickable_contiguous_region_by_color (GimpPickable *pickable,
gboolean antialias,
gfloat threshold,
gboolean select_transparent,
GimpSelectCriterion select_criterion,
const GimpRGB *color);
#endif /* __GIMP_PICKABLE_CONTIGUOUS_REGION_H__ */
......@@ -165,7 +165,7 @@ drawable_edit_bucket_fill_invoker (GimpProcedure *procedure,
if (gimp_fill_options_set_by_fill_type (options, context,
fill_type, error))
{
gimp_drawable_bucket_fill (drawable, options,
gimp_drawable_bucket_fill (drawable, NULL, options,
GIMP_PDB_CONTEXT (context)->sample_transparent,
GIMP_PDB_CONTEXT (context)->sample_criterion,
GIMP_PDB_CONTEXT (context)->sample_threshold,
......
......@@ -31,6 +31,9 @@
#include "core/gimpfilloptions.h"
#include "core/gimpimage.h"
#include "core/gimpitem.h"
#include "core/gimplineart.h"
#include "core/gimppickable.h"
#include "core/gimppickable-contiguous-region.h"
#include "widgets/gimphelp-ids.h"
#include "widgets/gimpwidgets-utils.h"
......@@ -44,8 +47,18 @@
#include "gimp-intl.h"
struct _GimpBucketFillToolPrivate
{
GeglBuffer *line_art;
GWeakRef cached_image;
GWeakRef cached_drawable;
};
/* 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);
......@@ -65,8 +78,25 @@ static void gimp_bucket_fill_tool_cursor_update (GimpTool *tool,
GdkModifierType state,
GimpDisplay *display);
G_DEFINE_TYPE (GimpBucketFillTool, gimp_bucket_fill_tool, GIMP_TYPE_TOOL)
static void gimp_bucket_fill_compute_line_art (GimpBucketFillTool *tool);
static gboolean gimp_bucket_fill_tool_connect_handlers (gpointer data);
static void gimp_bucket_fill_tool_options_notified (GimpBucketFillOptions *options,
GParamSpec *pspec,
GimpBucketFillTool *tool);
static void gimp_bucket_fill_tool_image_changed (GimpContext *context,
GimpImage *image,
GimpBucketFillTool *tool);
static void gimp_bucket_fill_tool_drawable_changed (GimpImage *image,
GimpBucketFillTool *tool);
static void gimp_bucket_fill_tool_drawable_update (GimpDrawable *drawable,
gint x,
gint y,
gint width,
gint height,
GimpBucketFillTool *tool);
G_DEFINE_TYPE_WITH_PRIVATE (GimpBucketFillTool, gimp_bucket_fill_tool, GIMP_TYPE_TOOL)
#define parent_class gimp_bucket_fill_tool_parent_class
......@@ -95,7 +125,11 @@ gimp_bucket_fill_tool_register (GimpToolRegisterCallback callback,
static void
gimp_bucket_fill_tool_class_init (GimpBucketFillToolClass *klass)
{
GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
object_class->constructed = gimp_bucket_fill_tool_constructed;
object_class->finalize = gimp_bucket_fill_tool_finalize;
tool_class->initialize = gimp_bucket_fill_tool_initialize;
tool_class->button_release = gimp_bucket_fill_tool_button_release;
......@@ -116,6 +150,58 @@ gimp_bucket_fill_tool_init (GimpBucketFillTool *bucket_fill_tool)
"context/context-opacity-set");
gimp_tool_control_set_action_object_1 (tool->control,
"context/context-pattern-select-set");
bucket_fill_tool->priv = gimp_bucket_fill_tool_get_instance_private (bucket_fill_tool);
}
static void
gimp_bucket_fill_tool_constructed (GObject *object)
{
GimpTool *tool = GIMP_TOOL (object);
GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (object);
Gimp *gimp = GIMP_CONTEXT (options)->gimp;
G_OBJECT_CLASS (parent_class)->constructed (object);
/* Avoid computing initial line art several times (for every option
* property as it gets deserialized) if tool is selected when starting
* GIMP with an image.
*/
if (gimp_is_restored (gimp))
gimp_bucket_fill_tool_connect_handlers (tool);
else
g_idle_add (gimp_bucket_fill_tool_connect_handlers, tool);
}
static void
gimp_bucket_fill_tool_finalize (GObject *object)
{
GimpBucketFillTool *tool = GIMP_BUCKET_FILL_TOOL (object);
GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
Gimp *gimp = GIMP_CONTEXT (options)->gimp;
GimpContext *context = gimp_get_user_context (gimp);
GimpImage *image = g_weak_ref_get (&tool->priv->cached_image);
GimpDrawable *drawable = g_weak_ref_get (&tool->priv->cached_drawable);
g_clear_object (&tool->priv->line_art);
if (image)
{
g_signal_handlers_disconnect_by_data (image, tool);
g_object_unref (image);
}
if (drawable)
{
g_signal_handlers_disconnect_by_data (drawable, tool);
g_object_unref (drawable);
}
g_signal_handlers_disconnect_by_func (options,
G_CALLBACK (gimp_bucket_fill_tool_options_notified),
tool);
g_signal_handlers_disconnect_by_data (context, tool);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
......@@ -123,8 +209,19 @@ gimp_bucket_fill_tool_initialize (GimpTool *tool,
GimpDisplay *display,
GError **error)
{
GimpImage *image = gimp_display_get_image (display);
GimpDrawable *drawable = gimp_image_get_active_drawable (image);
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);
}
if (! GIMP_TOOL_CLASS (parent_class)->initialize (tool, display, error))
{
......@@ -138,17 +235,17 @@ gimp_bucket_fill_tool_initialize (GimpTool *tool,
return FALSE;
}
if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
if (! gimp_item_is_visible (GIMP_ITEM (drawable)))
{
g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
_("The active layer's pixels are locked."));
_("The active layer is not visible."));
return FALSE;
}
if (! gimp_item_is_visible (GIMP_ITEM (drawable)))
if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
{
g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
_("The active layer is not visible."));
_("The active layer's pixels are locked."));
return FALSE;
}
......@@ -208,7 +305,10 @@ gimp_bucket_fill_tool_button_release (GimpTool *tool,
y -= off_y;
}
gimp_drawable_bucket_fill (drawable, fill_options,
gimp_drawable_bucket_fill (drawable,
GIMP_BUCKET_FILL_TOOL (tool)->priv->line_art,
fill_options,
options->fill_transparent,
options->fill_criterion,
options->threshold / 255.0,
......@@ -307,3 +407,149 @@ gimp_bucket_fill_tool_cursor_update (GimpTool *tool,
GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
}
static void
gimp_bucket_fill_compute_line_art (GimpBucketFillTool *tool)
{
GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
g_clear_object (&tool->priv->line_art);
if (options->fill_criterion == GIMP_SELECT_CRITERION_LINE_ART)
{
GimpPickable *pickable = NULL;
GimpDrawable *image = g_weak_ref_get (&tool->priv->cached_image);
GimpDrawable *drawable = g_weak_ref_get (&tool->priv->cached_drawable);
if (image && options->sample_merged)
pickable = GIMP_PICKABLE (image);
else if (drawable && ! options->sample_merged)
pickable = GIMP_PICKABLE (drawable);
if (pickable)
tool->priv->line_art = gimp_pickable_contiguous_region_prepare_line_art (pickable,
options->fill_transparent);
if (image)
g_object_unref (image);
if (drawable)
g_object_unref (drawable);
}
}
static gboolean
gimp_bucket_fill_tool_connect_handlers (gpointer data)
{
GimpBucketFillTool *tool = GIMP_BUCKET_FILL_TOOL (data);
GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
Gimp *gimp = GIMP_CONTEXT (options)->gimp;
if (gimp_is_restored (gimp))
{
GimpContext *context = gimp_get_user_context (gimp);
GimpImage *image = gimp_context_get_image (context);
g_signal_connect (options, "notify::fill-criterion",
G_CALLBACK (gimp_bucket_fill_tool_options_notified),
tool);
g_signal_connect (options, "notify::sample-merged",
G_CALLBACK (gimp_bucket_fill_tool_options_notified),
tool);
g_signal_connect (options, "notify::fill-transparent",
G_CALLBACK (gimp_bucket_fill_tool_options_notified),
tool);
g_signal_connect (context, "image-changed",
G_CALLBACK (gimp_bucket_fill_tool_image_changed),
tool);
gimp_bucket_fill_tool_image_changed (context, image, GIMP_BUCKET_FILL_TOOL (tool));
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
}
static void
gimp_bucket_fill_tool_options_notified (GimpBucketFillOptions *options,
GParamSpec *pspec,
GimpBucketFillTool *tool)
{
if ((! strcmp (pspec->name, "fill-criterion") ||
! strcmp (pspec->name, "fill-transparent") ||
! strcmp (pspec->name, "sample-merged")) &&
options->fill_criterion == GIMP_SELECT_CRITERION_LINE_ART)
{
gimp_bucket_fill_compute_line_art (tool);
}
}
static void
gimp_bucket_fill_tool_image_changed (GimpContext *context,
GimpImage *image,
GimpBucketFillTool *tool)
{
GimpImage *prev_image = g_weak_ref_get (&tool->priv->cached_image);
if (image != prev_image)
{
GimpImage *prev_drawable = g_weak_ref_get (&tool->priv->cached_drawable);
g_clear_object (&tool->priv->line_art);
if (prev_image)
g_signal_handlers_disconnect_by_data (prev_image, tool);
if (prev_drawable)
{
g_signal_handlers_disconnect_by_data (prev_drawable, tool);
g_object_unref (prev_drawable);
}
g_weak_ref_set (&tool->priv->cached_image, image ? image : NULL);
g_weak_ref_set (&tool->priv->cached_drawable, NULL);
if (image)
{
g_signal_connect (image, "active-layer-changed",
G_CALLBACK (gimp_bucket_fill_tool_drawable_changed),
tool);
g_signal_connect (image, "active-channel-changed",
G_CALLBACK (gimp_bucket_fill_tool_drawable_changed),
tool);
gimp_bucket_fill_tool_drawable_changed (image, tool);
}
}
if (prev_image)
g_object_unref (prev_image);
}
static void
gimp_bucket_fill_tool_drawable_changed (GimpImage *image,
GimpBucketFillTool *tool)
{
GimpDrawable *drawable = gimp_image_get_active_drawable (image);
GimpDrawable *prev_drawable = g_weak_ref_get (&tool->priv->cached_drawable);
if (drawable != prev_drawable)
{
if (prev_drawable)
g_signal_handlers_disconnect_by_data (prev_drawable, tool);
g_weak_ref_set (&tool->priv->cached_drawable, drawable ? drawable : NULL);
if (drawable)
g_signal_connect (drawable, "update",
G_CALLBACK (gimp_bucket_fill_tool_drawable_update),
tool);
gimp_bucket_fill_compute_line_art (tool);
}
if (prev_drawable)
g_object_unref (prev_drawable);
}
static void
gimp_bucket_fill_tool_drawable_update (GimpDrawable *drawable,
gint x,
gint y,
gint width,
gint height,
GimpBucketFillTool *tool)
{
gimp_bucket_fill_compute_line_art (tool);
}
......@@ -34,10 +34,13 @@
typedef struct _GimpBucketFillTool GimpBucketFillTool;
typedef struct _GimpBucketFillToolClass GimpBucketFillToolClass;
typedef struct _GimpBucketFillToolPrivate GimpBucketFillToolPrivate;
struct _GimpBucketFillTool
{
GimpTool parent_instance;
GimpTool parent_instance;
GimpBucketFillToolPrivate *priv;
};
struct _GimpBucketFillToolClass
......
......@@ -122,7 +122,7 @@ gimp_fuzzy_select_tool_get_mask (GimpRegionSelectTool *region_select,
pickable = GIMP_PICKABLE (image);
}
return gimp_pickable_contiguous_region_by_seed (pickable,
return gimp_pickable_contiguous_region_by_seed (pickable, NULL,
sel_options->antialias,
options->threshold / 255.0,
options->select_transparent,
......
......@@ -169,7 +169,7 @@ HELP
if (gimp_fill_options_set_by_fill_type (options, context,
fill_type, error))
{
gimp_drawable_bucket_fill (drawable, options,
gimp_drawable_bucket_fill (drawable, NULL, options,
GIMP_PDB_CONTEXT (context)->sample_transparent,
GIMP_PDB_CONTEXT (context)->sample_criterion,
GIMP_PDB_CONTEXT (context)->sample_threshold,
......
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