mirror of https://github.com/GNOME/gimp.git
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:
parent
c4ff81540d
commit
93a49951a0
|
@ -68,8 +68,11 @@ typedef struct _Edgel
|
|||
guint next, previous;
|
||||
} Edgel;
|
||||
|
||||
static GeglBuffer * gimp_lineart_get_labels (GeglBuffer *line_art,
|
||||
gboolean is_high_connectivity);
|
||||
static void gimp_lineart_add_label_equivalency (GHashTable *equivalencies,
|
||||
guint label1,
|
||||
guint label2);
|
||||
static GeglBuffer * gimp_lineart_label (GeglBuffer *line_art,
|
||||
guint32 *n_labels);
|
||||
static void gimp_lineart_erode (GeglBuffer *buffer,
|
||||
gint s);
|
||||
static void gimp_lineart_denoise (GeglBuffer *buffer,
|
||||
|
@ -304,6 +307,7 @@ gimp_lineart_close (GeglBuffer *line_art,
|
|||
if (erode_size)
|
||||
gimp_lineart_erode (strokes, 2 * erode_size);
|
||||
}
|
||||
|
||||
/* Denoise (remove small connected components) */
|
||||
gimp_lineart_denoise (strokes, minimal_lineart_area);
|
||||
|
||||
|
@ -459,128 +463,126 @@ gimp_lineart_close (GeglBuffer *line_art,
|
|||
|
||||
/* Private functions */
|
||||
|
||||
static GeglBuffer *
|
||||
gimp_lineart_get_labels (GeglBuffer *line_art,
|
||||
gboolean is_high_connectivity)
|
||||
static void
|
||||
gimp_lineart_add_label_equivalency (GHashTable *equivalencies,
|
||||
guint label1,
|
||||
guint label2)
|
||||
{
|
||||
/*
|
||||
* Converted from CImg.get_label() code, with tolerance = 0 (used to
|
||||
* determine if two neighboring pixels belong to the same region).
|
||||
* 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.
|
||||
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.
|
||||
*/
|
||||
guint32 *data;
|
||||
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);
|
||||
guint32 counter = 0;
|
||||
guint32 p = 0;
|
||||
gint x;
|
||||
gint y;
|
||||
|
||||
/* Create neighborhood tables. */
|
||||
int dx[4], dy[4];
|
||||
equivalencies = g_hash_table_new (NULL, NULL);
|
||||
|
||||
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;
|
||||
}
|
||||
labels = g_new (guint, sizeof (guint) * width * height);
|
||||
|
||||
data = g_new (guint32,
|
||||
babl_format_get_bytes_per_pixel (babl_format_n (babl_type ("u32"), 1)) * width * height);
|
||||
gi = gegl_buffer_iterator_new (line_art, gegl_buffer_get_extent (line_art),
|
||||
0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
|
||||
|
||||
/* Init label numbers. */
|
||||
for (guint32 i = 0; i < width * height; i++)
|
||||
data[i] = i;
|
||||
|
||||
/* For each neighbour-direction, label. */
|
||||
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);
|
||||
*n_labels = 0;
|
||||
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;
|
||||
guint8 *stroke = (guint8*) gi->items[0].data;
|
||||
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 (k = 0; k < gi->length; k++)
|
||||
for (y = starty; y < endy; y++)
|
||||
for (x = startx; x < endx; x++)
|
||||
{
|
||||
if (pixel == neighbour)
|
||||
{
|
||||
const glong p = width * y;
|
||||
const guint32 q = p + offset;
|
||||
guint32 i, j;
|
||||
label = labels + y * width + x;
|
||||
|
||||
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. */
|
||||
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];
|
||||
guint *pxy = label - width - 1;
|
||||
|
||||
data[_p] = (guint32) j;
|
||||
_p = h;
|
||||
*label = *pxy;
|
||||
}
|
||||
for (guint32 _q = (guint32) q; _q != j; )
|
||||
if (y > 0)
|
||||
{
|
||||
const guint32 h = (guint32) data[_q];
|
||||
guint *py = label - width;
|
||||
|
||||
data[_q] = (guint32) j;
|
||||
_q = h;
|
||||
if (! *label)
|
||||
*label = *py;
|
||||
else if (*py && *label != *py)
|
||||
gimp_lineart_add_label_equivalency (equivalencies,
|
||||
*label, *py);
|
||||
}
|
||||
}
|
||||
pixel++;
|
||||
neighbour++;
|
||||
|
||||
x++;
|
||||
if (x - roi->x >= roi->width)
|
||||
if (y > 0 && x < width - 1)
|
||||
{
|
||||
x = roi->x;
|
||||
y++;
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Resolve equivalences. */
|
||||
p = 0;
|
||||
for (guint32 i = 0; i < width * height; i++)
|
||||
label = labels;
|
||||
for (y = 0; y < height; y++)
|
||||
for (x = 0; x < width; x++)
|
||||
{
|
||||
data[i] = data[i] == p ? counter++ : data[data[i]];
|
||||
p++;
|
||||
}
|
||||
if (*label > 1)
|
||||
{
|
||||
gpointer eq = g_hash_table_lookup (equivalencies,
|
||||
GINT_TO_POINTER (*label));
|
||||
|
||||
return gegl_buffer_linear_new_from_data (data,
|
||||
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),
|
||||
gegl_buffer_get_extent (line_art), 0,
|
||||
g_free, NULL);
|
||||
|
@ -1715,22 +1717,8 @@ gimp_lineart_estimate_stroke_width (GeglBuffer* mask)
|
|||
gegl_node_process (sink);
|
||||
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)
|
||||
{
|
||||
g_object_unref (labels);
|
||||
|
@ -1738,30 +1726,6 @@ gimp_lineart_estimate_stroke_width (GeglBuffer* mask)
|
|||
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 */
|
||||
dmax = g_array_sized_new (FALSE, TRUE, sizeof (gfloat), 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 (distmap);
|
||||
|
||||
return 2.0 * res;
|
||||
return 1.5 * res;
|
||||
}
|
||||
|
||||
static guint
|
||||
|
|
Loading…
Reference in New Issue