/*
 *  $Id: grains.c 30118 2026-06-22 18:23:21Z yeti-dn $
 *  Copyright (C) 2025-2026 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include <string.h>
#include <glib/gi18n-lib.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/arithmetic.h"
#include "libgwyddion/grains.h"

#include "libgwyddion/int-list.h"
#include "libgwyddion/omp.h"
#include "libgwyddion/internal.h"

static gint*       enumerate_grain_indices    (GwyNield *nield,
                                               gint col,
                                               gint row,
                                               gint *nindices);
static gint        fill_one_grain             (GwyNield *nield,
                                               gint col,
                                               gint row,
                                               gint fill_with,
                                               gint *visited,
                                               IntList *listv,
                                               IntList *listh);
static GHashTable* get_grain_numbers_hashtable(GwyNield *nield,
                                               GArray *values);
static gint        compactify_dense           (GwyNield *nield,
                                               gint maxgno);

static inline gboolean
gwy_nield_inside(GwyNield *nield, gint i, gint j)
{
    if (i >= 0 && j >= 0 && i < nield->xres && j < nield->yres)
        return TRUE;
    else
        return FALSE;
}

static void
set_cache_for_compact_nield(GwyNield *nield, gint maxgno)
{
    nield->priv->cached = NCBIT(MAX);
    NCVAL(nield, MAX) = maxgno;
}

static gboolean
is_single_pixel_grain(GwyNield *nield, gint col, gint row)
{
    gint xres = nield->xres, yres = nield->yres;
    const gint *data = nield->priv->data;
    gint k = row*xres + col;
    gint grain_no = data[k];
    if (row && data[k-xres] == grain_no)
        return FALSE;
    if (col && data[k-1] == grain_no)
        return FALSE;
    if (col < xres-1 && data[k+1] == grain_no)
        return FALSE;
    if (row < yres-1 && data[k+xres] == grain_no)
        return FALSE;
    return TRUE;
}

static inline void
init_bbox_ranges(gint *bboxes, gint ngrains)
{
    for (gint i = 0; i <= ngrains; i++) {
        bboxes[4*i] = bboxes[4*i + 1] = G_MAXINT;
        bboxes[4*i + 2] = bboxes[4*i + 3] = -1;
    }
}

static inline void
extend_bbox_range(gint *bboxes, gint id, gint j, gint i)
{
    id *= 4;
    if (j < bboxes[id])
        bboxes[id] = j;
    if (i < bboxes[id + 1])
        bboxes[id + 1] = i;
    if (j > bboxes[id + 2])
        bboxes[id + 2] = j;
    if (i > bboxes[id + 3])
        bboxes[id + 3] = i;
}

static inline void
transform_range_to_bbox(gint *bbox)
{
    bbox[2] = bbox[2] + 1 - bbox[0];
    bbox[3] = bbox[3] + 1 - bbox[1];
}

/**
 * gwy_nield_isolate:
 * @nield: A number field.
 * @number: A grain number in @nield.
 * @bbox: (out) (array size=4) (nullable):
 *        Optional array of 4 integers to be filled with the bounding box of the extracted grain.
 *
 * Clears all numbers in a number field except one.
 *
 * The result is flattened, i.e. all values become either 1 (marked) or 0 (outside).
 *
 * See gwy_nield_isolate_at() for a function which isolates a contiguous region.
 *
 * See gwy_nield_bounding_boxes() for bounding box representation. If there are no non-zero pixels at the end,
 * @bbox is filled with values corresponding to a non-existent grain.
 *
 * Returns: %TRUE if there are any non-zero values.
 **/
gboolean
gwy_nield_isolate(GwyNield *nield,
                  gint number,
                  gint *bbox)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), FALSE);
    if (G_UNLIKELY(number <= 0)) {
        gwy_nield_clear(nield);
        if (bbox)
            init_bbox_ranges(bbox, 0);
        return FALSE;
    }

    GwyNieldPrivate *priv = nield->priv;
    gboolean has_any = FALSE;
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gint *data = priv->data;

    if (!bbox) {
        for (gsize i = 0; i < n; i++) {
            if (data[i] == number) {
                data[i] = 1;
                has_any = TRUE;
            }
            else
                data[i] = 0;
        }
    }
    else {
        gint bb[4];
        init_bbox_ranges(bb, 0);
        for (gint i = 0; i < yres; i++) {
            gint *drow = data + i*xres;
            for (gint j = 0; j < xres; j++) {
                if (drow[j] == number) {
                    extend_bbox_range(bb, 0, j, i);
                    drow[j] = 1;
                }
                else
                    drow[j] = 0;
            }
        }

        if (bb[2] != -1) {
            transform_range_to_bbox(bb);
            has_any = TRUE;
        }
        gwy_assign(bbox, bb, 4);
    }
    set_cache_for_compact_nield(nield, has_any ? 1 : 0);

    return has_any;
}

/**
 * gwy_nield_isolate_at:
 * @nield: A number field.
 * @col: Column index.
 * @row: Row index.
 * @bbox: (out) (array size=4) (nullable):
 *        Optional array of 4 integers to be filled with the bounding box of the extracted grain.
 *
 * Isolates a contiguous region at given location in a number field.
 *
 * The result is flattened, i.e. all values become either 1 (marked) or 0 (outside).
 *
 * The region is defined by being both contiguous and having the same non-zero number as at (@col,@row). If the value
 * at (@col,@row) is not positive, the number field is cleared.
 *
 * See gwy_nield_isolate() for a function that isolates a possibly non-contiguous region by grain number and can be
 * used  to isolate a non-contiguous grain by postion (just read the value at (@col, @row)). If you instead want to
 * isolate a contiguous marked region regardless of the grain number, use gwy_nield_flatten() first.
 *
 * See gwy_nield_bounding_boxes() for bounding box representation. If there are no non-zero pixels at the end,
 * @bbox is filled with values corresponding to a non-existent grain.
 *
 * Returns: %TRUE if there are any non-zero values.
 **/
gboolean
gwy_nield_isolate_at(GwyNield *nield,
                     gint col,
                     gint row,
                     gint *bbox)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), FALSE);
    g_return_val_if_fail(gwy_nield_inside(nield, col, row), FALSE);
    GwyNieldPrivate *priv = nield->priv;
    gint *data = priv->data;

    if (data[row*nield->xres + col] <= 0) {
        init_bbox_ranges(bbox, 0);
        gwy_nield_clear(nield);
        return FALSE;
    }

    gint count;
    gint *indices = enumerate_grain_indices(nield, col, row, &count);
    gwy_nield_clear(nield);
    if (!bbox) {
        for (gsize i = 0; i < count; i++)
            data[indices[i]] = 1;
    }
    else {
        gint bb[4], xres = nield->xres;
        init_bbox_ranges(bb, 0);
        for (gsize i = 0; i < count; i++) {
            gint jj = indices[i] % xres, ii = indices[i]/xres;
            data[indices[i]] = 1;
            extend_bbox_range(bb, 0, jj, ii);
        }
        transform_range_to_bbox(bb);
        gwy_assign(bbox, bb, 4);
    }
    g_free(indices);
    set_cache_for_compact_nield(nield, 1);

    return TRUE;
}

/**
 * gwy_nield_clear_by_number:
 * @nield: A number field.
 * @number: A grain number in @nield.
 * @compactify: %TRUE to shift all grains with large values one down.
 *
 * Clears a single grain in a number field, given by number.
 *
 * If @compactify is %FALSE, there will be a hole in the grain numbers. Passing %TRUE moves larger grain numbers down
 * by one. However, this only removes the newly created hole in numbers, not any pre-existing ones. Use
 * gwy_nield_compactify() to remove all holes in grain numbers.
 **/
void
gwy_nield_clear_by_number(GwyNield *nield,
                          gint number,
                          gboolean compactify)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    g_return_if_fail(number > 0);

    GwyNieldPrivate *priv = nield->priv;
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gint *data = priv->data;
    if (compactify) {
        for (gsize i = 0; i < n; i++) {
            if (data[i] > number)
                data[i]--;
            else if (data[i] == number)
                data[i] = 0;
        }
    }
    else {
        for (gsize i = 0; i < n; i++) {
            if (data[i] == number)
                data[i] = 0;
        }
    }
    gwy_nield_invalidate(nield);
}

/**
 * gwy_nield_clear_at:
 * @nield: A number field.
 * @col: Column index.
 * @row: Row index.
 *
 * Clears a contiguous region at given location in a number field.
 *
 * The region is defined by being both contiguous and having the same non-zero number as at (@col,@row). If the value
 * at (@col,@row) is not positive, the function is no-op.
 *
 * See gwy_nield_clear_by_number() for a function which clears a possibly non-contiguous region by grain number. If
 * you want to clear a contiguous marked region regardless of the grain number, use gwy_nield_flatten() first.
 *
 * Returns: %TRUE if anything was cleared.
 **/
gboolean
gwy_nield_clear_at(GwyNield *nield,
                   gint col,
                   gint row)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), FALSE);
    g_return_val_if_fail(gwy_nield_inside(nield, col, row), FALSE);
    GwyNieldPrivate *priv = nield->priv;
    gint *data = priv->data;
    gint grain_no = data[row*nield->xres + col];

    if (grain_no <= 0)
        return FALSE;

    gint count;
    gint *indices = enumerate_grain_indices(nield, col, row, &count);
    for (gsize i = 0; i < count; i++)
        data[indices[i]] = 0;
    g_free(indices);
    gwy_nield_invalidate(nield);

    return TRUE;
}

/**
 * gwy_nield_clear_by_size:
 * @nield: A number field.
 * @size: Minimum grain area, in pixels.
 *
 * Clears all grains below specified area in a number field.
 *
 * All numbered regions with total pixel area smaller than @size are filled with 0.
 *
 * Grain numbers are not modified, so holes in grain numbers are likely to occur. Use gwy_nield_compactify() to
 * remove the holes if it matters.
 **/
