Commit 3467acf0 authored by Jehan's avatar Jehan

app: replace gegl:watershed-transform with custom algorithm.

We don't really need to flow every line art pixel and this new
implementation is simpler (because we don't actually need over-featured
watershedding), and a lot lot faster, making the line art bucket fill
now very reactive.
For this, I am keeping the computed distance map, as well as local
thickness map around to be used when flooding the line art pixels
(basically I try to flood half the stroke thickness).

Note that there are still some issues with this new implementation as it
doesn't properly flood yet created (i.e. invisible) splines and
segments, and in particular the ones between 2 colored sections. I am
going to fix this next.
parent 8304d2b5
......@@ -516,7 +516,7 @@ gimp_channel_select_fuzzy (GimpChannel *channel,
pickable = GIMP_PICKABLE (drawable);
add_on = gimp_pickable_contiguous_region_by_seed (pickable,
NULL,
NULL, NULL, NULL,
antialias,
threshold,
select_transparent,
......
......@@ -50,6 +50,8 @@
void
gimp_drawable_bucket_fill (GimpDrawable *drawable,
GeglBuffer *line_art,
gfloat *distmap,
gfloat *thickmap,
GimpFillOptions *options,
gboolean fill_transparent,
GimpSelectCriterion fill_criterion,
......@@ -72,7 +74,8 @@ 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, options,
buffer = gimp_drawable_get_bucket_fill_buffer (drawable, line_art,
distmap, thickmap, options,
fill_transparent, fill_criterion,
threshold, sample_merged,
diagonal_neighbors,
......@@ -134,6 +137,8 @@ gimp_drawable_bucket_fill (GimpDrawable *drawable,
GeglBuffer *
gimp_drawable_get_bucket_fill_buffer (GimpDrawable *drawable,
GeglBuffer *line_art,
gfloat *distmap,
gfloat *thickmap,
GimpFillOptions *options,
gboolean fill_transparent,
GimpSelectCriterion fill_criterion,
......@@ -196,7 +201,7 @@ gimp_drawable_get_bucket_fill_buffer (GimpDrawable *drawable,
* contiguous region.
*/
new_mask = gimp_pickable_contiguous_region_by_seed (pickable,
line_art,
line_art, distmap, thickmap,
antialias,
threshold,
fill_transparent,
......
......@@ -21,6 +21,8 @@
void gimp_drawable_bucket_fill (GimpDrawable *drawable,
GeglBuffer *line_art,
gfloat *distmap,
gfloat *thickmap,
GimpFillOptions *options,
gboolean fill_transparent,
GimpSelectCriterion fill_criterion,
......@@ -32,6 +34,8 @@ void gimp_drawable_bucket_fill (GimpDrawable *drawabl
gdouble y);
GeglBuffer * gimp_drawable_get_bucket_fill_buffer (GimpDrawable *drawable,
GeglBuffer *line_art,
gfloat *distmap,
gfloat *thickmap,
GimpFillOptions *options,
gboolean fill_transparent,
GimpSelectCriterion fill_criterion,
......
......@@ -105,7 +105,8 @@ static GArray * gimp_lineart_line_segment_until_hit (const GeglBuffer
Pixel start,
GimpVector2 direction,
int size);
static gfloat * gimp_lineart_estimate_strokes_radii (GeglBuffer *mask);
static gfloat * gimp_lineart_estimate_strokes_radii (GeglBuffer *mask,
gfloat **lineart_distmap);
/* Some callback-type functions. */
......@@ -163,7 +164,7 @@ static void gimp_edgelset_next8 (const GeglBuffer *buffer,
/**
* gimp_lineart_close:
* @line_art: the input #GeglBuffer.
* @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
......@@ -187,8 +188,10 @@ static void gimp_edgelset_next8 (const GeglBuffer *buffer,
* @segments_max_length: the maximum length for creating segments
* between end points. Unlike splines, segments
* are straight lines.
* @lineart_distmap: a distance map of the line art pixels.
* @lineart_radii: a map of estimated radii of line art border pixels.
*
* Creates a binarized version of the strokes of @line_art, detected either
* Creates a binarized version of the strokes of @buffer, detected either
* with luminosity (light means background) or alpha values depending on
* @select_transparent. This binary version of the strokes will have closed
* regions allowing adequate selection of "nearly closed regions".
......@@ -201,24 +204,28 @@ static void gimp_edgelset_next8 (const GeglBuffer *buffer,
* Fourey, David Tschumperlé, David Revoy.
*
* Returns: a new #GeglBuffer of format "Y u8" representing the
* binarized @line_art. A value of
* binarized @line_art. If @lineart_radii and @lineart_distmap
* are not #NULL, newly allocated float buffer are returned,
* which can be used for overflowing created masks later.
*/
GeglBuffer *
gimp_lineart_close (GeglBuffer *line_art,
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)
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,
gfloat **lineart_radii)
{
const Babl *gray_format;
gfloat *normals;
......@@ -236,8 +243,8 @@ gimp_lineart_close (GeglBuffer *line_art,
guchar max_value = 0;
gfloat threshold;
gfloat clamped_threshold;
gint width = gegl_buffer_get_width (line_art);
gint height = gegl_buffer_get_height (line_art);
gint width = gegl_buffer_get_width (buffer);
gint height = gegl_buffer_get_height (buffer);
gint i;
normals = g_new0 (gfloat, width * height * 2);
......@@ -252,9 +259,9 @@ gimp_lineart_close (GeglBuffer *line_art,
gray_format = babl_format ("Y' u8");
/* Transform the line art from any format to gray. */
strokes = gegl_buffer_new (gegl_buffer_get_extent (line_art),
strokes = gegl_buffer_new (gegl_buffer_get_extent (buffer),
gray_format);
gegl_buffer_copy (line_art, NULL, GEGL_ABYSS_NONE, strokes, NULL);
gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, strokes, NULL);
gegl_buffer_set_format (strokes, babl_format ("Y' u8"));
if (! select_transparent)
......@@ -306,7 +313,7 @@ gimp_lineart_close (GeglBuffer *line_art,
smoothed_curvatures,
normal_estimate_mask_size);
radii = gimp_lineart_estimate_strokes_radii (strokes);
radii = gimp_lineart_estimate_strokes_radii (strokes, lineart_distmap);
threshold = 1.0f - end_point_rate;
clamped_threshold = MAX (0.25f, threshold);
for (i = 0; i < width; i++)
......@@ -321,7 +328,10 @@ gimp_lineart_close (GeglBuffer *line_art,
curvatures[i + j * width] = 0.0;
}
}
g_free (radii);
if (lineart_radii)
*lineart_radii = radii;
else
g_free (radii);
keypoints = gimp_lineart_curvature_extremums (curvatures, smoothed_curvatures,
width, height);
......@@ -1266,7 +1276,8 @@ gimp_lineart_line_segment_until_hit (const GeglBuffer *mask,
}
static gfloat *
gimp_lineart_estimate_strokes_radii (GeglBuffer *mask)
gimp_lineart_estimate_strokes_radii (GeglBuffer *mask,
gfloat **lineart_distmap)
{
GeglBufferIterator *gi;
gfloat *dist;
......@@ -1411,7 +1422,10 @@ gimp_lineart_estimate_strokes_radii (GeglBuffer *mask)
}
}
g_free (dist);
if (lineart_distmap)
*lineart_distmap = dist;
else
g_free (dist);
g_object_unref (distmap);
return thickness;
......
......@@ -22,21 +22,23 @@
#define __GIMP_LINEART__
GeglBuffer * gimp_lineart_close (GeglBuffer *line_art,
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);
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,
gfloat **lineart_radii);
#endif /* __GIMP_LINEART__ */
......@@ -46,6 +46,13 @@ typedef struct
gfloat stroke_threshold;
} LineArtData;
typedef struct
{
gint x;
gint y;
gfloat dist;
} BorderPixel;
/* local function prototypes */
......@@ -106,10 +113,20 @@ 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);
static void line_art_data_free (LineArtData *data);
static LineArtData * line_art_data_new (GeglBuffer *buffer,
gboolean select_transparent,
gfloat stroke_threshold);
static void line_art_data_free (LineArtData *data);
static GimpPickableLineArtAsyncResult *
line_art_result_new (GeglBuffer *line_art,
gfloat *distmap,
gfloat *thickmap);
static void line_art_result_free (GimpPickableLineArtAsyncResult
*data);
static void line_art_queue_pixel (GQueue *queue,
gint x,
gint y,
gfloat dist);
/* public functions */
......@@ -119,6 +136,8 @@ gimp_pickable_contiguous_region_prepare_line_art_async_func (GimpAsync *async,
LineArtData *data)
{
GeglBuffer *lineart;
gfloat *distmap;
gfloat *thickmap;
gboolean has_alpha;
gboolean select_transparent = FALSE;
......@@ -201,23 +220,29 @@ gimp_pickable_contiguous_region_prepare_line_art_async_func (GimpAsync *async,
/*small_segments_from_spline_sources,*/
TRUE,
/*segments_max_length*/
20);
20,
&distmap, &thickmap);
GIMP_TIMER_END("close line-art");
gimp_async_finish_full (async, lineart, g_object_unref);
gimp_async_finish_full (async,
line_art_result_new (lineart, distmap, thickmap),
(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)
gimp_pickable_contiguous_region_prepare_line_art (GimpPickable *pickable,
gboolean select_transparent,
gfloat stroke_threshold,
gfloat **distmap,
gfloat **thickmap)
{
GimpAsync *async;
LineArtData *data;
GeglBuffer *lineart;
GimpAsync *async;
LineArtData *data;
GimpPickableLineArtAsyncResult *result;
GeglBuffer *lineart;
g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
......@@ -230,7 +255,13 @@ gimp_pickable_contiguous_region_prepare_line_art (GimpPickable *pickable,
gimp_pickable_contiguous_region_prepare_line_art_async_func (async, data);
lineart = g_object_ref (gimp_async_get_result (async));
result = gimp_async_get_result (async);
lineart = g_object_ref (result->line_art);
*distmap = result->distmap;
*thickmap = result->thickmap;
result->distmap = NULL;
result->thickmap = NULL;
g_object_unref (async);
......@@ -272,6 +303,8 @@ gimp_pickable_contiguous_region_prepare_line_art_async (GimpPickable *pickable,
GeglBuffer *
gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
GeglBuffer *line_art,
gfloat *distmap,
gfloat *thickmap,
gboolean antialias,
gfloat threshold,
gboolean select_transparent,
......@@ -288,7 +321,6 @@ 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;
gboolean free_line_art = FALSE;
......@@ -296,6 +328,9 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
if (select_criterion == GIMP_SELECT_CRITERION_LINE_ART)
{
g_return_val_if_fail ((line_art && distmap && thickmap) ||
(! line_art && ! distmap && ! thickmap),
NULL);
if (line_art == NULL)
{
/* It is much better experience to pre-compute the line art,
......@@ -303,7 +338,8 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
* selecting/filling through a PDB call).
*/
line_art = gimp_pickable_contiguous_region_prepare_line_art (pickable, select_transparent,
stroke_threshold);
stroke_threshold,
&distmap, &thickmap);
free_line_art = TRUE;
}
......@@ -382,59 +418,198 @@ gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
/* The last step of the line art algorithm is to make sure that
* selections does not leave "holes" between its borders and the
* line arts, while not stepping over as well.
* To achieve this, I label differently the selection and the rest
* and leave the stroke pixels unlabelled, then I let these
* unknown pixels be labelled by flooding through watershed.
* I used to run the "gegl:watershed-transform" operation to flood
* the stroke pixels, but for such simple need, this simple code
* is so much faster while producing better results.
*/
GeglBufferIterator *gi;
GeglNode *graph;
GeglNode *input;
GeglNode *op;
gfloat *mask;
GQueue *queue = g_queue_new ();
gint width = gegl_buffer_get_width (line_art);
gint height = gegl_buffer_get_height (line_art);
gint nx, ny;
GIMP_TIMER_START();
/* Flag the unselected line art pixels. */
gi = gegl_buffer_iterator_new (src_buffer, NULL, 0, NULL,
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
gegl_buffer_iterator_add (gi, mask_buffer, NULL, 0,
babl_format ("Y float"),
GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE);
while (gegl_buffer_iterator_next (gi))
mask = g_new (gfloat, width * height);
gegl_buffer_get (mask_buffer, NULL, 1.0, NULL,
mask, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
for (y = 0; y < height; y++)
for (x = 0; x < width; x++)
{
gfloat thickness = thickmap[x + y * width];
if (thickness > 0.0)
{
if (x > 0)
{
nx = x - 1;
if (y > 0)
{
ny = y - 1;
if (mask[nx + ny * width] != 0.0)
{
line_art_queue_pixel (queue, x, y, thickness);
continue;
}
}
ny = y;
if (mask[nx + ny * width] != 0.0)
{
line_art_queue_pixel (queue, x, y, thickness);
continue;
}
if (y < height - 1)
{
ny = y + 1;
if (mask[nx + ny * width] != 0.0)
{
line_art_queue_pixel (queue, x, y, thickness);
continue;
}
}
}
if (x < width - 1)
{
nx = x + 1;
if (y > 0)
{
ny = y - 1;
if (mask[nx + ny * width] != 0.0)
{
line_art_queue_pixel (queue, x, y, thickness);
continue;
}
}
ny = y;
if (mask[nx + ny * width] != 0.0)
{
line_art_queue_pixel (queue, x, y, thickness);
continue;
}
if (y < height - 1)
{
ny = y + 1;
if (mask[nx + ny * width] != 0.0)
{
line_art_queue_pixel (queue, x, y, thickness);
continue;
}
}
}
nx = x;
if (y > 0)
{
ny = y - 1;
if (mask[nx + ny * width] != 0.0)
{
line_art_queue_pixel (queue, x, y, thickness);
continue;
}
}
if (y < height - 1)
{
ny = y + 1;
if (mask[nx + ny * width] != 0.0)
{
line_art_queue_pixel (queue, x, y, thickness);
continue;
}
}
}
}
while (! g_queue_is_empty (queue))
{
guchar *lineart = (guchar*) gi->items[0].data;
gfloat *mask = (gfloat*) gi->items[1].data;
gint k;
BorderPixel *c = g_queue_pop_head (queue);
for (k = 0; k < gi->length; k++)
if (mask[c->x + c->y * width] != 1.0)
{
if (*lineart && ! *mask)
*mask = flag;
lineart++;
mask++;
mask[c->x + c->y * width] = 1.0;
if (c->x > 0)
{
nx = c->x - 1;
if (c->y > 0)
{
ny = c->y - 1;
if (mask[nx + ny * width] == 0.0 &&
distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
distmap[nx + ny * width] < c->dist)
line_art_queue_pixel (queue, nx, ny, c->dist);
}
ny = c->y;
if (mask[nx + ny * width] == 0.0 &&
distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
distmap[nx + ny * width] < c->dist)
line_art_queue_pixel (queue, nx, ny, c->dist);
if (c->y < height - 1)
{
ny = c->y - 1;
if (mask[nx + ny * width] == 0.0 &&
distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
distmap[nx + ny * width] < c->dist)
line_art_queue_pixel (queue, nx, ny, c->dist);
}
}
if (c->x < width - 1)
{
nx = c->x + 1;
if (c->y > 0)
{
ny = c->y - 1;
if (mask[nx + ny * width] == 0.0 &&
distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
distmap[nx + ny * width] < c->dist)
line_art_queue_pixel (queue, nx, ny, c->dist);
}
ny = c->y;
if (mask[nx + ny * width] == 0.0 &&
distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
distmap[nx + ny * width] < c->dist)
line_art_queue_pixel (queue, nx, ny, c->dist);
if (c->y < height - 1)
{
ny = c->y - 1;
if (mask[nx + ny * width] == 0.0 &&
distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
distmap[nx + ny * width] < c->dist)
line_art_queue_pixel (queue, nx, ny, c->dist);
}
}
nx = c->x;
if (c->y > 0)
{
ny = c->y - 1;
if (mask[nx + ny * width] == 0.0 &&
distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
distmap[nx + ny * width] < c->dist)
line_art_queue_pixel (queue, nx, ny, c->dist);
}
if (c->y < height - 1)
{
ny = c->y + 1;
if (mask[nx + ny * width] == 0.0 &&
distmap[nx + ny * width] > distmap[c->x + c->y * width] &&
distmap[nx + ny * width] < c->dist)
line_art_queue_pixel (queue, nx, ny, c->dist);
}
}
g_free (c);
}
/* Watershed the line art. */
graph = gegl_node_new ();
input = gegl_node_new_child (graph,
"operation", "gegl:buffer-source",
"buffer", mask_buffer,
NULL);
op = gegl_node_new_child (graph,
"operation", "gegl:watershed-transform",
"flag-component", 0,
"flag", &flag,
NULL);
gegl_node_connect_to (input, "output",
op, "input");
gegl_node_blit_buffer (op, mask_buffer, NULL, 0, GEGL_ABYSS_NONE);
g_object_unref (graph);
g_queue_free (queue);
gegl_buffer_set (mask_buffer, gegl_buffer_get_extent (mask_buffer),
0, NULL, mask, GEGL_AUTO_ROWSTRIDE);
g_free (mask);
GIMP_TIMER_END("watershed line art");
if (free_line_art)
{
g_object_unref (src_buffer);
g_free (distmap);
g_free (thickmap);
}
}
if (free_line_art)
g_object_unref (src_buffer);
return mask_buffer;
}
......@@ -1001,3 +1176,43 @@ line_art_data_free (LineArtData *data)
g_slice_free (LineArtData, data);
}
static GimpPickableLineArtAsyncResult *
line_art_result_new (GeglBuffer *line_art,
gfloat *distmap,
gfloat *thickmap)
{
GimpPickableLineArtAsyncResult *data;
data = g_slice_new (GimpPickableLineArtAsyncResult);
data->line_art = line_art;
data->distmap = distmap;
data->thickmap = thickmap;
return data;
}
static void
line_art_result_free (GimpPickableLineArtAsyncResult *data)
{
g_object_unref (data->line_art);
g_clear_pointer (&data->distmap, g_free);
g_clear_pointer (&data->thickmap, g_free);
g_slice_free (GimpPickableLineArtAsyncResult, data);
}
static void
line_art_queue_pixel (GQueue *queue,
gint x,
gint y,
gfloat dist)
{
BorderPixel *p = g_new (BorderPixel, 1);
p->x = x;
p->y = y;
p->dist = dist;
g_queue_push_head (queue, p);
}
......@@ -18,10 +18,18 @@
#ifndef __GIMP_PICKABLE_CONTIGUOUS_REGION_H__
#define __GIMP_PICKABLE_CONTIGUOUS_REGION_H__
typedef struct
{
GeglBuffer *line_art;
gfloat *distmap;
gfloat *thickmap;
} GimpPickableLineArtAsyncResult;
GeglBuffer * gimp_pickable_contiguous_region_prepare_line_art (GimpPickable *pickable,
gboolean select_transparent,
gfloat stroke_threshold);
gfloat stroke_threshold,
gfloat **distmap,
gfloat **thickmap);
GimpAsync * gimp_pickable_contiguous_region_prepare_line_art_async (GimpPickable *pickable,
gboolean select_transparent,
gfloat stroke_threshold,
......@@ -29,6 +37,8 @@ GimpAsync * gimp_pickable_contiguous_region_prepare_line_art_async (GimpPickabl
GeglBuffer * gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
GeglBuffer *line_art,
gfloat *distmap,
gfloat *thickmap,
gboolean antialias,
gfloat threshold,
gboolean select_transparent,
......
......@@ -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, NULL, options,
gimp_drawable_bucket_fill (drawable, NULL, NULL, NULL, options,
GIMP_PDB_CONTEXT (context)->sample_transparent,
GIMP_PDB_CONTEXT (context)->sample_criterion,
GIMP_PDB_CONTEXT (context)->sample_threshold,
......
......@@ -61,6 +61,8 @@ struct _GimpBucketFillToolPrivate
{
GimpAsync *async;
GeglBuffer *line_art;
gfloat *distmap;
gfloat *thickmap;
GWeakRef cached_image;
GWeakRef cached_drawable;
......@@ -243,6 +245,8 @@ gimp_bucket_fill_tool_finalize (GObject *object)
}
g_clear_object (&tool->priv->line_art);
g_clear_pointer (&tool->priv->distmap, g_free);
g_clear_pointer (&tool->priv->thickmap, g_free);
if (image)
{
......@@ -363,6 +367,8 @@ gimp_bucket_fill_tool_preview (GimpBucketFillTool *tool,
{
GeglBuffer *fill = NULL;
GeglBuffer *line_art = NULL;