Commit 93a49951 authored by Jehan's avatar Jehan

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.
parent c4ff8154
...@@ -68,8 +68,11 @@ typedef struct _Edgel ...@@ -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, ...@@ -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, ...@@ -459,128 +463,126 @@ gimp_lineart_close (GeglBuffer *line_art,
/* Private functions */ /* Private functions */
static void
gimp_lineart_add_label_equivalency (GHashTable *equivalencies,
guint label1,
guint label2)
{
gpointer key = GUINT_TO_POINTER (MAX (label1, label2));
gpointer eq = GUINT_TO_POINTER (MIN (label1, label2));
gpointer old_eq = g_hash_table_lookup (equivalencies, key);
if (old_eq && old_eq != eq)
eq = MIN (old_eq, eq);
/* Check that the equivalent label has no equivalent itself. */
if ((old_eq = g_hash_table_lookup (equivalencies, eq)))
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 * static GeglBuffer *
gimp_lineart_get_labels (GeglBuffer *line_art, gimp_lineart_label (GeglBuffer *line_art,
gboolean is_high_connectivity) guint32 *n_labels)
{ {
/* GeglBufferIterator *gi;
* Converted from CImg.get_label() code, with tolerance = 0 (used to guint *labels;
* determine if two neighboring pixels belong to the same region). guint *label;
* The algorithm of connected components computation has been primarily done GHashTable *equivalencies;
* by A. Meijster, according to the publication: 'W.H. Hesselink, A. gint width = gegl_buffer_get_width (line_art);
* Meijster, C. Bron, "Concurrent Determination of Connected Components.", gint height = gegl_buffer_get_height (line_art);
* In: Science of Computer Programming 41 (2001), pp. 173--194'. gint x;
* The submitted code has then been modified to fit CImg first, then GIMP. gint y;
*/
guint32 *data; equivalencies = g_hash_table_new (NULL, NULL);
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. */
int dx[4], dy[4];
dx[0] = 1; dy[0] = 0;
dx[1] = 0; dy[1] = 1;
if (is_high_connectivity)
{
dx[2] = 1; dy[2] = 1;
dx[3] = 1; dy[3] = -1;
}
data = g_new (guint32, labels = g_new (guint, sizeof (guint) * width * height);
babl_format_get_bytes_per_pixel (babl_format_n (babl_type ("u32"), 1)) * width * height);
/* Init label numbers. */ gi = gegl_buffer_iterator_new (line_art, gegl_buffer_get_extent (line_art),
for (guint32 i = 0; i < width * height; i++) 0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
data[i] = i;
/* For each neighbour-direction, label. */ *n_labels = 0;
for (unsigned int n = 0; n < (is_high_connectivity ? 4 : 2); ++n) while (gegl_buffer_iterator_next (gi))
{ {
GeglBufferIterator *gi; guint8 *stroke = (guint8*) gi->items[0].data;
const gint _dx = dx[n]; gint startx = gi->items[0].roi.x;
const gint _dy = dy[n]; gint starty = gi->items[0].roi.y;
gint endy = starty + gi->items[0].roi.height;
const gint y0 = (_dy < 0) ? -_dy : 0; gint endx = startx + gi->items[0].roi.width;
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++) for (y = starty; y < endy; y++)
{ for (x = startx; x < endx; x++)
if (pixel == neighbour) {
{ label = labels + y * width + x;
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; ) *label = 0;
{ if (*stroke)
i = (guint32) data[i]; {
if (i < j) if (x > 0 && y > 0)
{ {
/* Swap i and j. */ guint *pxy = label - width - 1;
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; *label = *pxy;
_p = h; }
} if (y > 0)
for (guint32 _q = (guint32) q; _q != j; ) {
{ guint *py = label - width;
const guint32 h = (guint32) data[_q];
data[_q] = (guint32) j; if (! *label)
_q = h; *label = *py;
} else if (*py && *label != *py)
} gimp_lineart_add_label_equivalency (equivalencies,
pixel++; *label, *py);
neighbour++; }
if (y > 0 && x < width - 1)
{
guint *py_nx = label - width + 1;
x++; if (! *label)
if (x - roi->x >= roi->width) *label = *py_nx;
{ else if (*py_nx && *label != *py_nx)
x = roi->x; gimp_lineart_add_label_equivalency (equivalencies,
y++; *label, *py_nx);
} }
} if (x > 0)
} {
} guint *px = label - 1;
/* Resolve equivalences. */ if (! *label)
p = 0; *label = *px;
for (guint32 i = 0; i < width * height; i++) else if (*px && *label != *px)
{ gimp_lineart_add_label_equivalency (equivalencies,
data[i] = data[i] == p ? counter++ : data[data[i]]; *label, *px);
p++; }
if (! *label)
*label = ++(*n_labels);
}
stroke++;
}
} }
return gegl_buffer_linear_new_from_data (data, label = labels;
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));
if (eq)
*label = GPOINTER_TO_INT (eq);
}
label++;
}
g_hash_table_destroy (equivalencies);
return gegl_buffer_linear_new_from_data (labels,
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) ...@@ -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) ...@@ -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) ...@@ -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
......
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