void
gwy_nield_clear_by_size(GwyNield *nield,
                        gint size)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    const guint *sizes = _gwy_nield_ensure_sizes(nield);
    gint maxgno = gwy_nield_max(nield);

    gint remove_any = FALSE;
    for (gint i = 1; i <= maxgno; i++) {
        if (sizes[i] && sizes[i] < size) {
            remove_any = TRUE;
            break;
        }
    }
    if (!remove_any)
        return;

    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gint *data = nield->priv->data;
    for (gsize i = 0; i < n; i++) {
        gint grain_no = data[i];
        if (grain_no > 0 && sizes[grain_no] < size)
            data[i] = 0;
    }

    gwy_nield_invalidate(nield);
}

static inline void
remember_touching(gint grain_no, gboolean *touching)
{
    if (grain_no > 0)
        touching[grain_no] = TRUE;
}

/**
 * gwy_nield_clear_touching_border:
 * @nield: A number field.
 * @completely: Pass %TRUE to clear non-contiguous grains completely, %FALSE to only clear the touching parts.
 *
 * Clears all grains that touch any of a number field's borders.
 *
 * Grain numbers are not modified, so holes in grain numbers are likely to occur. Use gwy_nield_compactify() to
 * remove the holes if it matters.
 **/
void
gwy_nield_clear_touching_border(GwyNield *nield,
                                gboolean completely)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    gint maxgno = gwy_nield_max(nield);
    if (maxgno < 1)
        return;

    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gint *data = nield->priv->data;

    if (!completely) {
        /* If we are removing only the touching bits of non-contiguous grains, we need to be careful. The numbered
         * regions themselves can be touching. So a piece of grain N touches the border, but also grain M, which
         * touches another piece of grain N here (and other piece of M can be touching the border elsewhere). But only
         * the first piece should be removed, even though all three form a contiguous region touching the border and
         * both N and M would have the touching flag set. We do this by splitting the grains to contiguous pieces
         * and doing the removal there. */
        GwyNield *numbered = gwy_nield_copy(nield);
        gint *ndata = numbered->priv->data;

        gwy_nield_split_noncontiguous(numbered);
        gwy_nield_clear_touching_border(numbered, TRUE);

        for (gsize i = 0; i < n; i++) {
            if (data[i] > 0 && !ndata[i])
                data[i] = 0;
        }
        g_object_unref(numbered);
        gwy_nield_invalidate(nield);
        return;
    }

    /* Remember grains that touch any border. */
    gint *touching = g_new0(gboolean, maxgno + 1);
    for (gint i = 0; i < xres; i++) {
        remember_touching(data[i], touching);
        remember_touching(data[(yres-1)*xres + i], touching);
    }
    for (gint i = 1; i < yres-1; i++) {
        remember_touching(data[i*xres], touching);
        remember_touching(data[i*xres + xres-1], touching);
    }
    gboolean remove_any = FALSE;
    for (gint i = 1; i <= maxgno; i++) {
        if (touching[i]) {
            remove_any = TRUE;
            break;
        }
    }
    if (!remove_any) {
        g_free(touching);
        return;
    }

    /* Remove data. */
    for (gsize i = 0; i < n; i++) {
        if (touching[data[i]])
            data[i] = 0;
    }

    g_free(touching);
    gwy_nield_invalidate(nield);
}

/**
 * gwy_nield_mark_range:
 * @nield: A number field.
 * @field: Source data field.
 * @lower: Lower threshold for marking.
 * @upper: Upper threshold for marking.
 *
 * Marks all values in a data field within (or outside) an interval.
 *
 * If @lower ≤ @upper, @field values in the closed interval [@lower, @upper] are marked in @nield with 1, the rest
 * marked with 0.
 *
 * If @lower > @upper, @field values in the two closed intervals [-∞, @lower] and [@upper, ∞] are marked in @nield
 * with 1, the rest marked with 0. The closed intervals should be read as all ‘values ≤ @lower’ and ‘values ≥ @upper’.
 *
 * See also gwy_nield_mark_range() for marking using a single threshold and gwy_field_mark_outliers2() and
 * gwy_field_mark_outliers_iqr() for outlier marking.
 **/
void
gwy_nield_mark_range(GwyNield *nield,
                     GwyField *field,
                     gdouble lower,
                     gdouble upper)
{
    if (!_gwy_nield_check_field(nield, field, FALSE))
        return;

    gint xres = nield->xres, yres = nield->yres;
    const gdouble *src = field->priv->data;
    gint *dest = nield->priv->data;
    gsize n = (gsize)xres * (gsize)yres;
    if (lower <= upper) {
        for (gsize i = 0; i < n; i++)
            dest[i] = (src[i] >= lower && src[i] <= upper);
    }
    else {
        for (gsize i = 0; i < n; i++)
            dest[i] = (src[i] >= lower || src[i] <= upper);
    }
    gwy_nield_invalidate(nield);
}

/**
 * gwy_nield_mark_by_threshold:
 * @nield: A number field.
 * @field: Source data field.
 * @threshold: Marking threshold.
 * @above: %TRUE for marking values above the threshold, %FALSE for marking values below.
 *
 * Marks all values in a data field based on a global threshold.
 *
 * If @above is %TRUE, values ≤ @threshold are marked. Otherwise values ≥ @threshold are marked. Values equal to the
 * threshold are marked in both cases.
 *
 * See also gwy_nield_mark_range() for marking a range of values.
 **/
void
gwy_nield_mark_by_threshold(GwyNield *nield,
                            GwyField *field,
                            gdouble threshold,
                            gboolean above)
{
    if (!_gwy_nield_check_field(nield, field, FALSE))
        return;

    gint xres = nield->xres, yres = nield->yres;
    const gdouble *src = field->priv->data;
    gint *dest = nield->priv->data;
    gsize n = (gsize)xres * (gsize)yres;
    if (above) {
        for (gsize i = 0; i < n; i++)
            dest[i] = (src[i] >= threshold);
    }
    else {
        for (gsize i = 0; i < n; i++)
            dest[i] = (src[i] <= threshold);
    }
    gwy_nield_invalidate(nield);
}

/**
 * gwy_nield_union:
 * @nield: A number field.
 * @union_with: Another field to union the number field with.
 *
 * Combines two number fields by grain union.
 *
 * More precisely, the resulting numbers are the maxima of the two inputs. The result is not renumbered in any
 * manner. Although this is valid when just distinguishing marked and empty regions, often you will want to apply
 * gwy_nield_number_contiguous() or gwy_nield_flatten() aftewards.
 **/
void
gwy_nield_union(GwyNield *nield,
                GwyNield *union_with)
{
    if (!_gwy_nield_check_other(nield, union_with, FALSE))
        return;

    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    GwyNieldPrivate *priv = nield->priv, *oppriv = union_with->priv;
    gint *data = priv->data;
    const gint *opdata = oppriv->data;
    for (gsize i = 0; i < n; i++)
        data[i] = MAX(data[i], opdata[i]);
    gwy_nield_invalidate(nield);
}

/**
 * gwy_nield_intersect:
 * @nield: A number field.
 * @intersect_with:  Another field to intersect the number field with.
 *
 * Combines two number fields by grain intersection.
 *
 * More precisely, the resulting numbers are the minima of the two inputs. The result is not renumbered in any
 * manner. Although this is valid when just distinguishing marked and empty regions, often you will want to apply
 * gwy_nield_number_contiguous() or gwy_nield_flatten() aftewards.
 **/
void
gwy_nield_intersect(GwyNield *nield,
                    GwyNield *intersect_with)
{
    if (!_gwy_nield_check_other(nield, intersect_with, FALSE))
        return;

    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    GwyNieldPrivate *priv = nield->priv, *oppriv = intersect_with->priv;
    gint *data = priv->data;
    const gint *opdata = oppriv->data;
    for (gsize i = 0; i < n; i++)
        data[i] = MIN(data[i], opdata[i]);
    gwy_nield_invalidate(nield);
}

/* FIXME: Should we add options to immediately flatten or number-contiguous? */
/**
 * gwy_nield_merge:
 * @nield: A number field.
 * @operand: (nullable): Another field to intersect or union the number field with.
 * @operation: Logical operation to apply.
 *
 * Combines two number fields by grain logical operation.
 *
 * The function is a shorthand for doing either gwy_nield_union() or gwy_nield_intersect() depending on a
 * #GwyMergeType setting. If @operand is %NULL, @nield is left unmodified.
 *
 * Value outside the enum are allowed as @operation, meaning to do nothing. However, #GwyMergeType can be extended to
 * other logical operations, so you need to pass a value like -1 or %G_MAXINT.
 **/
