app: reorganize the line art code inside a GimpLineArt object.

The code was too much spread out, in core and tool code, and also it was
made too specific to fill. I'll want to reuse this code at least in the
fuzzy select tool. This will avoid code duplication, and also make this
new process more self-contained and simpler to review later (the
algorithm also has a lot of settings and it is much cleaner to have them
as properties rather than passing these as parameters through many
functions).

The refactoring may not be finished; that's at least a first step.
This commit is contained in:
Jehan 2018-12-01 18:33:51 +01:00
parent 27aa87ba8e
commit db18c679f3
12 changed files with 824 additions and 619 deletions

View File

@ -196,6 +196,7 @@ typedef struct _GimpHistogram GimpHistogram;
typedef struct _GimpIdTable GimpIdTable;
typedef struct _GimpImagefile GimpImagefile;
typedef struct _GimpInterpreterDB GimpInterpreterDB;
typedef struct _GimpLineArt GimpLineArt;
typedef struct _GimpObjectQueue GimpObjectQueue;
typedef struct _GimpParasiteList GimpParasiteList;
typedef struct _GimpPdbProgress GimpPdbProgress;

View File

@ -516,13 +516,12 @@ gimp_channel_select_fuzzy (GimpChannel *channel,
pickable = GIMP_PICKABLE (drawable);
add_on = gimp_pickable_contiguous_region_by_seed (pickable,
NULL, NULL,
NULL,
antialias,
threshold,
select_transparent,
select_criterion,
diagonal_neighbors,
0.92, 3, 20, 60, /* TODO */
x, y);
if (! sample_merged)

View File

@ -49,18 +49,13 @@
void
gimp_drawable_bucket_fill (GimpDrawable *drawable,
GeglBuffer *line_art,
gfloat *distmap,
GimpLineArt *line_art,
GimpFillOptions *options,
gboolean fill_transparent,
GimpSelectCriterion fill_criterion,
gdouble threshold,
gboolean sample_merged,
gboolean diagonal_neighbors,
gfloat line_art_stroke_threshold,
gint line_art_max_grow,
gint line_art_segment_max_length,
gint line_art_spline_max_length,
gdouble seed_x,
gdouble seed_y)
{
@ -76,15 +71,10 @@ gimp_drawable_bucket_fill (GimpDrawable *drawable,
image = gimp_item_get_image (GIMP_ITEM (drawable));
gimp_set_busy (image->gimp);
buffer = gimp_drawable_get_bucket_fill_buffer (drawable, line_art,
distmap, options,
buffer = gimp_drawable_get_bucket_fill_buffer (drawable, line_art, options,
fill_transparent, fill_criterion,
threshold, sample_merged,
diagonal_neighbors,
line_art_stroke_threshold,
line_art_max_grow,
line_art_segment_max_length,
line_art_spline_max_length,
seed_x, seed_y, NULL,
&mask_x, &mask_y, &width, &height);
@ -141,18 +131,13 @@ gimp_drawable_bucket_fill (GimpDrawable *drawable,
*/
GeglBuffer *
gimp_drawable_get_bucket_fill_buffer (GimpDrawable *drawable,
GeglBuffer *line_art,
gfloat *distmap,
GimpLineArt *line_art,
GimpFillOptions *options,
gboolean fill_transparent,
GimpSelectCriterion fill_criterion,
gdouble threshold,
gboolean sample_merged,
gboolean diagonal_neighbors,
gfloat stroke_threshold,
gint max_grow,
gint segment_max_length,
gint spline_max_length,
gdouble seed_x,
gdouble seed_y,
GeglBuffer **mask_buffer,
@ -208,16 +193,12 @@ gimp_drawable_get_bucket_fill_buffer (GimpDrawable *drawable,
* contiguous region.
*/
new_mask = gimp_pickable_contiguous_region_by_seed (pickable,
line_art, distmap,
line_art,
antialias,
threshold,
fill_transparent,
fill_criterion,
diagonal_neighbors,
stroke_threshold,
max_grow,
segment_max_length,
spline_max_length,
(gint) seed_x,
(gint) seed_y);
if (mask_buffer && *mask_buffer)

View File

@ -20,33 +20,23 @@
void gimp_drawable_bucket_fill (GimpDrawable *drawable,
GeglBuffer *line_art,
gfloat *distmap,
GimpLineArt *line_art,
GimpFillOptions *options,
gboolean fill_transparent,
GimpSelectCriterion fill_criterion,
gdouble threshold,
gboolean sample_merged,
gboolean diagonal_neighbors,
gfloat line_art_stroke_threshold,
gint line_art_max_grow,
gint line_art_segment_max_length,
gint line_art_spline_max_length,
gdouble x,
gdouble y);
GeglBuffer * gimp_drawable_get_bucket_fill_buffer (GimpDrawable *drawable,
GeglBuffer *line_art,
gfloat *distmap,
GimpLineArt *line_art,
GimpFillOptions *options,
gboolean fill_transparent,
GimpSelectCriterion fill_criterion,
gdouble threshold,
gboolean sample_merged,
gboolean diagonal_neighbors,
gfloat line_art_stroke_threshold,
gint line_art_max_grow,
gint line_art_segment_max_length,
gint line_art_spline_max_length,
gdouble seed_x,
gdouble seed_y,
GeglBuffer **mask_buffer,

View File

@ -20,13 +20,74 @@
#include "config.h"
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libgimpmath/gimpmath.h"
#include "core-types.h"
#include "gimp-parallel.h"
#include "gimp-utils.h" /* GIMP_TIMER */
#include "gimpasync.h"
#include "gimpcancelable.h"
#include "gimpdrawable.h"
#include "gimpimage.h"
#include "gimplineart.h"
#include "gimppickable.h"
#include "gimpprojection.h"
#include "gimpwaitable.h"
#include "gimp-intl.h"
enum
{
PROP_0,
PROP_SELECT_TRANSPARENT,
PROP_MAX_GROW,
PROP_THRESHOLD,
PROP_SPLINE_MAX_LEN,
PROP_SEGMENT_MAX_LEN,
};
typedef struct _GimpLineArtPrivate GimpLineArtPrivate;
struct _GimpLineArtPrivate
{
gboolean frozen;
gboolean compute_after_thaw;
GimpAsync *async;
GimpPickable *input;
GeglBuffer *closed;
gfloat *distmap;
/* Used in the closing step. */
gboolean select_transparent;
gdouble threshold;
gint spline_max_len;
gint segment_max_len;
/* Used in the grow step. */
gint max_grow;
};
typedef struct
{
GeglBuffer *buffer;
gboolean select_transparent;
gdouble threshold;
gint spline_max_len;
gint segment_max_len;
} LineArtData;
typedef struct
{
GeglBuffer *closed;
gfloat *distmap;
} LineArtResult;
static int DeltaX[4] = {+1, -1, 0, 0};
static int DeltaY[4] = {0, 0, +1, -1};
@ -67,56 +128,109 @@ typedef struct _Edgel
guint next, previous;
} Edgel;
static void gimp_lineart_denoise (GeglBuffer *buffer,
int size);
static void gimp_lineart_compute_normals_curvatures (GeglBuffer *mask,
gfloat *normals,
gfloat *curvatures,
gfloat *smoothed_curvatures,
int normal_estimate_mask_size);
static gfloat * gimp_lineart_get_smooth_curvatures (GArray *edgelset);
static GArray * gimp_lineart_curvature_extremums (gfloat *curvatures,
gfloat *smoothed_curvatures,
gint curvatures_width,
gint curvatures_height);
static gint gimp_spline_candidate_cmp (const SplineCandidate *a,
const SplineCandidate *b,
gpointer user_data);
static GList * gimp_lineart_find_spline_candidates (GArray *max_positions,
gfloat *normals,
gint width,
gint distance_threshold,
gfloat max_angle_deg);
static GArray * gimp_lineart_discrete_spline (Pixel p0,
GimpVector2 n0,
Pixel p1,
GimpVector2 n1);
static void gimp_line_art_finalize (GObject *object);
static void gimp_line_art_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_line_art_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static gint gimp_number_of_transitions (GArray *pixels,
GeglBuffer *buffer);
static gboolean gimp_lineart_curve_creates_region (GeglBuffer *mask,
GArray *pixels,
int lower_size_limit,
int upper_size_limit);
static GArray * gimp_lineart_line_segment_until_hit (const GeglBuffer *buffer,
Pixel start,
GimpVector2 direction,
int size);
static gfloat * gimp_lineart_estimate_strokes_radii (GeglBuffer *mask);
/* Functions for asynchronous computation. */
static void gimp_line_art_compute (GimpLineArt *line_art);
static void gimp_line_art_compute_cb (GimpAsync *async,
GimpLineArt *line_art);
static GimpAsync * gimp_line_art_prepare_async (GimpLineArt *line_art,
gint priority);
static void gimp_line_art_prepare_async_func (GimpAsync *async,
LineArtData *data);
static LineArtData * line_art_data_new (GeglBuffer *buffer,
GimpLineArt *line_art);
static void line_art_data_free (LineArtData *data);
static LineArtResult * line_art_result_new (GeglBuffer *line_art,
gfloat *distmap);
static void line_art_result_free (LineArtResult *result);
static void gimp_line_art_projection_rendered (GimpProjection *proj,
GimpLineArt *line_art);
static void gimp_line_art_drawable_painted (GimpDrawable *drawable,
GimpLineArt *line_art);
/* All actual computation functions. */
static GeglBuffer * gimp_line_art_close (GeglBuffer *buffer,
gboolean select_transparent,
gdouble stroke_threshold,
gint spline_max_length,
gint segment_max_length,
gint minimal_lineart_area,
gint normal_estimate_mask_size,
gfloat end_point_rate,
gfloat spline_max_angle,
gint end_point_connectivity,
gfloat spline_roundness,
gboolean allow_self_intersections,
gint created_regions_significant_area,
gint created_regions_minimum_area,
gboolean small_segments_from_spline_sources,
gfloat **lineart_distmap);
static void gimp_lineart_denoise (GeglBuffer *buffer,
int size);
static void gimp_lineart_compute_normals_curvatures (GeglBuffer *mask,
gfloat *normals,
gfloat *curvatures,
gfloat *smoothed_curvatures,
int normal_estimate_mask_size);
static gfloat * gimp_lineart_get_smooth_curvatures (GArray *edgelset);
static GArray * gimp_lineart_curvature_extremums (gfloat *curvatures,
gfloat *smoothed_curvatures,
gint curvatures_width,
gint curvatures_height);
static gint gimp_spline_candidate_cmp (const SplineCandidate *a,
const SplineCandidate *b,
gpointer user_data);
static GList * gimp_lineart_find_spline_candidates (GArray *max_positions,
gfloat *normals,
gint width,
gint distance_threshold,
gfloat max_angle_deg);
static GArray * gimp_lineart_discrete_spline (Pixel p0,
GimpVector2 n0,
Pixel p1,
GimpVector2 n1);
static gint gimp_number_of_transitions (GArray *pixels,
GeglBuffer *buffer);
static gboolean gimp_lineart_curve_creates_region (GeglBuffer *mask,
GArray *pixels,
int lower_size_limit,
int upper_size_limit);
static GArray * gimp_lineart_line_segment_until_hit (const GeglBuffer *buffer,
Pixel start,
GimpVector2 direction,
int size);
static gfloat * gimp_lineart_estimate_strokes_radii (GeglBuffer *mask);
/* Some callback-type functions. */
static guint visited_hash_fun (Pixel *key);
static gboolean visited_equal_fun (Pixel *e1,
Pixel *e2);
static guint visited_hash_fun (Pixel *key);
static gboolean visited_equal_fun (Pixel *e1,
Pixel *e2);
static inline gboolean border_in_direction (GeglBuffer *mask,
Pixel p,
int direction);
static inline GimpVector2 pair2normal (Pixel p,
gfloat *normals,
gint width);
static inline gboolean border_in_direction (GeglBuffer *mask,
Pixel p,
int direction);
static inline GimpVector2 pair2normal (Pixel p,
gfloat *normals,
gint width);
/* Edgel */
@ -157,22 +271,518 @@ static void gimp_edgelset_next8 (const GeglBuffer *buffer,
Edgel *it,
Edgel *n);
G_DEFINE_TYPE_WITH_CODE (GimpLineArt, gimp_line_art, GIMP_TYPE_OBJECT,
G_ADD_PRIVATE (GimpLineArt))
static void
gimp_line_art_class_init (GimpLineArtClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gimp_line_art_finalize;
object_class->set_property = gimp_line_art_set_property;
object_class->get_property = gimp_line_art_get_property;
g_object_class_install_property (object_class, PROP_SELECT_TRANSPARENT,
g_param_spec_boolean ("select-transparent",
_("Select transparent pixels instead of gray ones"),
_("Select transparent pixels instead of gray ones"),
TRUE,
G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_THRESHOLD,
g_param_spec_double ("threshold",
_("Line art detection threshold"),
_("Threshold to detect contour (higher values will include more pixels)"),
0.0, 1.0, 0.92,
G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_MAX_GROW,
g_param_spec_int ("max-grow",
_("Maximum growing size"),
_("Maximum number of pixels grown under the line art"),
1, 100, 3,
G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_SPLINE_MAX_LEN,
g_param_spec_int ("spline-max-length",
_("Maximum curved closing length"),
_("Maximum curved length (in pixels) to close the line art"),
1, 1000, 60,
G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_SEGMENT_MAX_LEN,
g_param_spec_int ("segment-max-length",
_("Maximum straight closing length"),
_("Maximum straight length (in pixels) to close the line art"),
1, 1000, 20,
G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
}
static void
gimp_line_art_init (GimpLineArt *line_art)
{
line_art->priv = gimp_line_art_get_instance_private (line_art);
}
static void
gimp_line_art_finalize (GObject *object)
{
GimpLineArt *line_art = GIMP_LINE_ART (object);
if (line_art->priv->input)
{
if (GIMP_IS_IMAGE (line_art->priv->input))
g_signal_handlers_disconnect_by_data (gimp_image_get_projection (GIMP_IMAGE (line_art->priv->input)),
line_art);
else
g_signal_handlers_disconnect_by_data (line_art->priv->input,
line_art);
}
if (line_art->priv->async)
{
/* we cancel the async, but don't wait for it to finish, since
* it can't actually be interrupted. instead
* gimp_bucket_fill_compute_line_art_cb() bails if the async has
* been canceled, to avoid accessing the dead tool.
*/
gimp_cancelable_cancel (GIMP_CANCELABLE (line_art->priv->async));
g_clear_object (&line_art->priv->async);
}
g_clear_object (&line_art->priv->closed);
g_clear_pointer (&line_art->priv->distmap, g_free);
}
static void
gimp_line_art_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpLineArt *line_art = GIMP_LINE_ART (object);
switch (property_id)
{
case PROP_SELECT_TRANSPARENT:
line_art->priv->select_transparent = g_value_get_boolean (value);
gimp_line_art_compute (line_art);
break;
case PROP_MAX_GROW:
line_art->priv->max_grow = g_value_get_int (value);
break;
case PROP_THRESHOLD:
line_art->priv->threshold = g_value_get_double (value);
gimp_line_art_compute (line_art);
break;
case PROP_SPLINE_MAX_LEN:
line_art->priv->spline_max_len = g_value_get_int (value);
gimp_line_art_compute (line_art);
break;
case PROP_SEGMENT_MAX_LEN:
line_art->priv->segment_max_len = g_value_get_int (value);
gimp_line_art_compute (line_art);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_line_art_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GimpLineArt *line_art = GIMP_LINE_ART (object);
switch (property_id)
{
case PROP_SELECT_TRANSPARENT:
g_value_set_boolean (value, line_art->priv->select_transparent);
break;
case PROP_MAX_GROW:
g_value_set_int (value, line_art->priv->max_grow);
break;
case PROP_THRESHOLD:
g_value_set_double (value, line_art->priv->threshold);
break;
case PROP_SPLINE_MAX_LEN:
g_value_set_int (value, line_art->priv->spline_max_len);
break;
case PROP_SEGMENT_MAX_LEN:
g_value_set_int (value, line_art->priv->segment_max_len);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
/* Public functions */
GimpLineArt *
gimp_line_art_new (void)
{
return g_object_new (GIMP_TYPE_LINE_ART,
NULL);
}
void
gimp_line_art_set_input (GimpLineArt *line_art,
GimpPickable *pickable)
{
if (line_art->priv->input)
{
if (GIMP_IS_IMAGE (line_art->priv->input))
g_signal_handlers_disconnect_by_data (gimp_image_get_projection (GIMP_IMAGE (line_art->priv->input)),
line_art);
else
g_signal_handlers_disconnect_by_data (line_art->priv->input,
line_art);
}
line_art->priv->input = pickable;
gimp_line_art_compute (line_art);
if (pickable)
{
if (GIMP_IS_IMAGE (pickable))
g_signal_connect (gimp_image_get_projection (GIMP_IMAGE (pickable)), "rendered",
G_CALLBACK (gimp_line_art_projection_rendered),
line_art);
else if (GIMP_IS_DRAWABLE (pickable))
g_signal_connect (pickable, "painted",
G_CALLBACK (gimp_line_art_drawable_painted),
line_art);
else
g_return_if_reached ();
}
}
void
gimp_line_art_freeze (GimpLineArt *line_art)
{
g_return_if_fail (! line_art->priv->frozen);
line_art->priv->frozen = TRUE;
line_art->priv->compute_after_thaw = FALSE;
}
void
gimp_line_art_thaw (GimpLineArt *line_art)
{
g_return_if_fail (line_art->priv->frozen);
line_art->priv->frozen = FALSE;
if (line_art->priv->compute_after_thaw)
{
gimp_line_art_compute (line_art);
line_art->priv->compute_after_thaw = FALSE;
}
}
GeglBuffer *
gimp_line_art_get (GimpLineArt *line_art,
gfloat **distmap)
{
g_return_val_if_fail (line_art->priv->input, NULL);
if (line_art->priv->async)
{
gimp_waitable_wait (GIMP_WAITABLE (line_art->priv->async));
}
else if (! line_art->priv->closed)
{
gimp_line_art_compute (line_art);
if (line_art->priv->async)
gimp_waitable_wait (GIMP_WAITABLE (line_art->priv->async));
}
g_return_val_if_fail (line_art->priv->closed, NULL);
if (distmap)
*distmap = line_art->priv->distmap;
return line_art->priv->closed;
}
/* Functions for asynchronous computation. */
static void
gimp_line_art_compute (GimpLineArt *line_art)
{
if (line_art->priv->frozen)
{
line_art->priv->compute_after_thaw = TRUE;
return;
}
if (line_art->priv->async)
{
gimp_cancelable_cancel (GIMP_CANCELABLE (line_art->priv->async));
g_clear_object (&line_art->priv->async);
}
g_clear_object (&line_art->priv->closed);
g_clear_pointer (&line_art->priv->distmap, g_free);
if (line_art->priv->input)
{
/* gimp_line_art_prepare_async() will flush the pickable, which
* may trigger this signal handler, and will leak a line art (as
* line_art->priv->async has not been set yet).
*/
if (GIMP_IS_IMAGE (line_art->priv->input))
g_signal_handlers_block_by_func (gimp_image_get_projection (GIMP_IMAGE (line_art->priv->input)),
G_CALLBACK (gimp_line_art_projection_rendered),
line_art);
else
g_signal_handlers_block_by_func (line_art->priv->input,
G_CALLBACK (gimp_line_art_drawable_painted),
line_art);
line_art->priv->async = gimp_line_art_prepare_async (line_art, +1);
if (GIMP_IS_IMAGE (line_art->priv->input))
g_signal_handlers_unblock_by_func (gimp_image_get_projection (GIMP_IMAGE (line_art->priv->input)),
G_CALLBACK (gimp_line_art_projection_rendered),
line_art);
else
g_signal_handlers_unblock_by_func (line_art->priv->input,
G_CALLBACK (gimp_line_art_drawable_painted),
line_art);
gimp_async_add_callback_for_object (line_art->priv->async,
(GimpAsyncCallback) gimp_line_art_compute_cb,
line_art, line_art);
}
}
static void
gimp_line_art_compute_cb (GimpAsync *async,
GimpLineArt *line_art)
{
if (gimp_async_is_canceled (async))
return;
if (gimp_async_is_finished (async))
{
LineArtResult *result;
result = gimp_async_get_result (async);
line_art->priv->closed = g_object_ref (result->closed);
line_art->priv->distmap = result->distmap;
result->distmap = NULL;
}
g_clear_object (&line_art->priv->async);
}
static GimpAsync *
gimp_line_art_prepare_async (GimpLineArt *line_art,
gint priority)
{
GeglBuffer *buffer;
GimpAsync *async;
LineArtData *data;
g_return_val_if_fail (GIMP_IS_PICKABLE (line_art->priv->input), NULL);
gimp_pickable_flush (line_art->priv->input);
buffer = gegl_buffer_dup (gimp_pickable_get_buffer (line_art->priv->input));
data = line_art_data_new (buffer, line_art);
g_object_unref (buffer);
async = gimp_parallel_run_async_full (
priority,
(GimpParallelRunAsyncFunc) gimp_line_art_prepare_async_func,
data, (GDestroyNotify) line_art_data_free);
return async;
}
static void
gimp_line_art_prepare_async_func (GimpAsync *async,
LineArtData *data)
{
GeglBuffer *closed;
gfloat *distmap;
gboolean has_alpha;
gboolean select_transparent = FALSE;
has_alpha = babl_format_has_alpha (gegl_buffer_get_format (data->buffer));
if (has_alpha)
{
if (data->select_transparent)
{
/* don't select transparent regions if there are no fully
* transparent pixels.
*/
GeglBufferIterator *gi;
gi = gegl_buffer_iterator_new (data->buffer, 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;
if (gimp_async_is_canceled (async))
{
gegl_buffer_iterator_stop (gi);
gimp_async_abort (async);
line_art_data_free (data);
return;
}
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);
}
}
/* 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();
closed = gimp_line_art_close (data->buffer,
select_transparent,
data->threshold,
data->spline_max_len,
data->segment_max_len,
/*minimal_lineart_area,*/
5,
/*normal_estimate_mask_size,*/
5,
/*end_point_rate,*/
0.85,
/*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,
&distmap);
GIMP_TIMER_END("close line-art");
gimp_async_finish_full (async,
line_art_result_new (closed, distmap),
(GDestroyNotify) line_art_result_free);
line_art_data_free (data);
}
static LineArtData *
line_art_data_new (GeglBuffer *buffer,
GimpLineArt *line_art)
{
LineArtData *data = g_slice_new (LineArtData);
data->buffer = g_object_ref (buffer);
data->select_transparent = line_art->priv->select_transparent;
data->threshold = line_art->priv->threshold;
data->spline_max_len = line_art->priv->spline_max_len;
data->segment_max_len = line_art->priv->segment_max_len;
return data;
}
static void
line_art_data_free (LineArtData *data)
{
g_object_unref (data->buffer);
g_slice_free (LineArtData, data);
}
static LineArtResult *
line_art_result_new (GeglBuffer *closed,
gfloat *distmap)
{
LineArtResult *data;
data = g_slice_new (LineArtResult);
data->closed = closed;
data->distmap = distmap;
return data;
}
static void
line_art_result_free (LineArtResult *data)
{
g_object_unref (data->closed);
g_clear_pointer (&data->distmap, g_free);
g_slice_free (LineArtResult, data);
}
static void
gimp_line_art_projection_rendered (GimpProjection *proj,
GimpLineArt *line_art)
{
gimp_line_art_compute (line_art);
}
static void
gimp_line_art_drawable_painted (GimpDrawable *drawable,
GimpLineArt *line_art)
{
gimp_line_art_compute (line_art);
}
/* All actual computation functions. */
/**
* gimp_lineart_close:
* gimp_line_art_close:
* @buffer: the input #GeglBuffer.
* @select_transparent: whether we binarize the alpha channel or the
* luminosity.
* @stroke_threshold: [0-1] threshold value for detecting stroke pixels
* (higher values will detect more stroke pixels).
* @spline_max_length: the maximum length for creating splines between
* end points.
* @segment_max_length: the maximum length for creating segments
* between end points. Unlike splines, segments
* are straight lines.
* @minimal_lineart_area: the minimum size in number pixels for area to
* be considered as line art.
* @normal_estimate_mask_size:
* @end_point_rate: threshold to estimate if a curvature is an end-point
* in [0-1] range value.
* @spline_max_length: the maximum length for creating splines between
* end points.
* @spline_max_angle: the maximum angle between end point normals for
* creating splines between them.
* @end_point_connectivity:
@ -182,9 +792,6 @@ static void gimp_edgelset_next8 (const GeglBuffer *buffer,
* @created_regions_significant_area:
* @created_regions_minimum_area:
* @small_segments_from_spline_sources:
* @segments_max_length: the maximum length for creating segments
* between end points. Unlike splines, segments
* are straight lines.
* @closed_distmap: a distance map of the closed line art pixels.
*
* Creates a binarized version of the strokes of @buffer, detected either
@ -204,23 +811,23 @@ static void gimp_edgelset_next8 (const GeglBuffer *buffer,
* newly allocated float buffer is returned, which can be used
* for overflowing created masks later.
*/
GeglBuffer *
gimp_lineart_close (GeglBuffer *buffer,
gboolean select_transparent,
gfloat stroke_threshold,
gint minimal_lineart_area,
gint normal_estimate_mask_size,
gfloat end_point_rate,
gint spline_max_length,
gfloat spline_max_angle,
gint end_point_connectivity,
gfloat spline_roundness,
gboolean allow_self_intersections,
gint created_regions_significant_area,
gint created_regions_minimum_area,
gboolean small_segments_from_spline_sources,
gint segments_max_length,
gfloat **closed_distmap)
static GeglBuffer *
gimp_line_art_close (GeglBuffer *buffer,
gboolean select_transparent,
gdouble stroke_threshold,
gint spline_max_length,
gint segment_max_length,
gint minimal_lineart_area,
gint normal_estimate_mask_size,
gfloat end_point_rate,
gfloat spline_max_angle,
gint end_point_connectivity,
gfloat spline_roundness,
gboolean allow_self_intersections,
gint created_regions_significant_area,
gint created_regions_minimum_area,
gboolean small_segments_from_spline_sources,
gfloat **closed_distmap)
{
const Babl *gray_format;
gfloat *normals;
@ -421,7 +1028,7 @@ gimp_lineart_close (GeglBuffer *buffer,
{
GArray *segment = gimp_lineart_line_segment_until_hit (closed, *point,
pair2normal (*point, normals, width),
segments_max_length);
segment_max_length);
if (segment->len &&
! gimp_lineart_curve_creates_region (closed, segment,
@ -487,8 +1094,6 @@ gimp_lineart_close (GeglBuffer *buffer,
return closed;
}
/* Private functions */
static void
gimp_lineart_denoise (GeglBuffer *buffer,
int minimum_area)

View File

@ -22,22 +22,42 @@
#define __GIMP_LINEART__
GeglBuffer * gimp_lineart_close (GeglBuffer *buffer,
gboolean select_transparent,
gfloat stroke_threshold,
gint minimal_lineart_area,
gint normal_estimate_mask_size,
gfloat end_point_rate,
gint spline_max_length,
gfloat spline_max_angle,
gint end_point_connectivity,
gfloat spline_roundness,
gboolean allow_self_intersections,
gint created_regions_significant_area,
gint created_regions_minimum_area,
gboolean small_segments_from_spline_sources,
gint segments_max_length,
gfloat **lineart_distmap);
#include "gimpobject.h"
#define GIMP_TYPE_LINE_ART (gimp_line_art_get_type ())
#define GIMP_LINE_ART(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LINE_ART, GimpLineArt))
#define GIMP_LINE_ART_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LINE_ART, GimpLineArtClass))
#define GIMP_IS_LINE_ART(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LINE_ART))
#define GIMP_IS_LINE_ART_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LINE_ART))
#define GIMP_LINE_ART_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LINE_ART, GimpLineArtClass))
typedef struct _GimpLineArtClass GimpLineArtClass;
typedef struct _GimpLineArtPrivate GimpLineArtPrivate;
struct _GimpLineArt
{
GimpObject parent_instance;
GimpLineArtPrivate *priv;
};
struct _GimpLineArtClass
{
GimpObjectClass parent_class;
};
GType gimp_line_art_get_type (void) G_GNUC_CONST;
GimpLineArt * gimp_line_art_new (void);
void gimp_line_art_set_input (GimpLineArt *line_art,
GimpPickable *pickable);
void gimp_line_art_freeze (GimpLineArt *line_art);
void gimp_line_art_thaw (GimpLineArt *line_art);
GeglBuffer * gimp_line_art_get (GimpLineArt *line_art,
gfloat **distmap);
#endif /* __GIMP_LINEART__ */

View File

@ -38,15 +38,6 @@
#include "gimppickable-contiguous-region.h"
typedef struct
{
GeglBuffer *buffer;
gboolean select_transparent;
gfloat stroke_threshold;
gint segment_max_length;
gint spline_max_length;
} LineArtData;
typedef struct
{
gint x;
@ -114,17 +105,6 @@ static void find_contiguous_region (GeglBuffer *src_buffer,
gint y,
const gfloat *col);
static LineArtData * line_art_data_new (GeglBuffer *buffer,
gboolean select_transparent,
gfloat stroke_threshold,
gint segment_max_length,
gint spline_max_length);
static void line_art_data_free (LineArtData *data);
static GimpPickableLineArtAsyncResult *
line_art_result_new (GeglBuffer *line_art,
gfloat *distmap);
static void line_art_result_free (GimpPickableLineArtAsyncResult
*data);
static void line_art_queue_pixel (GQueue *queue,
gint x,
gint y,
@ -133,220 +113,46 @@ static void line_art_queue_pixel (GQueue *queue,
/* public functions */
static void
gimp_pickable_contiguous_region_prepare_line_art_async_func (GimpAsync *async,
LineArtData *data)
{
GeglBuffer *lineart;
gfloat *distmap;
gboolean has_alpha;
gboolean select_transparent = FALSE;
has_alpha = babl_format_has_alpha (gegl_buffer_get_format (data->buffer));
if (has_alpha)
{
if (data->select_transparent)
{
/* don't select transparent regions if there are no fully
* transparent pixels.
*/
GeglBufferIterator *gi;
gi = gegl_buffer_iterator_new (data->buffer, 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;
if (gimp_async_is_canceled (async))
{
gegl_buffer_iterator_stop (gi);
gimp_async_abort (async);
line_art_data_free (data);
return;
}
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);
}
}
/* 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 (data->buffer,
select_transparent,
data->stroke_threshold,
/*minimal_lineart_area,*/
5,
/*normal_estimate_mask_size,*/
5,
/*end_point_rate,*/
0.85,
data->spline_max_length,
/*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,
data->segment_max_length,
&distmap);
GIMP_TIMER_END("close line-art");
gimp_async_finish_full (async,
line_art_result_new (lineart, distmap),
(GDestroyNotify) line_art_result_free);
line_art_data_free (data);
}
GeglBuffer *
gimp_pickable_contiguous_region_prepare_line_art (GimpPickable *pickable,
gboolean select_transparent,
gfloat stroke_threshold,
gint segment_max_length,
gint spline_max_length,
gfloat **distmap)
{
GimpAsync *async;
LineArtData *data;
GimpPickableLineArtAsyncResult *result;
GeglBuffer *lineart;
g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
gimp_pickable_flush (pickable);
async = gimp_async_new ();
data = line_art_data_new (gimp_pickable_get_buffer (pickable),
select_transparent, stroke_threshold,
segment_max_length, spline_max_length);
gimp_pickable_contiguous_region_prepare_line_art_async_func (async, data);
result = gimp_async_get_result (async);
lineart = g_object_ref (result->line_art);
*distmap = result->distmap;
result->distmap = NULL;
g_object_unref (async);
return lineart;
}
GimpAsync *
gimp_pickable_contiguous_region_prepare_line_art_async (GimpPickable *pickable,
gboolean select_transparent,
gfloat stroke_threshold,
gint segment_max_length,
gint spline_max_length,
gint priority)
{
GeglBuffer *buffer;
GimpAsync *async;
LineArtData *data;
g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
gimp_pickable_flush (pickable);
buffer = gegl_buffer_dup (gimp_pickable_get_buffer (pickable));
data = line_art_data_new (buffer, select_transparent, stroke_threshold,
segment_max_length, spline_max_length);
g_object_unref (buffer);
async = gimp_parallel_run_async_full (
priority,
(GimpParallelRunAsyncFunc)
gimp_pickable_contiguous_region_prepare_line_art_async_func,
data,
(GDestroyNotify) line_art_data_free);
return async;
}
GeglBuffer *
gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
GeglBuffer *line_art,
gfloat *distmap,
GimpLineArt *line_art,
gboolean antialias,
gfloat threshold,
gboolean select_transparent,
GimpSelectCriterion select_criterion,
gboolean diagonal_neighbors,
gfloat stroke_threshold,
gint flooding_max,
gint segment_max_length,
gint spline_max_length,
gint x,
gint y)
{
GeglBuffer *src_buffer;
GeglBuffer *mask_buffer;
const Babl *format;
gfloat *distmap = NULL;
GeglRectangle extent;
gint n_components;
gboolean has_alpha;
gfloat start_col[MAX_CHANNELS];
gboolean smart_line_art = FALSE;
gboolean free_line_art = FALSE;
gint line_art_max_grow;
g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
if (select_criterion == GIMP_SELECT_CRITERION_LINE_ART)
{
g_return_val_if_fail ((line_art && distmap) ||
(! line_art && ! distmap),
NULL);
if (line_art == NULL)
if (! line_art)
{
/* 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).
*/
line_art = gimp_pickable_contiguous_region_prepare_line_art (pickable, select_transparent,
stroke_threshold,
segment_max_length,
spline_max_length,
&distmap);
line_art = gimp_line_art_new ();
gimp_line_art_set_input (line_art, pickable);
free_line_art = TRUE;
}
src_buffer = line_art;
src_buffer = gimp_line_art_get (line_art, &distmap);
g_return_val_if_fail (src_buffer && distmap, NULL);
smart_line_art = TRUE;
antialias = FALSE;
@ -427,8 +233,8 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
*/
gfloat *mask;
GQueue *queue = g_queue_new ();
gint width = gegl_buffer_get_width (line_art);
gint height = gegl_buffer_get_height (line_art);
gint width = gegl_buffer_get_width (src_buffer);
gint height = gegl_buffer_get_height (src_buffer);
gint nx, ny;
GIMP_TIMER_START();
@ -520,6 +326,9 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
}
}
g_object_get (line_art,
"max-grow", &line_art_max_grow,
NULL);
while (! g_queue_is_empty (queue))
{
BorderPixel *c = g_queue_pop_head (queue);
@ -527,7 +336,7 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
if (mask[c->x + c->y * width] != 1.0)
{
mask[c->x + c->y * width] = 1.0;
if (c->level >= flooding_max)
if (c->level >= line_art_max_grow)
/* Do not overflood under line arts. */
continue;
if (c->x > 0)
@ -600,10 +409,7 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
GIMP_TIMER_END("watershed line art");
if (free_line_art)
{
g_object_unref (src_buffer);
g_free (distmap);
}
g_clear_object (&line_art);
}
return mask_buffer;
@ -1150,54 +956,6 @@ find_contiguous_region (GeglBuffer *src_buffer,
#endif
}
static LineArtData *
line_art_data_new (GeglBuffer *buffer,
gboolean select_transparent,
gfloat stroke_threshold,
gint segment_max_length,
gint spline_max_length)
{
LineArtData *data = g_slice_new (LineArtData);
data->buffer = g_object_ref (buffer);
data->select_transparent = select_transparent;
data->stroke_threshold = stroke_threshold;
data->segment_max_length = segment_max_length;
data->spline_max_length = spline_max_length;
return data;
}
static void
line_art_data_free (LineArtData *data)
{
g_object_unref (data->buffer);
g_slice_free (LineArtData, data);
}
static GimpPickableLineArtAsyncResult *
line_art_result_new (GeglBuffer *line_art,
gfloat *distmap)
{
GimpPickableLineArtAsyncResult *data;
data = g_slice_new (GimpPickableLineArtAsyncResult);
data->line_art = line_art;
data->distmap = distmap;
return data;
}
static void
line_art_result_free (GimpPickableLineArtAsyncResult *data)
{
g_object_unref (data->line_art);
g_clear_pointer (&data->distmap, g_free);
g_slice_free (GimpPickableLineArtAsyncResult, data);
}
static void
line_art_queue_pixel (GQueue *queue,
gint x,

View File

@ -18,37 +18,14 @@
#ifndef __GIMP_PICKABLE_CONTIGUOUS_REGION_H__
#define __GIMP_PICKABLE_CONTIGUOUS_REGION_H__
typedef struct
{
GeglBuffer *line_art;
gfloat *distmap;
} GimpPickableLineArtAsyncResult;
GeglBuffer * gimp_pickable_contiguous_region_prepare_line_art (GimpPickable *pickable,
gboolean select_transparent,
gfloat stroke_threshold,
gint segment_max_length,
gint spline_max_length,
gfloat **distmap);
GimpAsync * gimp_pickable_contiguous_region_prepare_line_art_async (GimpPickable *pickable,
gboolean select_transparent,
gfloat stroke_threshold,
gint segment_max_length,
gint spline_max_length,
gint priority);
GeglBuffer * gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
GeglBuffer *line_art,
gfloat *line_art_distmap,
GimpLineArt *line_art,
gboolean antialias,
gfloat threshold,
gboolean select_transparent,
GimpSelectCriterion select_criterion,
gboolean diagonal_neighbors,
gfloat line_art_stroke_threshold,
gint line_art_max_grow,
gint line_art_segment_max_length,
gint line_art_spline_max_length,
gint x,
gint y);

View File

@ -165,13 +165,12 @@ 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, NULL, NULL, 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,
GIMP_PDB_CONTEXT (context)->sample_merged,
GIMP_PDB_CONTEXT (context)->diagonal_neighbors,
0.92, 3, 20, 60, /* TODO */
x, y);
}
else

View File

@ -61,15 +61,10 @@
struct _GimpBucketFillToolPrivate
{
GimpAsync *async;
GeglBuffer *line_art;
gfloat *distmap;
GimpLineArt *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 */
@ -129,20 +124,17 @@ static void gimp_bucket_fill_tool_cursor_update (GimpTool *t
GdkModifierType state,
GimpDisplay *display);
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_reset_line_art (GimpBucketFillTool *tool,
GimpBucketFillOptions *options);
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_projection_rendered (GimpProjection *proj,
GimpBucketFillTool *tool);
static void gimp_bucket_fill_tool_drawable_painted (GimpDrawable *drawable,
GimpBucketFillTool *tool);
G_DEFINE_TYPE_WITH_PRIVATE (GimpBucketFillTool, gimp_bucket_fill_tool, GIMP_TYPE_COLOR_TOOL)
@ -211,9 +203,28 @@ 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;
GimpLineArt *line_art;
G_OBJECT_CLASS (parent_class)->constructed (object);
line_art = gimp_line_art_new ();
g_object_bind_property (options, "fill-transparent",
line_art, "select-transparent",
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
g_object_bind_property (options, "line-art-threshold",
line_art, "threshold",
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
g_object_bind_property (options, "line-art-max-grow",
line_art, "max-grow",
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
g_object_bind_property (options, "line-art-spline-max-len",
line_art, "spline-max-length",
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
g_object_bind_property (options, "line-art-segment-max-len",
line_art, "segment-max-length",
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
GIMP_BUCKET_FILL_TOOL (tool)->priv->line_art = line_art;
/* 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.
@ -235,38 +246,16 @@ gimp_bucket_fill_tool_finalize (GObject *object)
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);
if (tool->priv->async)
{
/* we cancel the async, but don't wait for it to finish, since
* it can't actually be interrupted. instead
* gimp_bucket_fill_compute_line_art_cb() bails if the async has
* been canceled, to avoid accessing the dead tool.
*/
gimp_cancelable_cancel (GIMP_CANCELABLE (tool->priv->async));
g_clear_object (&tool->priv->async);
}
g_clear_object (&tool->priv->line_art);
g_clear_pointer (&tool->priv->distmap, g_free);
if (image)
{
g_signal_handlers_disconnect_by_data (image, tool);
g_signal_handlers_disconnect_by_data (gimp_image_get_projection (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 (options, tool);
g_signal_handlers_disconnect_by_data (context, tool);
G_OBJECT_CLASS (parent_class)->finalize (object);
@ -321,7 +310,7 @@ gimp_bucket_fill_tool_start (GimpBucketFillTool *tool,
g_return_if_fail (! tool->priv->filter);
tool->priv->fill_in_progress = TRUE;
gimp_line_art_freeze (tool->priv->line_art);
GIMP_TOOL (tool)->display = display;
GIMP_TOOL (tool)->drawable = drawable;
@ -348,12 +337,6 @@ gimp_bucket_fill_tool_start (GimpBucketFillTool *tool,
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));
}
}
static void
@ -369,8 +352,6 @@ gimp_bucket_fill_tool_preview (GimpBucketFillTool *tool,
if (tool->priv->filter)
{
GeglBuffer *fill = NULL;
GeglBuffer *line_art = NULL;
gfloat *distmap = NULL;
gdouble x = coords->x;
gdouble y = coords->y;
@ -384,29 +365,16 @@ gimp_bucket_fill_tool_preview (GimpBucketFillTool *tool,
y -= (gdouble) off_y;
}
if (options->fill_criterion == GIMP_SELECT_CRITERION_LINE_ART)
{
line_art = g_object_ref (tool->priv->line_art);
distmap = tool->priv->distmap;
}
fill = gimp_drawable_get_bucket_fill_buffer (drawable,
line_art, distmap,
tool->priv->line_art,
fill_options,
options->fill_transparent,
options->fill_criterion,
options->threshold / 255.0,
options->sample_merged,
options->diagonal_neighbors,
options->line_art_threshold,
options->line_art_max_grow,
options->line_art_segment_max_len,
options->line_art_spline_max_len,
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,
@ -427,12 +395,6 @@ gimp_bucket_fill_tool_commit (GimpBucketFillTool *tool)
{
if (tool->priv->filter)
{
GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
/* Make sure the drawable will signal being painted. */
if (! options->sample_merged)
tool->priv->fill_in_progress = FALSE;
gimp_drawable_filter_commit (tool->priv->filter,
GIMP_PROGRESS (tool), FALSE);
gimp_image_flush (gimp_display_get_image (GIMP_TOOL (tool)->display));
@ -455,8 +417,7 @@ gimp_bucket_fill_tool_halt (GimpBucketFillTool *tool)
}
g_clear_object (&tool->priv->fill_mask);
tool->priv->fill_in_progress = FALSE;
tool->priv->compute_line_art_after_fill = FALSE;
gimp_line_art_thaw (tool->priv->line_art);
GIMP_TOOL (tool)->display = NULL;
GIMP_TOOL (tool)->drawable = NULL;
@ -590,8 +551,6 @@ gimp_bucket_fill_tool_motion (GimpTool *tool,
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,
@ -752,92 +711,6 @@ 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_cb (GimpAsync *async,
GimpBucketFillTool *tool)
{
if (gimp_async_is_canceled (async))
return;
if (gimp_async_is_finished (async))
{
GimpPickableLineArtAsyncResult *result;
result = gimp_async_get_result (async);
tool->priv->line_art = g_object_ref (result->line_art);
tool->priv->distmap = result->distmap;
result->distmap = NULL;
}
g_clear_object (&tool->priv->async);
}
static void
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;
}
if (tool->priv->async)
{
gimp_cancelable_cancel (GIMP_CANCELABLE (tool->priv->async));
g_clear_object (&tool->priv->async);
}
g_clear_object (&tool->priv->line_art);
g_clear_pointer (&tool->priv->distmap, g_free);
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)
{
/* gimp_pickable_contiguous_region_prepare_line_art_async()
* will flush the pickable, which may trigger this signal
* handler, and will leak a line art (as tool->priv->async has
* not been set yet.
*/
g_signal_handlers_block_by_func (gimp_image_get_projection (GIMP_IMAGE (image)),
G_CALLBACK (gimp_bucket_fill_tool_projection_rendered),
tool);
tool->priv->async =
gimp_pickable_contiguous_region_prepare_line_art_async (
pickable,
options->fill_transparent,
options->line_art_threshold,
options->line_art_segment_max_len,
options->line_art_spline_max_len,
+1);
g_signal_handlers_unblock_by_func (gimp_image_get_projection (GIMP_IMAGE (image)),
G_CALLBACK (gimp_bucket_fill_tool_projection_rendered),
tool);
gimp_async_add_callback_for_object (
tool->priv->async,
(GimpAsyncCallback) gimp_bucket_fill_compute_line_art_cb,
tool,
tool);
}
g_clear_object (&image);
g_clear_object (&drawable);
}
}
static gboolean
gimp_bucket_fill_tool_connect_handlers (gpointer data)
{
@ -856,13 +729,6 @@ gimp_bucket_fill_tool_connect_handlers (gpointer data)
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 (options, "notify::line-art-threshold",
G_CALLBACK (gimp_bucket_fill_tool_options_notified),
tool);
g_signal_connect (options, "notify::fill-mode",
G_CALLBACK (gimp_bucket_fill_tool_options_notified),
tool);
@ -882,13 +748,10 @@ 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, "line-art-threshold") ||
! strcmp (pspec->name, "sample-merged")) &&
options->fill_criterion == GIMP_SELECT_CRITERION_LINE_ART)
if (! strcmp (pspec->name, "fill-criterion") ||
! strcmp (pspec->name, "sample-merged"))
{
gimp_bucket_fill_compute_line_art (tool);
gimp_bucket_fill_reset_line_art (tool, options);
}
else if (! strcmp (pspec->name, "fill-mode"))
{
@ -917,6 +780,49 @@ gimp_bucket_fill_tool_options_notified (GimpBucketFillOptions *options,
}
}
static void
gimp_bucket_fill_reset_line_art (GimpBucketFillTool *tool,
GimpBucketFillOptions *options)
{
GimpImage *prev_image = g_weak_ref_get (&tool->priv->cached_image);
GimpContext *context = gimp_get_user_context (GIMP_CONTEXT (options)->gimp);
GimpImage *image = gimp_context_get_image (context);
if (prev_image)
{
g_signal_handlers_disconnect_by_data (prev_image, tool);
g_object_unref (prev_image);
}
g_weak_ref_set (&tool->priv->cached_image, image ? image : NULL);
g_weak_ref_set (&tool->priv->cached_drawable, NULL);
if (image && options->fill_criterion == GIMP_SELECT_CRITERION_LINE_ART)
{
GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
GimpDrawable *drawable = gimp_image_get_active_drawable (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);
g_weak_ref_set (&tool->priv->cached_drawable, drawable ? drawable : NULL);
if (options->sample_merged)
gimp_line_art_set_input (tool->priv->line_art, GIMP_PICKABLE (image));
else if (drawable)
gimp_line_art_set_input (tool->priv->line_art, GIMP_PICKABLE (drawable));
else
gimp_line_art_set_input (tool->priv->line_art, NULL);
}
else
{
gimp_line_art_set_input (tool->priv->line_art, NULL);
}
}
static void
gimp_bucket_fill_tool_image_changed (GimpContext *context,
GimpImage *image,
@ -926,37 +832,32 @@ gimp_bucket_fill_tool_image_changed (GimpContext *context,
if (image != prev_image)
{
GimpImage *prev_drawable = g_weak_ref_get (&tool->priv->cached_drawable);
g_clear_object (&tool->priv->line_art);
g_clear_pointer (&tool->priv->distmap, g_free);
if (prev_image)
{
g_signal_handlers_disconnect_by_data (prev_image, tool);
g_signal_handlers_disconnect_by_data (gimp_image_get_projection (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)
{
GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
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);
g_signal_connect (gimp_image_get_projection (image), "rendered",
G_CALLBACK (gimp_bucket_fill_tool_projection_rendered),
tool);
gimp_bucket_fill_tool_drawable_changed (image, tool);
if (options->sample_merged)
gimp_line_art_set_input (tool->priv->line_art,
GIMP_PICKABLE (image));
}
else
{
gimp_line_art_set_input (tool->priv->line_art, NULL);
}
}
if (prev_image)
@ -972,37 +873,13 @@ gimp_bucket_fill_tool_drawable_changed (GimpImage *image,
if (drawable != prev_drawable)
{
if (prev_drawable)
g_signal_handlers_disconnect_by_data (prev_drawable, tool);
GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
g_weak_ref_set (&tool->priv->cached_drawable, drawable ? drawable : NULL);
if (drawable)
g_signal_connect (drawable, "painted",
G_CALLBACK (gimp_bucket_fill_tool_drawable_painted),
tool);
gimp_bucket_fill_compute_line_art (tool);
if (! options->sample_merged)
gimp_line_art_set_input (tool->priv->line_art,
drawable ? GIMP_PICKABLE (drawable) : NULL);
}
if (prev_drawable)
g_object_unref (prev_drawable);
}
static void
gimp_bucket_fill_tool_projection_rendered (GimpProjection *proj,
GimpBucketFillTool *tool)
{
GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
if (options->sample_merged)
gimp_bucket_fill_compute_line_art (tool);
}
static void
gimp_bucket_fill_tool_drawable_painted (GimpDrawable *drawable,
GimpBucketFillTool *tool)
{
GimpBucketFillOptions *options = GIMP_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
if (! options->sample_merged)
gimp_bucket_fill_compute_line_art (tool);
}

View File

@ -122,12 +122,11 @@ gimp_fuzzy_select_tool_get_mask (GimpRegionSelectTool *region_select,
pickable = GIMP_PICKABLE (image);
}
return gimp_pickable_contiguous_region_by_seed (pickable, NULL, NULL,
return gimp_pickable_contiguous_region_by_seed (pickable, NULL,
sel_options->antialias,
options->threshold / 255.0,
options->select_transparent,
options->select_criterion,
options->diagonal_neighbors,
0.92, 3, 20, 60, /* TODO */
x, y);
}

View File

@ -169,13 +169,12 @@ HELP
if (gimp_fill_options_set_by_fill_type (options, context,
fill_type, error))
{
gimp_drawable_bucket_fill (drawable, NULL, NULL, 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,
GIMP_PDB_CONTEXT (context)->sample_merged,
GIMP_PDB_CONTEXT (context)->diagonal_neighbors,
0.92, 3, 20, 60, /* TODO */
x, y);
}
else