app: fix line art labellization.

The older labelling based off CImg code was broken (probably because of
me, from my port). Anyway I realized what it was trying to do was too
generic, which is why we had to fix the result later (labeling all
non-stroke pixels as 0, etc.). Instead I just implemented a simpler
labelling and only look for stroke regions. It still over-label a bit
the painting but a lot less, and is much faster.
This commit is contained in:
Jehan 2018-10-31 10:14:20 +01:00
parent c4ff81540d
commit 93a49951a0
1 changed files with 120 additions and 156 deletions

View File

@ -68,8 +68,11 @@ typedef struct _Edgel
guint next, previous; guint next, previous;
} Edgel; } Edgel;
static GeglBuffer * gimp_lineart_get_labels (GeglBuffer *line_art, static void gimp_lineart_add_label_equivalency (GHashTable *equivalencies,
gboolean is_high_connectivity); guint label1,
guint label2);
static GeglBuffer * gimp_lineart_label (GeglBuffer *line_art,
guint32 *n_labels);
static void gimp_lineart_erode (GeglBuffer *buffer, static void gimp_lineart_erode (GeglBuffer *buffer,
gint s); gint s);
static void gimp_lineart_denoise (GeglBuffer *buffer, static void gimp_lineart_denoise (GeglBuffer *buffer,
@ -304,6 +307,7 @@ gimp_lineart_close (GeglBuffer *line_art,
if (erode_size) if (erode_size)
gimp_lineart_erode (strokes, 2 * erode_size); gimp_lineart_erode (strokes, 2 * erode_size);
} }
/* Denoise (remove small connected components) */ /* Denoise (remove small connected components) */
gimp_lineart_denoise (strokes, minimal_lineart_area); gimp_lineart_denoise (strokes, minimal_lineart_area);
@ -459,128 +463,126 @@ gimp_lineart_close (GeglBuffer *line_art,
/* Private functions */ /* Private functions */
static GeglBuffer * static void
gimp_lineart_get_labels (GeglBuffer *line_art, gimp_lineart_add_label_equivalency (GHashTable *equivalencies,
gboolean is_high_connectivity) guint label1,
guint label2)
{ {
/* gpointer key = GUINT_TO_POINTER (MAX (label1, label2));
* Converted from CImg.get_label() code, with tolerance = 0 (used to gpointer eq = GUINT_TO_POINTER (MIN (label1, label2));
* determine if two neighboring pixels belong to the same region). gpointer old_eq = g_hash_table_lookup (equivalencies, key);
* The algorithm of connected components computation has been primarily done
* by A. Meijster, according to the publication: 'W.H. Hesselink, A.
* Meijster, C. Bron, "Concurrent Determination of Connected Components.",
* In: Science of Computer Programming 41 (2001), pp. 173--194'.
* The submitted code has then been modified to fit CImg first, then GIMP.
*/
guint32 *data;
gint width = gegl_buffer_get_width (line_art);
gint height = gegl_buffer_get_height (line_art);
guint32 counter = 0;
guint32 p = 0;
/* Create neighborhood tables. */ if (old_eq && old_eq != eq)
int dx[4], dy[4]; eq = MIN (old_eq, eq);
dx[0] = 1; dy[0] = 0; /* Check that the equivalent label has no equivalent itself. */
dx[1] = 0; dy[1] = 1; if ((old_eq = g_hash_table_lookup (equivalencies, eq)))
if (is_high_connectivity) g_hash_table_insert (equivalencies, key, old_eq);
else
g_hash_table_insert (equivalencies, key, eq);
}
/**
* Label connected stroke pixels in regions, and leave all non-stroke
* pixels with label 0.
*/
static GeglBuffer *
gimp_lineart_label (GeglBuffer *line_art,
guint32 *n_labels)
{
GeglBufferIterator *gi;
guint *labels;
guint *label;
GHashTable *equivalencies;
gint width = gegl_buffer_get_width (line_art);
gint height = gegl_buffer_get_height (line_art);
gint x;
gint y;
equivalencies = g_hash_table_new (NULL, NULL);
labels = g_new (guint, sizeof (guint) * width * height);
gi = gegl_buffer_iterator_new (line_art, gegl_buffer_get_extent (line_art),
0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
*n_labels = 0;
while (gegl_buffer_iterator_next (gi))
{ {
dx[2] = 1; dy[2] = 1; guint8 *stroke = (guint8*) gi->items[0].data;
dx[3] = 1; dy[3] = -1; gint startx = gi->items[0].roi.x;
gint starty = gi->items[0].roi.y;
gint endy = starty + gi->items[0].roi.height;
gint endx = startx + gi->items[0].roi.width;
for (y = starty; y < endy; y++)
for (x = startx; x < endx; x++)
{
label = labels + y * width + x;
*label = 0;
if (*stroke)
{
if (x > 0 && y > 0)
{
guint *pxy = label - width - 1;
*label = *pxy;
}
if (y > 0)
{
guint *py = label - width;
if (! *label)
*label = *py;
else if (*py && *label != *py)
gimp_lineart_add_label_equivalency (equivalencies,
*label, *py);
}
if (y > 0 && x < width - 1)
{
guint *py_nx = label - width + 1;
if (! *label)
*label = *py_nx;
else if (*py_nx && *label != *py_nx)
gimp_lineart_add_label_equivalency (equivalencies,
*label, *py_nx);
}
if (x > 0)
{
guint *px = label - 1;
if (! *label)
*label = *px;
else if (*px && *label != *px)
gimp_lineart_add_label_equivalency (equivalencies,
*label, *px);
}
if (! *label)
*label = ++(*n_labels);
}
stroke++;
}
} }
data = g_new (guint32, label = labels;
babl_format_get_bytes_per_pixel (babl_format_n (babl_type ("u32"), 1)) * width * height); for (y = 0; y < height; y++)
for (x = 0; x < width; x++)
{
if (*label > 1)
{
gpointer eq = g_hash_table_lookup (equivalencies,
GINT_TO_POINTER (*label));
/* Init label numbers. */ if (eq)
for (guint32 i = 0; i < width * height; i++) *label = GPOINTER_TO_INT (eq);
data[i] = i; }
label++;
}
g_hash_table_destroy (equivalencies);
/* For each neighbour-direction, label. */ return gegl_buffer_linear_new_from_data (labels,
for (unsigned int n = 0; n < (is_high_connectivity ? 4 : 2); ++n)
{
GeglBufferIterator *gi;
const gint _dx = dx[n];
const gint _dy = dy[n];
const gint y0 = (_dy < 0) ? -_dy : 0;
const gint it_width = width - _dx + 1;
const gint it_height = (_dy < 0) ? height - y0 + 1: height - _dy - y0 + 1;
const glong offset = _dy * width + _dx;
gi = gegl_buffer_iterator_new (line_art, GEGL_RECTANGLE (0, y0, it_width, it_height),
0, babl_format ("Y u32"),
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
gegl_buffer_iterator_add (gi, line_art, GEGL_RECTANGLE (_dx, y0 + _dy, it_width, it_height),
0, babl_format ("Y u32"),
GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
while (gegl_buffer_iterator_next (gi))
{
GeglRectangle *roi = &gi->items[0].roi;
guint32 *pixel = (guint32*) gi->items[0].data;
guint32 *neighbour = (guint32*) gi->items[1].data;
gint k;
gint x = roi->x;
gint y = roi->y;
for (k = 0; k < gi->length; k++)
{
if (pixel == neighbour)
{
const glong p = width * y;
const guint32 q = p + offset;
guint32 i, j;
for (i = MAX (p, q), j = MIN (p, q); i != j && data[i] != i; )
{
i = (guint32) data[i];
if (i < j)
{
/* Swap i and j. */
guint32 temp = i;
i = j;
j = temp;
}
}
if (i != j)
data[i] = j;
for (guint32 _p = (guint32) p; _p != j; )
{
const guint32 h = (guint32) data[_p];
data[_p] = (guint32) j;
_p = h;
}
for (guint32 _q = (guint32) q; _q != j; )
{
const guint32 h = (guint32) data[_q];
data[_q] = (guint32) j;
_q = h;
}
}
pixel++;
neighbour++;
x++;
if (x - roi->x >= roi->width)
{
x = roi->x;
y++;
}
}
}
}
/* Resolve equivalences. */
p = 0;
for (guint32 i = 0; i < width * height; i++)
{
data[i] = data[i] == p ? counter++ : data[data[i]];
p++;
}
return gegl_buffer_linear_new_from_data (data,
babl_format_n (babl_type ("u32"), 1), babl_format_n (babl_type ("u32"), 1),
gegl_buffer_get_extent (line_art), 0, gegl_buffer_get_extent (line_art), 0,
g_free, NULL); g_free, NULL);
@ -1715,22 +1717,8 @@ gimp_lineart_estimate_stroke_width (GeglBuffer* mask)
gegl_node_process (sink); gegl_node_process (sink);
g_object_unref (graph); g_object_unref (graph);
labels = gimp_lineart_get_labels (mask, TRUE); labels = gimp_lineart_label (mask, &label_max);
/* Check biggest label. */
gi = gegl_buffer_iterator_new (labels, NULL, 0, NULL,
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
while (gegl_buffer_iterator_next (gi))
{
guint32 *data = (guint32*) gi->items[0].data;
gint k;
for (k = 0; k < gi->length; k++)
{
label_max = MAX (*data, label_max);
data++;
}
}
if (label_max == 0) if (label_max == 0)
{ {
g_object_unref (labels); g_object_unref (labels);
@ -1738,30 +1726,6 @@ gimp_lineart_estimate_stroke_width (GeglBuffer* mask)
return 0.0; return 0.0;
} }
/* Make sure that stroke pixels are label 0. */
label_max++;
gi = gegl_buffer_iterator_new (mask, NULL, 0, NULL,
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
gegl_buffer_iterator_add (gi, labels, NULL, 0,
babl_format_n (babl_type ("u32"), 1),
GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
while (gegl_buffer_iterator_next (gi))
{
guint8 *m = (guint8*) gi->items[0].data;
guint32 *l = (guint32*) gi->items[1].data;
gint k;
for (k = 0; k < gi->length; k++)
{
if (! *m)
*l = 0;
else if (*l == 0)
*l = label_max;
m++;
l++;
}
}
/* Create an array of max distance per label */ /* Create an array of max distance per label */
dmax = g_array_sized_new (FALSE, TRUE, sizeof (gfloat), label_max); dmax = g_array_sized_new (FALSE, TRUE, sizeof (gfloat), label_max);
g_array_set_size (dmax, label_max); g_array_set_size (dmax, label_max);
@ -1807,7 +1771,7 @@ gimp_lineart_estimate_stroke_width (GeglBuffer* mask)
g_object_unref (labels); g_object_unref (labels);
g_object_unref (distmap); g_object_unref (distmap);
return 2.0 * res; return 1.5 * res;
} }
static guint static guint