void
gwy_nield_merge(GwyNield *nield,
                GwyNield *operand,
                GwyMergeType operation)
{
    if (operation == GWY_MERGE_UNION) {
        gwy_nield_union(nield, operand);
        return;
    }
    if (operation == GWY_MERGE_INTERSECTION) {
        gwy_nield_intersect(nield, operand);
        return;
    }
    if (operation == GWY_MERGE_COPY) {
        if (operand)
            gwy_nield_copy_data(operand, nield);
        return;
    }
    if (operation == GWY_MERGE_CLEAR) {
        gwy_nield_clear(nield);
        return;
    }
    if (operation == GWY_MERGE_FILL) {
        /* We might take the maximum of nield and 1, but that may be a too extreme interpretation. */
        gwy_nield_fill(nield, 1);
        return;
    }
    if (operation == GWY_MERGE_NOT) {
        gwy_nield_invert(nield);
        return;
    }

    if (!_gwy_nield_check_other(nield, operand, TRUE) || !operand)
        return;

    if (operation == GWY_MERGE_KEEP)
        return;

    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    GwyNieldPrivate *priv = nield->priv, *oppriv = operand->priv;
    gint *data = priv->data;
    const gint *opdata = oppriv->data;
    if (operation == GWY_MERGE_XOR) {
        /* Give the positive value of whichever input is the positive one. */
        for (gsize i = 0; i < n; i++)
            data[i] = data[i]*(opdata[i] <= 0) | opdata[i]*(data[i] <= 0);
    }
    else if (operation == GWY_MERGE_SUBTRACT) {
        /* Keep the value of @nield if the operand does not cause subtraction. */
        for (gsize i = 0; i < n; i++)
            data[i] = data[i]*(opdata[i] <= 0);
    }
    else if (operation == GWY_MERGE_REVSUBTRACT) {
        /* Keep the value of @operand if @nield does not cause subtraction. */
        for (gsize i = 0; i < n; i++)
            data[i] = opdata[i]*(data[i] <= 0);
    }
    else if (operation == GWY_MERGE_OUTSIDE) {
        for (gsize i = 0; i < n; i++)
            data[i] = (opdata[i] <= 0)*(data[i] <= 0);
    }
    else if (operation == GWY_MERGE_NAND) {
        for (gsize i = 0; i < n; i++)
            data[i] = (opdata[i] <= 0) | (data[i] <= 0);
    }
    else if (operation == GWY_MERGE_EQUAL) {
        /* Keep the value of the larger operand if both are set (a bit extreme interpretation). */
        for (gsize i = 0; i < n; i++) {
            gint a = data[i] > 0, b = opdata[i] > 0;
            data[i] = !a*!b + a*b*MAX(data[i], opdata[i]);
        }
    }
    else if (operation == GWY_MERGE_NCOPY) {
        for (gsize i = 0; i < n; i++)
            data[i] = (opdata[i] <= 0);
    }
    else if (operation == GWY_MERGE_CONVERSE) {
        /* Keep the value of @nield if it gives positive result by default. */
        for (gsize i = 0; i < n; i++) {
            gint a = data[i] > 0;
            data[i] = a*data[i] + !a*(opdata[i] <= 0);
        }
    }
    else if (operation == GWY_MERGE_IMPLIES) {
        /* Keep the value of @operand if it gives positive result by default. */
        for (gsize i = 0; i < n; i++) {
            gint b = opdata[i] > 0;
            data[i] = b*opdata[i] + !b*(data[i] <= 0);
        }
    }
    gwy_nield_invalidate(nield);
}

/**
 * gwy_nield_invert:
 * @nield: A number field.
 *
 * Swaps marked and empty regions in a number field.
 *
 * The result is flattened. All previously empty pixels are set to 1 and all previously marked to 0.
 **/
void
gwy_nield_invert(GwyNield *nield)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    GwyNieldPrivate *priv = nield->priv;
    gint *data = priv->data;
    gint m = 0;
    for (gsize i = 0; i < n; i++) {
        data[i] = (data[i] <= 0);
        m |= data[i];
    }
    set_cache_for_compact_nield(nield, m ? 1 : 0);
}

/**
 * gwy_nield_autocrop:
 * @nield: A number field.
 * @symmetrically: %TRUE to remove borders symmetrically, i.e the same number of pixels from left and right, and also
 *                 top and bottom. %FALSE to remove as many empty rows and columns as possible.
 * @left: (out) (optional): Location to store how many column were removed from the left, or %NULL.
 * @right: (out) (optional): Location to store how many column were removed from the right, or %NULL.
 * @up: (out) (optional): Location to store how many row were removed from the top, or %NULL.
 * @down: (out) (optional): Location to store how many row were removed from the bottom, or %NULL.
 *
 * Removes empty border rows and columns from a number field.
 *
 * If there are border rows and columns filled completely with non-positive values the size of the data field is
 * reduced, removing these rows or columns. Parameter @symmetrically controls whether the size reduction is maximum
 * possible or symmetrical.
 *
 * When there is no positive value in the field the field size is reduced to the smallest possible. It means 1×1 for
 * @symmetrical being %FALSE or odd original dimensions. If the original dimensions are even and @symmetrical %TRUE,
 * only the field is reduced to 2×2.
 *
 * Returns: %TRUE if the field size was reduced at all.  Detailed information about the reduction can be obtained from
 *          @left, @right, @up and @down.
 **/
gboolean
gwy_nield_autocrop(GwyNield *nield,
                   gboolean symmetrically,
                   guint *left,
                   guint *right,
                   guint *up,
                   guint *down)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), FALSE);
    gint xres = nield->xres, yres = nield->yres;
    gint firstcol = xres, firstrow = yres, lastrow = -1, lastcol = -1;
    const gint *data = nield->priv->data;

    for (gint i = 0; i < yres; i++) {
        const gint *row = data + i*xres;
        for (gint j = 0; j < xres; j++) {
            if (row[j] > 0) {
                if (G_UNLIKELY(i < firstrow))
                    firstrow = i;
                if (G_UNLIKELY(j < firstcol))
                    firstcol = j;
                if (G_UNLIKELY(i > lastrow))
                    lastrow = i;
                if (G_UNLIKELY(j > lastcol))
                    lastcol = j;
            }
        }
    }
    gwy_debug("first (%d,%d) last (%d,%d)", firstcol, firstrow, lastcol, lastrow);
    if (firstcol > lastcol) {
        /* Empty field. */
        g_assert(firstrow > lastrow);
        /* Anticipate the reduction to 2 for even-sized dimensions. */
        firstcol = lastcol = (xres - 1)/2;
        firstrow = lastrow = (yres - 1)/2;
        gwy_debug("empty first (%d,%d) last (%d,%d)", firstcol, firstrow, lastcol, lastrow);
    }
    if (symmetrically) {
        firstcol = MIN(firstcol, xres-1 - lastcol);
        lastcol = xres-1 - firstcol;
        firstrow = MIN(firstrow, yres-1 - lastrow);
        lastrow = yres-1 - firstrow;
        gwy_debug("symm first (%d,%d) last (%d,%d)", firstcol, firstrow, lastcol, lastrow);
    }
    lastcol++;
    lastrow++;

    if (left)
        *left = firstcol;
    if (right)
        *right = xres - lastcol;
    if (up)
        *up = firstrow;
    if (down)
        *down = yres - lastrow;

    gwy_debug("final %dx%d at (%d,%d) of %dx%d", lastcol-firstcol, lastrow-firstrow, firstcol, firstrow, xres, yres);
    if (firstcol == 0 && firstrow == 0 && lastcol == xres && lastrow == yres)
        return FALSE;

    gwy_nield_crop(nield, firstcol, firstrow, lastcol-firstcol, lastrow-firstrow);
    return TRUE;
}

/**
 * gwy_nield_compactify:
 * @nield: A number field.
 *
 * Compactifies the numbering of marked regions in a number field.
 *
 * Positive grain numbers will form an uninterrupted sequence from 1 to the return value. This is useful for removing
 * holes in grain numbers. Non-positive values are unchanged by this function.
 *
 * Returns: The new maximum grain number.
 **/
gint
gwy_nield_compactify(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);
    gint m = gwy_nield_max(nield);
    if (m < 2)
        return m;

    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    GwyNieldPrivate *priv = nield->priv;
    gint *data = priv->data;
    if ((guint)m < n/2)
        return compactify_dense(nield, m);

    /* Very scattered values. Use a hash table & sorting. */
    GArray *values = g_array_new(FALSE, FALSE, sizeof(guint));
    GHashTable *number_map = get_grain_numbers_hashtable(nield, values);
    for (guint i = 0; i < values->len; i++) {
        guint v = g_array_index(values, guint, i);
        g_hash_table_insert(number_map, GUINT_TO_POINTER(v), GUINT_TO_POINTER(i+1));
    }
    guint maxno = values->len;
    g_array_free(values, TRUE);

    for (gsize i = 0; i < n; i++) {
        guint v = data[i];
        if (v > 0) {
            gpointer key = GUINT_TO_POINTER(v);
            data[i] = GPOINTER_TO_UINT(g_hash_table_lookup(number_map, key));
        }
    }
    g_hash_table_destroy(number_map);

    set_cache_for_compact_nield(nield, maxno);
    return maxno;
}

/**
 * gwy_nield_flatten:
 * @nield: A number field.
 *
 * Flattens all values in a number field to 0 or 1.
 *
 * Returns: %TRUE if there are any positive values.
 **/
gboolean
gwy_nield_flatten(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), FALSE);
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    GwyNieldPrivate *priv = nield->priv;
    gint *data = priv->data;
    gint m = 0;
    for (gsize i = 0; i < n; i++) {
        data[i] = (data[i] > 0);
        m |= data[i];
    }
    set_cache_for_compact_nield(nield, m ? 1 : 0);
    return m;
}

static inline gint
finalise_grain_numbering(gssize *m, gint max_id,
                         gint *grains, gint n)
{
    /* Resolve remianing grain number links in map */
    for (gint i = 1; i <= max_id; i++)
        m[i] = m[m[i]];

    /* Compactify grain numbers */
    gint *mm = g_new0(gint, max_id + 1);
    gint id = 0;
    for (gint i = 1; i <= max_id; i++) {
        if (!mm[m[i]]) {
            id++;
            mm[m[i]] = id;
        }
        m[i] = mm[m[i]];
    }
    g_free(mm);

    /* Renumber grains. */
    for (gint i = 0; i < n; i++) {
        if (grains[i] > 0)
            grains[i] = m[grains[i]];
    }

    return id;
}

static inline gboolean
init_grain_numbering_list(IntList **m, gint xres, gint yres)
{
    gboolean must_free = FALSE;
    if (!*m) {
        *m = int_list_new(xres + yres);
        must_free = TRUE;
    }
    else
        (*m)->len = 0;

    int_list_add(*m, 0);

    return must_free;
}

/* Negative values are untouched. All positive values are renumbered based purely on continuity. The result has a
 * compact numbering. */
static gint
renumber_contiguous4(GwyNield *nield, IntList *m)
{
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gboolean must_free = init_grain_numbering_list(&m, xres, yres);

    /* Number grains with simple unidirectional grain number propagation, updating map m for later full grain join */
    /* OpenMP: This seems too simple and fast to parallelise reasonably. */
    gint *grains = nield->priv->data;
    gint max_id = 0;
    gint k = 0;
    for (gint i = 0; i < yres; i++) {
        gint current_grain_id = 0;
        for (gint j = 0; j < xres; j++, k++) {
            if (grains[k] > 0) {
                /* Grain number is kept from left neighbour unless it does not exist (a new number is assigned) or
                 * a join with top neighbour occurs (map m is updated). */
                gint upper;
                if (i && (upper = grains[k-xres]) > 0) {
                    if (!current_grain_id)
                        current_grain_id = upper;
                    else if (upper != current_grain_id) {
                        resolve_grain_map(m->data, upper, current_grain_id);
                        current_grain_id = m->data[upper];
                    }
                }
                if (!current_grain_id) {
                    current_grain_id = ++max_id;
                    int_list_add(m, current_grain_id);
                }
                grains[k] = current_grain_id;
            }
            else
                current_grain_id = 0;
        }
    }

    max_id = finalise_grain_numbering(m->data, max_id, grains, n);
    if (must_free)
        int_list_free(m);

    return max_id;
}

/* Negative values are untouched. All positive values are renumbered based purely on 8-continuity. The result has a
 * compact numbering. */
static gint
renumber_contiguous8(GwyNield *nield, IntList *m)
{
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gboolean must_free = init_grain_numbering_list(&m, xres, yres);

    /* Number grains with simple unidirectional grain number propagation, updating map m for later full grain join */
    /* OpenMP: This seems too simple and fast to parallelise reasonably. */
    gint *grains = nield->priv->data;
    gint max_id = 0;
    gint k = 0;
    for (gint i = 0; i < yres; i++) {
        gint current_grain_id = 0;
        for (gint j = 0; j < xres; j++, k++) {
            if (grains[k] > 0) {
                /* There are up to four pixels we need to check: left, upper-left, upper and upper-right. At most two
                 * are really distinct grains, but we can get up to three different grain numbers as one of them can
                 * come from before the merging. The map lookup and merging is trivial for already merged grains. So
                 * instead of complicating it, we just pretend they are all independent and resolve all in sequence. */
                gint other, nconnected = 0, connected[4];
                if (current_grain_id)
                    connected[nconnected++] = current_grain_id;
                if (i) {
                    if (j && (other = grains[k-xres-1]) > 0)
                        connected[nconnected++] = other;
                    if ((other = grains[k-xres]) > 0)
                        connected[nconnected++] = other;
                    if (j < xres-1 && (other = grains[k-xres+1]) > 0)
                        connected[nconnected++] = other;
                }

                if (nconnected == 0) {
                    // Start a new grain.
                    current_grain_id = ++max_id;
                    int_list_add(m, current_grain_id);
                }
                else {
                    // Resolve everything and connect to the lowest id. If nconnected is 1, the loop is no-op.
                    for (gint l = 1; l < nconnected; l++)
                        resolve_grain_map(m->data, connected[l-1], connected[l]);
                    // After merging, any would be OK, but connected[nconnected-1] is the most completely resolved.
                    current_grain_id = connected[nconnected-1];
                }
                grains[k] = current_grain_id;
            }
            else
                current_grain_id = 0;
        }
    }

    max_id = finalise_grain_numbering(m->data, max_id, grains, n);
    if (must_free)
        int_list_free(m);

    return max_id;
}

/* Negative values are untouched. Distinct positive values are preserved as distinct grains. Otherwise, positive
 * values are renumbered based on contiguity. The result has compact numbering.
 *
 * Meaning, if there are already multiple marked regions, this function splits them into contiguous pieces. If there
 * are not (or they should not be distinguished), use instead the simpler renumber_contiguous4(). */
static gint
renumber_contiguous_distinct(GwyNield *nield, IntList *m)
{
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gboolean must_free = init_grain_numbering_list(&m, xres, yres);

    /* Number grains with simple unidirectional grain number propagation, updating map m for later full grain join */
    /* OpenMP: This seems too simple and fast to parallelise reasonably. */
    gint *two_rows = g_new(gint, 2*xres), *this_row = two_rows, *prev_row = two_rows + xres;
    gint *grains = nield->priv->data;
    gint max_id = 0;
    gint k = 0;
    for (gint i = 0; i < yres; i++) {
        GWY_SWAP(gint*, prev_row, this_row);
        gwy_assign(this_row, grains + i*xres, xres);

        gint current_grain_id = 0;
        for (gint j = 0; j < xres; j++, k++) {
            gint orig;
            if ((orig = grains[k]) > 0) {
                /* Grain number is kept from upper or left neighbour, unless it does not exist or it has a different
                 * original grain number than the neighbour (a new number is assigned). If the pixel is connected to
                 * the upper neighbour, the map m needs to be updated. */
                gint orig_upper = (i ? prev_row[j] : 0);
                gint orig_left = (j ? this_row[j-1] : 0);
                gint new_upper = grains[k-xres];
                if (orig == orig_upper) {
                    if (!current_grain_id || orig != orig_left) {
                        /* Not connected to the left (in original numbering), but connected to the upper. Simply
                         * adopt the upper neighbour new grain id. */
                        current_grain_id = new_upper;
                    }
                    else if (new_upper != current_grain_id) {
                        /* Connected to both left and upper, but upper new grain id is not current_grain_id. Must
                         * merge the two pieces and update the map. */
                        resolve_grain_map(m->data, new_upper, current_grain_id);
                        current_grain_id = m->data[new_upper];
                    }
                    /* Connected to both left and upper and their new grain ids match, so everything is all right as
                     * it is. */
                }
                else {
                    /* Not connected up, still may or may not be connected left. If not connected to the left either
                     * (or there is no left), a new grain must be started. */
                    if (!current_grain_id || orig != orig_left) {
                        current_grain_id = ++max_id;
                        int_list_add(m, current_grain_id);
                    }
                }
                grains[k] = current_grain_id;
            }
            else
                current_grain_id = 0;
        }
    }

    max_id = finalise_grain_numbering(m->data, max_id, grains, n);
    g_free(two_rows);
    if (must_free)
        int_list_free(m);

    return max_id;
}

/**
 * gwy_nield_number_contiguous:
 * @nield: A number field.
 *
 * Renumbers regions in a number field based on contiguity.
 *
 * Each distinct 4-connected region will have a unique number, and these will form an uninterrputed sequence starting
 * from 1. Non-positive values are unchanged.
 *
 * Returns: The new maximum grain number.
 **/
gint
gwy_nield_number_contiguous(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);
    if (NCTEST(nield, IS_NUM_CONTIGUOUS)) {
        g_assert(NCTEST(nield, MAX));
        return NCVAL(nield, MAX);
    }
    /* The default behaviour of renumber_contiguous4() is all we need here. */
    gint maxgno = renumber_contiguous4(nield, NULL);
    set_cache_for_compact_nield(nield, maxgno);
    nield->priv->cached |= NCBIT(IS_NUM_CONTIGUOUS);
    return maxgno;
}

/**
 * gwy_nield_number_contiguous_periodic:
 * @nield: A number field.
 *
 * Renumbers regions in a number field based on periodic contiguity.
 *
 * An alternative version of gwy_nield_number_contiguous() which considers the number field as peroidic in the plane,
 * i.e. grains can also connect through the opposite edges.
 *
 * Returns: The new maximum grain number.
 **/
gint
gwy_nield_number_contiguous_periodic(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);

    gint maxgno = gwy_nield_number_contiguous(nield);
    if (maxgno < 2)
        return maxgno;

    gssize *m = g_new0(gssize, maxgno+1);
    for (gint j = 0; j <= maxgno; j++)
        m[j] = j;

    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gboolean merged_anything = FALSE;
    gint *grains = nield->priv->data;

    for (gint i = 0; i < xres; i++) {
        gint gno1 = grains[i], gno2 = grains[i + (yres-1)*xres];
        if (gno1 && gno2 && gno1 != gno2) {
            resolve_grain_map(m, gno1, gno2);
            merged_anything = TRUE;
        }
    }

    for (gint i = 0; i < yres; i++) {
        gint gno1 = grains[i*xres], gno2 = grains[i*xres + xres-1];
        if (gno1 && gno2 && gno1 != gno2) {
            resolve_grain_map(m, gno1, gno2);
            merged_anything = TRUE;
        }
    }

    if (merged_anything)
        maxgno = finalise_grain_numbering(m, maxgno, grains, n);

    g_free(m);
    set_cache_for_compact_nield(nield, maxgno);

    return maxgno;
}

/**
 * gwy_nield_split_noncontiguous:
 * @nield: A number field.
 *
 * Splits regions in a number field based on contiguity.
 *
 * Numbered regions formed by multiple disconnected parts are split, giving each part a distinct number. Touching
 * regions with distinct numbers are not merged; use gwy_nield_number_contiguous() if you want also merging, not just
 * splitting. Non-positive values are unchanged.
 *
 * Returns: The new maximum grain number.
 **/
gint
gwy_nield_split_noncontiguous(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);
    /* The default behaviour of renumber_contiguous_distinct() is all we need here. */
    gint maxgno = renumber_contiguous_distinct(nield, NULL);
    set_cache_for_compact_nield(nield, maxgno);
    return maxgno;
}

/**
 * gwy_nield_grain_numbers:
 * @nield: A number field.
 * @ngrains: (out): Location to store the number of distinct grain numbers.
 *
 * Gets all the distinct positive grain numbers in a number field.
 *
 * Returns: (transfer full) (array length=ngrains) (nullable):
 *          Newly allocated array with distinct grain numbers, in ascending order.
 **/
gint*
gwy_nield_grain_numbers(GwyNield *nield,
                        gint *ngrains)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);
    g_return_val_if_fail(ngrains, NULL);
    gint m = gwy_nield_max(nield);
    if (m == 1) {
        gint *retval = g_new(gint, 1);
        retval[0] = 1;
        *ngrains = 1;
        return retval;
    }
    else if (m <= 0) {
        *ngrains = 0;
        return NULL;
    }

    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    GwyNieldPrivate *priv = nield->priv;
    gint *data = priv->data;
    if ((guint)m < n/2) {
        /* Moderately scattered values. Pay O(n) memory for a very simple implementation. */
        gint *number_map = g_new0(gint, m+1);
        for (gsize i = 0; i < n; i++) {
            if (data[i] > 0)
                number_map[data[i]] = 1;
        }
        gint ng = 0;
        for (gint i = 1; i < m; i++) {
            if (number_map[i])
                ng++;
        }
        gint *retval = g_new(gint, ng);
        ng = 0;
        for (gint i = 1; i < m; i++) {
            if (number_map[i])
                retval[ng++] = i;
        }
        g_free(number_map);
        *ngrains = ng;
        return retval;
    }

    GArray *values = g_array_new(FALSE, FALSE, sizeof(guint));
    GHashTable *number_map = get_grain_numbers_hashtable(nield, values);
    g_hash_table_destroy(number_map);
    *ngrains = values->len;
    return (gint*)g_array_free(values, FALSE);
}

const gint*
_gwy_nield_ensure_bboxes(GwyNield *nield)
{
    GwyNieldPrivate *priv = nield->priv;
    if (NCTEST(nield, BBOXES)) {
        g_assert(NCTEST(nield, MAX));
        return &g_array_index(priv->bboxes, gint, 0);
    }

    nield->priv->cached |= NCBIT(BBOXES);
    if (!priv->bboxes)
        priv->bboxes = g_array_new(FALSE, FALSE, sizeof(gint));
    GArray *bboxes = priv->bboxes;

    gint ngrains = gwy_nield_max(nield);
    if (ngrains < 1) {
        g_array_set_size(bboxes, 4);
        gint *bb = &g_array_index(bboxes, gint, 0);
        init_bbox_ranges(bb, 0);
        return bb;
    }

    gint xres = nield->xres, yres = nield->yres;
    g_array_set_size(bboxes, 4*(ngrains + 1));
    gint *bb = &g_array_index(bboxes, gint, 0);
    const gint *data = priv->data;

    init_bbox_ranges(bb, ngrains);
    for (gint i = 0; i < yres; i++) {
        const gint *drow = data + i*xres;
        for (gint j = 0; j < xres; j++) {
            gint grain_no = MAX(drow[j], 0);
            extend_bbox_range(bb, grain_no, j, i);
        }
    }
    for (guint i = 0; i <= ngrains; i++)
        transform_range_to_bbox(bb + 4*i);

    return bb;
}

/* FIXME: This is not introspectable because you cannot specify the array size as anything else than the exact value
 * of a function argument: Not twice the number, not the product of two parameters, nor any other expression. So all
 * functions working with tuples in C need a special wrapper for introspection. */
/**
 * gwy_nield_bounding_boxes:
 * @nield: A number field.
 * @maxgno: (out) (nullable):
 *          Location to store the maximum grain number. Possibly %NULL if you know the maximum grain number.
 *
 * Finds bounding boxes of all grains in a number field.
 *
 * The zeroth element of the returned array does not correspond to any grain; grain numbers start from 1.
 *
 * The rectangles are stored as quadruples of indices: (column, row, width, height). Empty grains, which can occur
 * when grain numbers have holes, have negative bounding box width and height.
 *
 * The function works for non-contiguous numbered regions and gives the overall bounding boxes for all the pieces.
 *
 * Returns: (transfer full): Newly allocated array with 4×(@maxgno+1) values giving the bounding boxes.
 **/
gint*
gwy_nield_bounding_boxes(GwyNield *nield,
                         gint *maxgno)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);
    const gint *bboxes = _gwy_nield_ensure_bboxes(nield);
    gint ngrains = gwy_nield_max(nield);
    ngrains = MAX(ngrains, 0);
    if (maxgno)
        *maxgno = ngrains;
    return g_memdup2(bboxes, 4*(ngrains+1)*sizeof(gint));
}

/* Find bounding box from projection. */
static void
make_bbox_from_projection(const gboolean *projection, gint len,
                          gint *pos, gint *size)
{
    gint bl, br;

    if (projection[0] && projection[len-1]) {
        /* At most one gap in the middle, possibly entire width filled. */
        for (bl = 0; bl < len && projection[bl]; bl++)
            ;
        /* If all projection pixels are covered no smaller bbox is possible. */
        if (bl == len) {
            *pos = 0;
            *size = len;
        }
        else {
            /* There must be a gap. */
            for (br = len-1; projection[br]; br--)
                ;
            *pos = br+1;
            *size = bl + len-1-br;
        }
    }
    else {
        /* One contiguous segment, not covering the entire line.  So neither cycle needs an explicit length
         * stop-condition. */
        for (bl = 0; !projection[bl]; bl++)
            ;
        for (br = len-1; !projection[br]; br--)
            ;
        *pos = bl;
        *size = br+1 - bl;
    }
}

/**
 * gwy_nield_bounding_boxes_periodic:
 * @nield: A number field.
 * @maxgno: (out) (nullable):
 *          Location to store the maximum grain number. Possibly %NULL if you know the maximum grain number.
 *
 * Find bounding boxes of all grains in a number field with periodic boundary conditions.
 *
 * The zeroth element of @bboxes does not correspond to any grain; grain numbers start from 1. The rectangles are
 * stored as quadruples of indices: (column, row, width, height).
 *
 * The row and column always lie inside the the image.  However, width and height may specify an area which sticks
 * outside.  In this case periodicity needs to be taken into account.
 *
 * Returns: (transfer full): Newly allocated array with 4×(@maxgno+1) values giving the bounding boxes.
 **/
gint*
gwy_nield_bounding_boxes_periodic(GwyNield *nield,
                                  gint *maxgno)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);

    gint ngrains;
    gint *bboxes = gwy_nield_bounding_boxes(nield, &ngrains);
    g_return_val_if_fail(bboxes, NULL);
    if (maxgno)
        *maxgno = ngrains;

    /* Grains passing through the boundary will have full image width or height according to the normal bounding box
     * algorithm.  If there are none we can avoid the slow path. */
    gint xres = nield->xres, yres = nield->yres;
    gboolean *todograins = g_new0(gboolean, ngrains+1);
    gint ntodo = 0;
    for (gint gno = 1; gno <= ngrains; gno++) {
        // Importantly, emtpy grains fail this.
        if (bboxes[4*gno + 2] == xres || bboxes[4*gno + 3] == yres) {
            todograins[gno] = TRUE;
            ntodo++;
        }
        else {
            gwy_debug("normal bbox[%d] %dx%d at (%d,%d)",
                      gno,
                      bboxes[4*gno + 2], bboxes[4*gno + 3],
                      bboxes[4*gno + 0], bboxes[4*gno + 1]);
        }
    }
    gwy_debug("after normal bboxing %d possibly cross-border grains remaining", ntodo);
    if (!ntodo) {
        g_free(todograins);
        return bboxes;
    }

    /* Now try virtually shifting the image periodically by half (horizontally (0), vertically (1) and both (2)) and
     * find the bounding boxes again. This catches all grains that simply cross the boundary but are not actually too
     * large. */
    const gint *grains = nield->priv->data;
    gint *sbboxes = g_new(gint, 4*(ngrains + 1));
    for (gint k = 0; k < 3 && ntodo; k++) {
        init_bbox_ranges(sbboxes, ngrains);

        for (gint i = 0; i < yres; i++) {
            const gint *row = grains + i*xres;
            gint ii = (k == 0) ? i : (i >= yres/2 ? i : i + yres);
            gint gno;
            if (k == 1) {
                /* Horizontal lines are preserved. */
                for (gint j = 0; j < xres; j++) {
                    if (todograins[gno = row[j]])
                        extend_bbox_range(sbboxes, gno, j, ii);
                }
            }
            else {
                /* Horizontal lines are split and shifted. */
                for (gint j = 0; j < xres/2; j++) {
                    if (todograins[gno = row[j]])
                        extend_bbox_range(sbboxes, gno, j + xres, ii);
                }
                for (gint j = xres/2; j < xres; j++) {
                    if (todograins[gno = row[j]])
                        extend_bbox_range(sbboxes, gno, j, ii);
                }
            }
        }

        for (gint gno = 1; gno <= ngrains; gno++) {
            if (!todograins[gno])
                continue;

            transform_range_to_bbox(sbboxes + 4*gno);
            if (sbboxes[4*gno + 2] == xres || sbboxes[4*gno + 3] == yres)
                continue;

            /* We were able to find shifted finite bbox for grain @i. Furthermore if the bbox is finite now it must
             * begin inside the unshifted half -- otherwise it would lie *entirely* within the shifted half and be
             * found by the normal algorithm. */
            g_assert(sbboxes[4*gno + 0] < xres);
            g_assert(sbboxes[4*gno + 1] < yres);
            gwy_assign(bboxes + 4*gno, sbboxes + 4*gno, 4);
            gwy_debug("shifted-%d bbox[%d] %dx%d at (%d,%d)",
                      k, gno, bboxes[4*gno + 2], bboxes[4*gno + 3], bboxes[4*gno + 0], bboxes[4*gno + 1]);
            todograins[gno] = FALSE;
            ntodo--;
        }
    }
    g_free(sbboxes);

    gwy_debug("after shifted bboxing %d possibly cross-border grains remaining", ntodo);
    if (!ntodo) {
        g_free(todograins);
        return bboxes;
    }

    /* For any remaining grains, hopefully relatively few, find horizontal and vertical projections. */
    gboolean *proj_storage = g_new0(gboolean, ntodo*(xres + yres));
    gboolean **xprojection = g_new0(gboolean*, 2*(ngrains + 1));
    gboolean **yprojection = xprojection + ngrains+1;
    gint todograin_idx = 0;
    for (gint gno = 1; gno <= ngrains; gno++) {
        if (!todograins[gno])
            continue;

        xprojection[gno] = proj_storage + todograin_idx*(xres + yres);
        yprojection[gno] = proj_storage + todograin_idx*(xres + yres) + xres;
        todograin_idx++;
    }
    g_free(todograins);

    for (gint i = 0; i < yres; i++) {
        const gint *row = grains + i*xres;
        for (gint j = 0; j < xres; j++) {
            gint gno;
            if (xprojection[gno = row[j]]) {
                xprojection[gno][j] = TRUE;
                yprojection[gno][i] = TRUE;
            }
        }
    }

    for (gint gno = 1; gno <= ngrains; gno++) {
        if (!xprojection[gno])
            continue;

        gint *bbox = bboxes + 4*gno;
        make_bbox_from_projection(xprojection[gno], xres, bbox + 0, bbox + 2);
        make_bbox_from_projection(yprojection[gno], yres, bbox + 1, bbox + 3);
    }
    g_free(xprojection);
    g_free(proj_storage);

    return bboxes;
}

// The returned array is technically writable, but no caller has any business modifying it.
const guint*
_gwy_nield_ensure_sizes(GwyNield *nield)
{
    GwyNieldPrivate *priv = nield->priv;
    if (NCTEST(nield, SIZES)) {
        g_assert(NCTEST(nield, MAX));
        return &g_array_index(priv->sizes, guint, 0);
    }

    nield->priv->cached |= NCBIT(SIZES);
    if (!priv->sizes)
        priv->sizes = g_array_new(FALSE, FALSE, sizeof(guint));
    GArray *sizes = priv->sizes;

    gint ngrains = gwy_nield_max(nield);
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    if (ngrains < 1) {
        g_array_set_size(sizes, 1);
        g_array_index(sizes, guint, 0) = n;
        return &g_array_index(priv->sizes, guint, 0);
    }

    g_array_set_size(sizes, ngrains+1);
    guint *sz = &g_array_index(sizes, guint, 0);
    gwy_clear(sz, ngrains+1);
    const gint *data = nield->priv->data;

    for (gsize i = 0; i < n; i++) {
        gint grain_no = MAX(data[i], 0);
        sz[grain_no]++;
    }
    return sz;
}

/**
 * gwy_nield_sizes:
 * @nield: A number field.
 * @maxgno: (out) (nullable):
 *          Location to store the maximum grain number. Possibly %NULL if you know the maximum grain number.
 *
 * Computes the numbers of pixels of all grains in a number field.
 *
 * The zeroth element of the returned array does not correspond to any grain; grain numbers start from 1.
 *
 * Returns: (transfer full): Newly allocated array with (@maxgno+1) values giving the sizes.
 **/
guint*
gwy_nield_sizes(GwyNield *nield,
                gint *maxgno)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);
    const guint *sizes = _gwy_nield_ensure_sizes(nield);
    gint ngrains = gwy_nield_max(nield);
    ngrains = MAX(ngrains, 0);
    if (maxgno)
        *maxgno = ngrains;
    return g_memdup2(sizes, (ngrains+1)*sizeof(guint));
}

static void
close_rectangles(gint *rectwidths, gint from, gint to, gint *wmax, gint *hmax)
{
    gint hm = 0, wm = 0, areamax = 0;

    /* Ignore the zeroth element. */
    from = MAX(from, 1);
    for (gint h = from; h <= to; h++) {
        if (rectwidths[h] * h > areamax) {
            areamax = rectwidths[h] * h;
            hm = h;
            wm = rectwidths[h];
        }
        rectwidths[h] = 0;
    }

    *hmax = hm;
    *wmax = wm;
}

static inline void
remember_largest_rectangle(gint *iboxes, gint gno,
                           gint j, gint i, gint wmax, gint hmax)
{
    if (gno <= 0)
        return;

    iboxes += 4*gno;
    if (hmax*wmax > iboxes[2]*iboxes[3]) {
        iboxes[0] = j - wmax;
        iboxes[1] = i;
        iboxes[2] = wmax;
        iboxes[3] = hmax;
    }
}

/**
 * gwy_nield_inscribed_boxes:
 * @nield: A number field.
 * @maxgno: (out) (nullable):
 *          Location to store the maximum grain number. Possibly %NULL if you know the maximum grain number.
 *
 * Find maximum-area inscribed boxes of all grains in a number field.
 *
 * The zeroth element of the returned array does not correspond to any grain and does not contain useful information;
 * grain numbers start from 1. The rectangles are stored as quadruples of indices: (column, row, width, height). Empty
 * grains, which can occur when grain numbers have holes, have negative bounding box width and height.
 *
 * The function works for non-contiguous numbered regions. One of the pieces will contain the maximum rectangle.
 *
 * Returns: (transfer full): Newly allocated array with 4×(@maxgno+1) values giving the inscribed boxes.
 **/
gint*
gwy_nield_inscribed_boxes(GwyNield *nield,
                          gint *maxgno)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);
    gint ngrains = gwy_nield_max(nield);
    if (ngrains < 1) {
        gint *iboxes = g_new0(gint, 4);
        init_bbox_ranges(iboxes, 0);
        if (maxgno)
            *maxgno = 0;
        return iboxes;
    }

    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    const gint *data = nield->priv->data;

    /* Pass 1.  Each value in colsizes[] will be filled with how may pixels we we can extend this one while keeping in
     * the same grain.  One means just the single pixels.
     *
     * Unlike in the standard presentation of the algorithm, we go by row for good data locality. */
    gint *colsizes = g_new0(gint, n);
    gint *csrow = colsizes + (yres - 1)*xres;
    const gint *row = data + (yres - 1)*xres;
    for (gint j = 0; j < xres; j++)
        csrow[j] = (row[j] > 0);

    for (gint i = yres-1; i; i--) {
        /* These rows we set, reading from the row below. */
        csrow = colsizes + (i-1)*xres;
        row = data + (i-1)*xres;
        for (gint j = 0; j < xres; j++) {
            if (row[j] > 0) {
                if (row[j] == row[xres + j])
                    csrow[j] = csrow[xres + j] + 1;
                else
                    csrow[j] = 1;
            }
        }
    }

    /* Pass 2.  Go from the top and close rectangles, remembering the maximum rectangle for each grain number. */
    gint *iboxes = g_new0(gint, 4*(ngrains + 1));
    init_bbox_ranges(iboxes, ngrains);

    gint *rectwidths = g_new0(gint, yres);
    gint maxopen = 0, hmax, wmax;
    for (gint i = 0; i < yres; i++) {
        gint gno = 0;
        csrow = colsizes + i*xres;
        row = data + i*xres;
        gwy_clear(rectwidths, maxopen);
        maxopen = 0;
        for (gint j = 0; j < xres; j++) {
            gint h = csrow[j];
            /* We hit a new grain -- the algorithm works even with touching grains -- or empty space. */
            if (row[j] != gno) {
                close_rectangles(rectwidths, 0, maxopen, &wmax, &hmax);
                remember_largest_rectangle(iboxes, gno, j, i, wmax, hmax);
            }
            else {
                /* Grain continuation.  Close rectangles higher than h (up to maxopen). */
                close_rectangles(rectwidths, h+1, maxopen, &wmax, &hmax);
                remember_largest_rectangle(iboxes, gno, j, i, wmax, hmax);
            }
            gno = row[j];
            if (gno) {
                /* Open or extend all rectangles at most h high.  Both is realised by adding 1 because closed ones are
                 * zeros. */
                for (gint k = 1; k <= h; k++)
                    rectwidths[k]++;
                maxopen = h;
            }
            else
                maxopen = 0;
        }

        /* Hitting the end of row is the same as hitting empty space. */
        close_rectangles(rectwidths, 0, maxopen, &wmax, &hmax);
        remember_largest_rectangle(iboxes, gno, xres, i, wmax, hmax);
    }

    g_free(rectwidths);
    g_free(colsizes);

    if (maxgno)
        *maxgno = ngrains;

    return iboxes;
}

static inline void
add_boundary_grain(IntList *bpixels, guint *bindex, gint gno, gint k)
{
    int_list_add(bpixels, gno);
    int_list_add(bpixels, k);
    bindex[gno]++;
}

/**
 * gwy_nield_boundary_pixels:
 * @nield: A number field.
 * @bindex: Array of size at least @maxgno+2.  It will be filled with block starts in the returned array.
 * @from_border: %TRUE to consider image edges to be grain boundaries.
 *
 * Enumerates boundary pixels of all grains in a number field.
 *
 * Use gwy_nield_max() to obtain @maxgno and allocate correct @bindex.
 *
 * The returned array contains pixel indices of grain boundaries, concatenated all one after another. In each block,
 * the indices are enumerated in ascending order.
 *
 * The block for grain with number @i starts at position @bindex[@i] and ends one before position @bindex[@i+1] where
 * the next block starts. The end is also stored for the last block, which does not have any next block, and gives the
 * total size of the returned array.
 *
 * Boundary pixels are considered in the usual 4-connected metric.
 *
 * The boundary of the no-grain area (non-positive values) is not computed. Therefore, the first two elements of
 * @bindex will be always zeros.
 *
 * Returns: (transfer full):
 *          A newly allocated array of size given by @bindex[@maxgno+1].
 **/
gint*
gwy_nield_boundary_pixels(GwyNield *nield,
                          guint *bindex,
                          gboolean from_border)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);
    g_return_val_if_fail(bindex, NULL);

    gint ngrains = gwy_nield_max(nield);
    ngrains = MAX(ngrains, 0);
    gwy_clear(bindex, ngrains+2);
    if (ngrains < 1)
        return g_new0(gint, 2);

    gint xres = nield->xres, yres = nield->yres;
    const gint *grains = nield->priv->data;
    IntList *bpixels = int_list_new(ngrains+1);
    gboolean wide = xres > 1;

    if (yres == 1) {
        gint gno, j = 0;
        const gint *row = grains;
        if ((gno = row[j]) > 0 && (from_border | (wide && (gno ^ row[j+1]))))
            add_boundary_grain(bpixels, bindex, gno, j);
        for (j = 1; j < xres-1; j++) {
            if ((gno = row[j]) > 0 && (from_border | (gno ^ row[j-1]) | (gno ^ row[j+1])))
                add_boundary_grain(bpixels, bindex, gno, j);
        }
        if (wide) {
            j = xres-1;
            if ((gno = row[j]) > 0 && (from_border | (wide && (gno ^ row[j-1]))))
                add_boundary_grain(bpixels, bindex, gno, j);
        }
    }
    else {
        /* First row. */
        gint gno, i = 0, j = 0;
        const gint *row = grains + i*xres;
        const gint *gnext = row + xres;
        const gint *gprev;
        if ((gno = row[j]) > 0 && (from_border | (wide && (gno ^ row[j+1])) | (gno ^ gnext[j])))
            add_boundary_grain(bpixels, bindex, gno, i*xres + j);
        for (j = 1; j < xres-1; j++) {
            if ((gno = row[j]) > 0 && (from_border | (gno ^ row[j-1]) | (gno ^ row[j+1]) | (gno ^ gnext[j])))
                add_boundary_grain(bpixels, bindex, gno, i*xres + j);
        }
        if (wide) {
            j = xres-1;
            if ((gno = row[j]) > 0 && (from_border | (wide && (gno ^ row[j-1])) | (gno ^ gnext[j])))
                add_boundary_grain(bpixels, bindex, gno, i*xres + j);
        }

        /* Middle rows - we do not have to care abound borders, except for the first and last pixels. */
        for (i = 1; i < yres-1; i++) {
            row = grains + i*xres;
            gprev = row - xres;
            gnext = row + xres;
            j = 0;
            if ((gno = row[j]) > 0 && (from_border | (wide && (gno ^ row[j+1])) | (gno ^ gprev[j]) | (gno ^ gnext[j])))
                add_boundary_grain(bpixels, bindex, gno, i*xres + j);
            for (j = 1; j < xres-1; j++) {
                if ((gno = row[j]) > 0 && ((gno ^ row[j-1]) | (gno ^ row[j+1]) | (gno ^ gprev[j]) | (gno ^ gnext[j])))
                    add_boundary_grain(bpixels, bindex, gno, i*xres + j);
            }
            if (wide) {
                j = xres-1;
                if ((gno = row[j]) > 0 && (from_border | (wide && (gno ^ row[j-1])) | (gno ^ gprev[j]) | (gno ^ gnext[j])))
                    add_boundary_grain(bpixels, bindex, gno, i*xres + j);
            }
        }

        /* Last row. */
        i = yres-1;
        row = grains + i*xres;
        gprev = row - xres;
        j = 0;
        if ((gno = row[j]) > 0 && (from_border | (wide && (gno ^ row[j+1])) | (gno ^ gprev[j])))
            add_boundary_grain(bpixels, bindex, gno, i*xres + j);
        for (j = 1; j < xres-1; j++) {
            if ((gno = row[j]) > 0 && (from_border | (gno ^ row[j-1]) | (gno ^ row[j+1]) | (gno ^ gprev[j])))
                add_boundary_grain(bpixels, bindex, gno, i*xres + j);
        }
        if (wide) {
            j = xres-1;
            if ((gno = row[j]) > 0 && (from_border | (wide && (gno ^ row[j-1])) | (gno ^ gprev[j])))
                add_boundary_grain(bpixels, bindex, gno, i*xres + j);
        }
    }

    /* Sum and rewind the index. */
    gwy_accumulate_counts(bindex, ngrains+1, TRUE);

    /* Fill the result using remembered bpixels. */
    gint *boundaries = g_new(gint, bindex[ngrains+1]);
    for (guint i = 0; i < bpixels->len; i += 2) {
        gint gno = bpixels->data[i];
        gint k = bpixels->data[i+1];
        boundaries[bindex[gno]++] = k;
    }
    int_list_free(bpixels);
    for (guint i = ngrains+1; i > 0; i--)
        bindex[i] = bindex[i-1];
    bindex[0] = 0;

    return boundaries;
}

static inline gboolean
mark_nonsimple(gint *grain_neighbours, gboolean *unbound_vgrains,
               gint vgno, gint gno)
{
    if (!grain_neighbours[vgno])
        grain_neighbours[vgno] = gno;
    else if (grain_neighbours[vgno] != gno) {
        unbound_vgrains[vgno] = TRUE;
        return TRUE;
    }
    return FALSE;
}

static gboolean
have_any_voids(const gint *unbound_vgrains, gint nvgrains)
{
    for (gint gno = 1; gno <= nvgrains; gno++) {
        if (!unbound_vgrains[gno])
            return TRUE;
    }
    return FALSE;
}

/**
 * gwy_nield_fill_voids:
 * @nield: A number field.
 * @nonsimple: Pass %TRUE to fill also voids that are not simple-connected (e.g. ring-like).  This can result in grain
 *             merging if a small grain is contained within a void. Pass %FALSE to fill only simple-connected holes.
 *
 * Fills voids in grains in a number field.
 *
 * Voids are regions of non-positive numbers in @nield from which no path exists through other non-positive values to
 * the number field boundary. The paths are considered in 8-connectivity because grains themselves are considered in
 * 4-connectivity.
 *
 * When @nonsimple is %FALSE, the total number of grains dos not change, only grain areas. When it is %TRUE, some
 * grains may disappear. In either case, the function does not attempt to produce any reasonable grain numbering.
 * The result works as a mask, but needs renumbering for any grain operation.
 *
 * Returns: %TRUE if @nield has changed.
 **/
gboolean
gwy_nield_fill_voids(GwyNield *nield,
                     gboolean nonsimple)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), FALSE);
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gboolean onlysimple = !nonsimple;

    GwyNield *voids = gwy_nield_copy(nield);
    gwy_nield_invert(voids);
    gint nvgrains = renumber_contiguous8(voids, NULL);
    if (nvgrains < 1) {
        g_object_unref(voids);
        return FALSE;
    }
    gint *vgrains = voids->priv->data;

    // After renumber_contiguous8() on the void (inverted) mask, voids are exactly bound grains, i.e. grains that do
    // not touch any image edge. So if !onlysimple, find which grains touch edges and we are done.
    gboolean *unbound_vgrains = g_new0(gboolean, nvgrains+1);
    for (gint i = 0; i < xres; i++) {
        unbound_vgrains[vgrains[i]] = TRUE;
        unbound_vgrains[vgrains[xres*(yres - 1) + i]] = TRUE;
    }
    for (gint j = 0; j < yres; j++) {
        unbound_vgrains[vgrains[j*xres]] = TRUE;
        unbound_vgrains[vgrains[j*xres + xres-1]] = TRUE;
    }

    if (!have_any_voids(unbound_vgrains, nvgrains)) {
        g_free(unbound_vgrains);
        g_object_unref(voids);
        return FALSE;
    }

    if (onlysimple) {
        /* If a bound grain touches more than one grain (normal, not inverted), it is not simply connected. So for
         * onlysimple, we exclude such grains. Do it by remembering the id of the first touching grain and then
         * checking if everything else it touches has the same id. Immediately mark a non-simple grain as ‘unbound’.
         * This is an abuse of unbound_vgrains[], but just need a single flag what not to fill… */
        gint *vgrain_neighbours = g_new0(gint, nvgrains+1);
        gwy_nield_number_contiguous(nield);
        gint *data = nield->priv->data;

        for (gint i = 0; i < yres; i++) {
            for (gint j = 0; j < xres; j++) {
                gint k = i*xres + j;
                gint vgno, gno;
                if ((vgno = vgrains[k]) <= 0 || unbound_vgrains[vgno])
                    continue;

                if (i
                    && (gno = data[k-xres]) > 0
                    && mark_nonsimple(vgrain_neighbours, unbound_vgrains, vgno, gno))
                    continue;
                if (j
                    && (gno = data[k-1]) > 0
                    && mark_nonsimple(vgrain_neighbours, unbound_vgrains, vgno, gno))
                    continue;
                if (j < xres-1
                    && (gno = data[k+1]) > 0
                    && mark_nonsimple(vgrain_neighbours, unbound_vgrains, vgno, gno))
                    continue;
                if (i < yres-1
                    && (gno = data[k+xres]) > 0
                    && mark_nonsimple(vgrain_neighbours, unbound_vgrains, vgno, gno))
                    continue;
            }
        }
        g_free(vgrain_neighbours);
    }

    if (!have_any_voids(unbound_vgrains, nvgrains)) {
        g_object_unref(voids);
        return FALSE;
    }

    gint *data = nield->priv->data;
    for (gsize k = 0; k < n; k++)
        data[k] = (data[k] > 0 || !unbound_vgrains[vgrains[k]]);

    g_free(unbound_vgrains);
    g_object_unref(voids);
    gwy_nield_invalidate(nield);

    return TRUE;
}

/**
 * fill_grain:
 * @nield: A number field.
 * @col: Column inside a grain.
 * @row: Row inside a grain.
 * @nindices: Where the number of points in the grain at (@col, @row) should be stored.
 *
 * Finds all the points belonging to the contiguous grain at (@col, @row).
 *
 * Returns: A newly allocated array of indices of grain points in @dfield's data, the size of the list is returned in
 *          @nindices.
 **/
static gint*
enumerate_grain_indices(GwyNield *nield,
                        gint col, gint row,
                        gint *nindices)
{
    gint xres = nield->xres, yres = nield->yres;
    gint n = xres*yres;
    gint initial = row*xres + col;
    gint *data = nield->priv->data;
    g_return_val_if_fail(data[initial] > 0, NULL);

    /* Check for a single-pixel grain. */
    if (is_single_pixel_grain(nield, col, row)) {
        gint *indices = g_new(gint, 1);
        indices[0] = initial;
        *nindices = 1;
        return indices;
    }

    gint *visited = g_new0(gint, n);
    gint count = fill_one_grain(nield, col, row, 1, visited, NULL, NULL);
    gint *indices = g_new(gint, count);
    gint j = 0;
    for (gint i = 0; i < n; i++) {
        if (visited[i])
            indices[j++] = i;
    }
    g_free(visited);

    *nindices = count;
    return indices;
}

/**
 * fill_one_grain:
 * @nield: A number field.
 * @col: Column inside a grain.
 * @row: Row inside a grain.
 * @visited: An array @col x @row that contain zeroes in empty space and yet unvisited grains.  Current grain will be
 *           filled with @fill_with.
 * @fill_with: Value to fill current grain with.
 * @listv: An integer list scratch space, possibly %NULL for one-shot use.
 * @listh: An integer list scratch space, possibly %NULL for one-shot use.
 *
 * Fill a single grain using flodd fill.
 *
 * The @visited, @listv, and @listh buffers are recyclable between calls so they don not have to be allocated and freed
 * for each grain, speeding up sequential grain processing. Generally, this function itself does not allocate or free
 * any memory (unless @listv and @listh are passed as %NULL).
 *
 * Returns: The number of pixels in the grain.
 **/
static gint
fill_one_grain(GwyNield *nield,
               gint col, gint row,
               gint fill_with,
               gint *visited,
               IntList *listv,
               IntList *listh)
{
    gint xres = nield->xres, yres = nield->yres;
    gint n = xres*yres;
    gint initial = row*xres + col;
    const gint *data = nield->priv->data;

    /* Check for a single point. */
    gint count = 1;
    visited[initial] = fill_with;
    if (is_single_pixel_grain(nield, col, row))
        return count;

    gboolean free_listv = !listv;
    if (!listv)
        listv = int_list_new(64);
    else
        listv->len = 0;
    int_list_add(listv, initial);
    int_list_add(listv, initial);

    gboolean free_listh = !listh;
    if (!listh)
        listh = int_list_new(64);
    else
        listh->len = 0;
    int_list_add(listh, initial);
    int_list_add(listh, initial);

    gint look_for = data[initial];
    while (listv->len) {
        /* Go through vertical lines and expand them horizontally. */
        for (gint i = 0; i < listv->len; i += 2) {
            for (gint p = listv->data[i]; p <= listv->data[i + 1]; p += xres) {
                /* Scan left. */
                gint start = p - 1;
                gint stop = (p/xres)*xres;
                gint j;
                for (j = start; j >= stop; j--) {
                    if (visited[j] || data[j] != look_for)
                        break;
                    visited[j] = fill_with;
                    count++;
                }
                if (j < start) {
                    int_list_add(listh, j + 1);
                    int_list_add(listh, start);
                }

                /* Scan right. */
                start = p + 1;
                stop = (p/xres + 1)*xres;
                for (j = start; j < stop; j++) {
                    if (visited[j] || data[j] != look_for)
                        break;
                    visited[j] = fill_with;
                    count++;
                }
                if (j > start) {
                    int_list_add(listh, start);
                    int_list_add(listh, j - 1);
                }
            }
        }
        listv->len = 0;

        /* Go through horizontal lines and expand them vertically. */
        for (gint i = 0; i < listh->len; i += 2) {
            for (gint p = listh->data[i]; p <= listh->data[i + 1]; p++) {
                /* Scan up. */
                gint start = p - xres;
                gint stop = p % xres;
                gint j;
                for (j = start; j >= stop; j -= xres) {
                    if (visited[j] || data[j] != look_for)
                        break;
                    visited[j] = fill_with;
                    count++;
                }
                if (j < start) {
                    int_list_add(listv, j + xres);
                    int_list_add(listv, start);
                }

                /* Scan down. */
                start = p + xres;
                stop = p % xres + n;
                for (j = start; j < stop; j += xres) {
                    if (visited[j] || data[j] != look_for)
                        break;
                    visited[j] = fill_with;
                    count++;
                }
                if (j > start) {
                    int_list_add(listv, start);
                    int_list_add(listv, j - xres);
                }
            }
        }
        listh->len = 0;
    }

    if (free_listv)
        int_list_free(listv);
    if (free_listh)
        int_list_free(listh);

    return count;
}

static void
gather_numbers(gpointer hkey, G_GNUC_UNUSED gpointer hvalue, gpointer user_data)
{
    guint v = GPOINTER_TO_UINT(hkey);
    GArray *values = (GArray*)user_data;
    g_array_append_val(values, v);
}

static GHashTable*
get_grain_numbers_hashtable(GwyNield *nield, GArray *values)
{
    const gint *data = nield->priv->data;
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    GHashTable *number_map = g_hash_table_new(g_direct_hash, g_direct_equal);
    for (gsize i = 0; i < n; i++) {
        guint v = data[i];
        if (v > 0) {
            gpointer key = GUINT_TO_POINTER(v);
            if (!g_hash_table_lookup(number_map, key))
                g_hash_table_add(number_map, key);
        }
    }

    guint maxno = g_hash_table_size(number_map);
    g_array_set_size(values, maxno);
    g_array_set_size(values, 0);
    g_hash_table_foreach(number_map, gather_numbers, values);
    g_assert(maxno == values->len);
    gwy_guint_sort(&g_array_index(values, guint, 0), maxno);

    return number_map;
}

/* For only moderately scattered values. Pay O(n) memory for a very simple implementation. */
static gint
compactify_dense(GwyNield *nield, gint maxgno)
{
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    gint *data = nield->priv->data;
    gint *number_map = g_new0(gint, maxgno+1);
    for (gsize i = 0; i < n; i++) {
        if (data[i] > 0)
            number_map[data[i]] = 1;
    }
    gint grain_no = 1;
    for (gint i = 1; i < maxgno; i++) {
        if (number_map[i])
            number_map[i] = grain_no++;
    }
    for (gsize i = 0; i < n; i++) {
        if (data[i] > 0)
            data[i] = number_map[data[i]];
    }
    g_free(number_map);

    maxgno = grain_no-1;
    set_cache_for_compact_nield(nield, maxgno);

    return maxgno;
}

/**
 * SECTION: grains
 * @title: Grains
 * @short_description: Grain numbering and manipulation
 *
 * The numbered regions in a #GwyNield often represent the result of some classification. They are also often called
 * grains, especially when talking about distinct contiguous regions as they typically arise from grain marking.
 *
 * On one end, the classification can be a simple yes/no, for example marking by a height threshold. In such number
 * field, one can often distinguish different grains as contiguous marked regions. Function
 * gwy_nield_number_contiguous() takes any marking and renumbers it based on continuity. The opposite is done by
 * gwy_nield_flatten() which reduces all numbers to 0 and 1.
 *
 * On the other end, a clustering procedure may assign a cluster number to each pixel. The same number may be then
 * scattered throughout the field and may not form a contiguous regions at all. In such case functions using only
 * grain numbers are still useful, but functions based on contiguity are not.
 *
 * Functions characterising marked regions usually return arrays indexed by grain numbers. An occasional hole in grain
 * numbering is not an issue. However, avoid using unnecessarily large grain numbers, such as random integers, because
 * it would require correspondingly huge arrays. Or apply gwy_nield_compactify() to compactify the numbers if you use
 * large numbers temporarily.
 **/

/**
 * GwyMergeType:
 * @GWY_MERGE_CLEAR: Zero, disregarding both operands.
 * @GWY_MERGE_UNION: Union (logical-or/maximum) merging.
 * @GWY_MERGE_OR: Alternative name for @GWY_MERGE_UNION.
 * @GWY_MERGE_MAX: Alternative name for @GWY_MERGE_UNION.
 * @GWY_MERGE_INTERSECTION: Intersection (logical-and/minimum) merging.
 * @GWY_MERGE_AND: Alternative name for @GWY_MERGE_INTERSECTION.
 * @GWY_MERGE_MIN: Alternative name for @GWY_MERGE_INTERSECTION.
 * @GWY_MERGE_SUBTRACT: Subtraction (clearing the first operand where the second is set).
 * @GWY_MERGE_XOR: Exclusive or merging (if exactly one operand is set, keeping this one).
 * @GWY_MERGE_KEEP: Values are given by the first operand.
 * @GWY_MERGE_COPY: Values are given by the second operand.
 * @GWY_MERGE_REVSUBTRACT: Reverse subtraction (second operand, except where the first is set).
 * @GWY_MERGE_OUTSIDE: Positive only if both operands are unset.
 * @GWY_MERGE_NOR: Alternative name for @GWY_MERGE_OUTSIDE.
 * @GWY_MERGE_EQUAL: Positive where both operands are set or both unset (if they are both positive, the value is kept).
 * @GWY_MERGE_NXOR: Alternative name for @GWY_MERGE_EQUAL.
 * @GWY_MERGE_NCOPY: Values are given by the negation of the second operand.
 * @GWY_MERGE_NOT: Values are given by the negation of the first operand.
 * @GWY_MERGE_CONVERSE: Positive except when the first operand is set and the second unset.
 * @GWY_MERGE_IMPLIES: Positive except when the second operand is set and the first unset.
 * @GWY_MERGE_NAND: Positive only if either operand is unset.
 * @GWY_MERGE_FILL: Set, disregarding both operands.
 *
 * Mask merge type (namely used in grain processing).
 **/

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
