/*
 *  $Id: distance-transform.c 30005 2026-06-03 04:37:22Z yeti-dn $
 *  Copyright (C) 2003-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 <stdlib.h>
#include <glib/gi18n-lib.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/math.h"
#include "libgwyddion/arithmetic.h"
#include "libgwyddion/grains.h"
#include "libgwyddion/distance-transform.h"

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

enum {
    SEDINF = 0x7fffffffu,
    QUEUED = 0x80000000u,
};

typedef struct {
    gdouble distance;
    guint i;
    guint j;
} DistantPoint;

typedef struct {
    gdouble dist, ndist;
    gint j, i;
} ThinCandidate;

typedef gboolean (*ErodeFunc)(guint *dists,
                              gint width, gint height,
                              guint d,
                              const PixelQueue *inqueue,
                              PixelQueue *outqueue);


/* Euclidean distance transform */

static inline void
pixel_queue_add(PixelQueue *queue,
                gint i, gint j)
{
    if (G_UNLIKELY(queue->len == queue->size)) {
        queue->size = MAX(2*queue->size, 16);
        queue->points = g_renew(GridPoint, queue->points, queue->size);
    }

    queue->points[queue->len].i = i;
    queue->points[queue->len].j = j;
    queue->len++;
}

// Set squared distance for all points that have an 8-neighbour outside and add them to the queue.
static void
euclidean_distance_transform_first_step_merged(GwyNield *distances,
                                               IntList *queue,
                                               gboolean from_border)
{
    guint *ddata = (guint*)distances->priv->data;
    gint xres = distances->xres, yres = distances->yres;
    gsize k = 0;

    queue->len = 0;
    for (guint i = 0; i < yres; i++) {
        gboolean first_row = (i == 0);
        gboolean last_row = (i == yres-1);

        for (guint j = 0; j < xres; j++, k++) {
            gboolean first_column = (j == 0);
            gboolean last_column = (j == xres-1);

            if (!ddata[k])
                continue;

            if ((from_border && (first_row || first_column || last_row || last_column))
                || (!first_row && !ddata[k-xres])
                || (!first_column && !ddata[k-1])
                || (!last_column && !ddata[k+1])
                || (!last_row && !ddata[k+xres])) {
                ddata[k] = 1;
                int_list_add(queue, k);
            }
            else if ((!first_row && !first_column && !ddata[k-xres-1])
                     || (!first_row && !last_column && !ddata[k-xres+1])
                     || (!last_row && !first_column && !ddata[k+xres-1])
                     || (!last_row && !last_column && !ddata[k+xres+1])) {
                ddata[k] = 2;
                int_list_add(queue, k);
            }
        }
    }
}

// Field @distances must already be prefilled with SEDINF. However, we still use @grains to add boundaries which are
// between different touching grains, not between SEDINF and zero.
static void
euclidean_distance_transform_first_step_split(GwyNield *grains,
                                              GwyNield *distances,
                                              IntList *queue,
                                              gboolean from_border)
{
    guint *ddata = (guint*)distances->priv->data;
    const gint *gdata = grains->priv->data;
    gint xres = distances->xres, yres = distances->yres;
    gsize k = 0;

    queue->len = 0;
    for (guint i = 0; i < yres; i++) {
        gboolean first_row = (i == 0);
        gboolean last_row = (i == yres-1);

        for (guint j = 0; j < xres; j++, k++) {
            gboolean first_column = (j == 0);
            gboolean last_column = (j == xres-1);
            gint gno = gdata[k];

            if (!ddata[k]) {
                g_assert(gno <= 0);
                continue;
            }

            g_assert(gno >= 0);
            if ((from_border && (first_row || first_column || last_row || last_column))
                || (!first_row && gdata[k-xres] != gno)
                || (!first_column && gdata[k-1] != gno)
                || (!last_column && gdata[k+1] != gno)
                || (!last_row && gdata[k+xres] != gno)) {
                ddata[k] = 1;
                int_list_add(queue, k);
            }
            else if ((!first_row && !first_column && gdata[k-xres-1] != gno)
                     || (!first_row && !last_column && gdata[k-xres+1] != gno)
                     || (!last_row && !first_column && gdata[k+xres-1] != gno)
                     || (!last_row && !last_column && gdata[k+xres+1] != gno)) {
                ddata[k] = 2;
                int_list_add(queue, k);
            }
        }
    }
}

static void
euclidean_distance_transform_progress(GwyNield *distances, GwyNield *olddist,
                                      guint l,
                                      const IntList *inqueue,
                                      IntList *outqueue)
{
    guint *ddata = (guint*)distances->priv->data;
    const guint *oldd = (const guint*)olddist->priv->data;
    guint xres = distances->xres, yres = distances->yres;
    guint hvsed2 = 2*l - 1, diag2 = 2*hvsed2;

    outqueue->len = 0;

    for (guint q = 0; q < inqueue->len; q++) {
        guint k = inqueue->data[q], kk = k-xres-1;
        guint i = k/xres, j = k % xres;
        gboolean first_row = (i == 0);
        gboolean last_row = (i == yres-1);
        gboolean first_column = (j == 0);
        gboolean last_column = (j == xres-1);
        guint d2hv = oldd[k] + hvsed2, d2d = oldd[k] + diag2;

        if (!first_row && !first_column && (ddata[kk] & ~QUEUED) > d2d) {
            if (!(ddata[kk] & QUEUED))
                int_list_add(outqueue, kk);
            ddata[kk] = QUEUED | d2d;
        }
        kk++;
        if (!first_row && (ddata[kk] & ~QUEUED) > d2hv) {
            if (!(ddata[kk] & QUEUED))
                int_list_add(outqueue, kk);
            ddata[kk] = QUEUED | d2hv;
        }
        kk++;
        if (!first_row && !last_column && (ddata[kk] & ~QUEUED) > d2d) {
            if (!(ddata[kk] & QUEUED))
                int_list_add(outqueue, kk);
            ddata[kk] = QUEUED | d2d;
        }
        kk += xres-2;
        if (!first_column && (ddata[kk] & ~QUEUED) > d2hv) {
            if (!(ddata[kk] & QUEUED))
                int_list_add(outqueue, kk);
            ddata[kk] = QUEUED | d2hv;
        }
        kk += 2;
        if (!last_column && (ddata[kk] & ~QUEUED) > d2hv) {
            if (!(ddata[kk] & QUEUED))
                int_list_add(outqueue, kk);
            ddata[kk] = QUEUED | d2hv;
        }
        kk += xres-2;
        if (!last_row && !first_column && (ddata[kk] & ~QUEUED) > d2d) {
            if (!(ddata[kk] & QUEUED))
                int_list_add(outqueue, kk);
            ddata[kk] = QUEUED | d2d;
        }
        kk++;
        if (!last_row && (ddata[kk] & ~QUEUED) > d2hv) {
            if (!(ddata[kk] & QUEUED))
                int_list_add(outqueue, kk);
            ddata[kk] = QUEUED | d2hv;
        }
        kk++;
        if (!last_row && !last_column && (ddata[kk] & ~QUEUED) > d2d) {
            if (!(ddata[kk] & QUEUED))
                int_list_add(outqueue, kk);
            ddata[kk] = QUEUED | d2d;
        }
    }
}

static void
clear_queued_flag(GwyNield *intdist, IntList *queue)
{
    guint *ddata = (guint*)intdist->priv->data;
    gssize *qdata = queue->data;
    for (guint q = queue->len; q; q--, qdata++)
        ddata[*qdata] &= ~QUEUED;
}

static void
copy_queued(GwyNield *src, GwyNield *dest, IntList *queue)
{
    const gint *s = src->priv->data;
    gint *d = dest->priv->data;
    gssize *qdata = queue->data;
    for (guint q = 0; q < queue->len; q++) {
        gsize k = qdata[q];
        d[k] = s[k];
    }
}

static void
euclidean_distance_transform(GwyNield *nield, GwyField *distances,
                             gboolean merged, gboolean from_border)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    g_return_if_fail(GWY_IS_FIELD(distances));

    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    GwyNield *intdist = gwy_nield_new_alike(nield);
    GwyNield *workspace = gwy_nield_new_alike(nield);
    gwy_nield_area_fill(intdist, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres, SEDINF);

    guint inisize = (guint)(8*sqrt(n) + 16);
    IntList *inqueue = int_list_new(inisize);
    IntList *outqueue = int_list_new(inisize);

    if (merged)
        euclidean_distance_transform_first_step_merged(intdist, inqueue, from_border);
    else
        euclidean_distance_transform_first_step_split(nield, intdist, inqueue, from_border);

    for (guint l = 2; inqueue->len; l++) {
        copy_queued(intdist, workspace, inqueue);
        euclidean_distance_transform_progress(intdist, workspace, l, inqueue, outqueue);
        clear_queued_flag(intdist, outqueue);
        GWY_SWAP(IntList*, inqueue, outqueue);
    }

    int_list_free(inqueue);
    int_list_free(outqueue);

    gdouble *f = distances->priv->data;
    const gint *dist = intdist->priv->data;
    for (gsize k = 0; k < n; k++)
        f[k] = sqrt(dist[k]);

    g_object_unref(workspace);
    g_object_unref(intdist);

    gwy_field_invalidate(distances);
}

/* Init @queue with all 4-connectivitivy boundary pixels:
 * @grain: Grain numbering as in GwyNield.
 * @dist: Buffer to fill with initial distance transform state (0, 1 or infinity). */
static void
init_erosion_4_merged(const gint *grain, guint *dist,
                      guint width, guint height,
                      gboolean from_border,
                      PixelQueue *queue)
{
    for (guint i = 0; i < height; i++) {
        gboolean ifirst = !i, ilast = (i == height-1);
        for (guint j = 0; j < width; j++) {
            gboolean jfirst = !j, jlast = (j == width-1);
            guint k = i*width + j;
            if (grain[k] <= 0) {
                dist[k] = 0;
                continue;
            }

            if ((!ifirst && grain[k - width] <= 0)
                || (!jfirst && grain[k-1] <= 0)
                || (!jlast && grain[k+1] <= 0)
                || (!ilast && grain[k + width] <= 0)
                || (from_border && (ifirst || ilast || jfirst || jlast))) {
                dist[k] = 1;
                pixel_queue_add(queue, i, j);
            }
            else
                dist[k] = G_MAXUINT;
        }
    }
}

static void
init_erosion_4_split(const gint *grain, guint *dist,
                     guint width, guint height,
                     gboolean from_border,
                     PixelQueue *queue)
{
    for (guint i = 0; i < height; i++) {
        gboolean ifirst = !i, ilast = (i == height-1);
        for (guint j = 0; j < width; j++) {
            gboolean jfirst = !j, jlast = (j == width-1);
            guint k = i*width + j;
            if (grain[k] <= 0) {
                dist[k] = 0;
                continue;
            }

            gint gno = grain[k];
            if ((!ifirst && grain[k - width] != gno)
                || (!jfirst && grain[k-1] != gno)
                || (!jlast && grain[k+1] != gno)
                || (!ilast && grain[k + width] != gno)
                || (from_border && (ifirst || ilast || jfirst || jlast))) {
                dist[k] = 1;
                pixel_queue_add(queue, i, j);
            }
            else
                dist[k] = G_MAXUINT;
        }
    }
}

/* Init @queue with all 8-connectivitivy boundary pixels:
 * @grain: Grain numbering as in GwyNield.
 * @dist: Buffer to fill with initial distance transform state (0, 1 or infinity). */
static void
init_erosion_8_merged(const gint *grain, guint *dist,
                      gint width, gint height,
                      gboolean from_border,
                      PixelQueue *queue)
{
    for (guint i = 0; i < height; i++) {
        gboolean ifirst = !i, ilast = (i == height-1);
        for (guint j = 0; j < width; j++) {
            gboolean jfirst = !j, jlast = (j == width-1);
            guint k = i*width + j;
            gint gno = grain[k];
            if (gno <= 0) {
                dist[k] = 0;
                continue;
            }

            if ((!ifirst && !jfirst && grain[k-1 - width] <= 0)
                || (!ifirst && grain[k - width] <= 0)
                || (!ifirst && !jlast && grain[k+1 - width] <= 0)
                || (!jfirst && grain[k-1] <= 0)
                || (!jlast && grain[k+1] <= 0)
                || (!ilast && !jfirst && grain[k-1 + width] <= 0)
                || (!ilast && grain[k + width] <= 0)
                || (!ilast && !jlast && grain[k+1 + width] <= 0)
                || (from_border && (ifirst || ilast || jfirst || jlast))) {
                dist[k] = 1;
                pixel_queue_add(queue, i, j);
            }
            else
                dist[k] = G_MAXUINT;
        }
    }
}

static void
init_erosion_8_split(const gint *grain, guint *dist,
                     gint width, gint height,
                     gboolean from_border,
                     PixelQueue *queue)
{
    for (guint i = 0; i < height; i++) {
        gboolean ifirst = !i, ilast = (i == height-1);
        for (guint j = 0; j < width; j++) {
            gboolean jfirst = !j, jlast = (j == width-1);
            guint k = i*width + j;
            gint gno = grain[k];
            if (gno <= 0) {
                dist[k] = 0;
                continue;
            }

            if ((!ifirst && !jfirst && grain[k-1 - width] != gno)
                || (!ifirst && grain[k - width] != gno)
                || (!ifirst && !jlast && grain[k+1 - width] != gno)
                || (!jfirst && grain[k-1] != gno)
                || (!jlast && grain[k+1] != gno)
                || (!ilast && !jfirst && grain[k-1 + width] != gno)
                || (!ilast && grain[k + width] != gno)
                || (!ilast && !jlast && grain[k+1 + width] != gno)
                || (from_border && (ifirst || ilast || jfirst || jlast))) {
                dist[k] = 1;
                pixel_queue_add(queue, i, j);
            }
            else
                dist[k] = G_MAXUINT;
        }
    }
}

static gboolean
erode_4(guint *dists,
        gint width, gint height,
        guint d,
        const PixelQueue *inqueue,
        PixelQueue *outqueue)
{
    const GridPoint *ipt = inqueue->points;
    guint m;

    outqueue->len = 0;
    for (m = inqueue->len; m; m--, ipt++) {
        gint i = ipt->i, j = ipt->j, k = i*width + j;

        if (i && dists[k - width] == G_MAXUINT) {
            dists[k - width] = d+1;
            pixel_queue_add(outqueue, i-1, j);
        }
        if (j && dists[k - 1] == G_MAXUINT) {
            dists[k - 1] = d+1;
            pixel_queue_add(outqueue, i, j-1);
        }
        if (j < width-1 && dists[k + 1] == G_MAXUINT) {
            dists[k + 1] = d+1;
            pixel_queue_add(outqueue, i, j+1);
        }
        if (i < height-1 && dists[k + width] == G_MAXUINT) {
            dists[k + width] = d+1;
            pixel_queue_add(outqueue, i+1, j);
        }
    }

    return outqueue->len;
}

static gboolean
erode_8(guint *dists,
        gint width, gint height,
        guint d,
        const PixelQueue *inqueue,
        PixelQueue *outqueue)
{
    const GridPoint *ipt = inqueue->points;
    guint m;

    outqueue->len = 0;
    for (m = inqueue->len; m; m--, ipt++) {
        gint i = ipt->i, j = ipt->j, k = i*width + j;
        if (i && j && dists[k - width - 1] == G_MAXUINT) {
            dists[k - width - 1] = d+1;
            pixel_queue_add(outqueue, i-1, j-1);
        }
        if (i && dists[k - width] == G_MAXUINT) {
            dists[k - width] = d+1;
            pixel_queue_add(outqueue, i-1, j);
        }
        if (i && j < width-1 && dists[k - width + 1] == G_MAXUINT) {
            dists[k - width + 1] = d+1;
            pixel_queue_add(outqueue, i-1, j+1);
        }
        if (j && dists[k - 1] == G_MAXUINT) {
            dists[k - 1] = d+1;
            pixel_queue_add(outqueue, i, j-1);
        }
        if (j < width-1 && dists[k + 1] == G_MAXUINT) {
            dists[k + 1] = d+1;
            pixel_queue_add(outqueue, i, j+1);
        }
        if (i < height-1 && j && dists[k + width - 1] == G_MAXUINT) {
            dists[k + width - 1] = d+1;
            pixel_queue_add(outqueue, i+1, j-1);
        }
        if (i < height-1 && dists[k + width] == G_MAXUINT) {
            dists[k + width] = d+1;
            pixel_queue_add(outqueue, i+1, j);
        }
        if (i < height-1 && j < width-1 && dists[k + width + 1] == G_MAXUINT) {
            dists[k + width + 1] = d+1;
            pixel_queue_add(outqueue, i+1, j+1);
        }
    }

    return outqueue->len;
}

/* Perform a cityblock, chessboard or octagonal distance transform of given type using provided queues putting
 * the result to @distances. It is possible to pass NULL @distances for an in-place transform.
 *
 * If @merged is TRUE, grains are not distinguished and tne entire masked area is transformed.
 * If @merged is FALSE, borders of individual grains are distinguished (which matters if grains are touching). */
guint
_gwy_simple_dist_trans(GwyNield *grains, GwyNield *distances,
                       gboolean merged, gboolean from_border,
                       GwyDistanceTransformType dtype,
                       PixelQueue *inqueue, PixelQueue *outqueue)
{
    g_return_val_if_fail(dtype == GWY_DISTANCE_TRANSFORM_CONN4
                         || dtype == GWY_DISTANCE_TRANSFORM_OCTAGONAL48
                         || dtype == GWY_DISTANCE_TRANSFORM_CONN8
                         || dtype == GWY_DISTANCE_TRANSFORM_OCTAGONAL84,
                         0);
    gboolean start_with_4 = (dtype == GWY_DISTANCE_TRANSFORM_CONN4 || dtype == GWY_DISTANCE_TRANSFORM_OCTAGONAL48);
    gboolean is_octagonal = (dtype == GWY_DISTANCE_TRANSFORM_OCTAGONAL48
                             || dtype == GWY_DISTANCE_TRANSFORM_OCTAGONAL84);
    guint width = grains->xres, height = grains->yres;
    const gint *gdata = grains->priv->data;
    GwyNield *workspace = distances ? distances : gwy_nield_new_alike(grains);
    gint *ddata = workspace->priv->data;

    inqueue->len = outqueue->len = 0;

    if (merged) {
        if (start_with_4)
            init_erosion_4_merged(gdata, ddata, width, height, from_border, inqueue);
        else
            init_erosion_8_merged(gdata, ddata, width, height, from_border, inqueue);
    }
    else {
        if (start_with_4)
            init_erosion_4_split(gdata, ddata, width, height, from_border, inqueue);
        else
            init_erosion_8_split(gdata, ddata, width, height, from_border, inqueue);
    }

    // Once all the borders are correctly set up according to merged/split, further propagation can only encounter
    // pixels belonging to the same grain (however it is defined). So the erosion function do not need to worry about
    // the distinct grain treatment mode.
    guint dist = 1;
    ErodeFunc erode = (start_with_4 ? erode_4 : erode_8);
    // We already did the first step.
    if (is_octagonal)
        erode = (erode == erode_4) ? erode_8 : erode_4;
    while (erode(ddata, width, height, dist, inqueue, outqueue)) {
        GWY_SWAP(PixelQueue*, inqueue, outqueue);
        dist++;
        if (is_octagonal)
            erode = (erode == erode_4) ? erode_8 : erode_4;
    }

    if (!distances) {
        gwy_nield_copy_data(workspace, grains);
        g_object_unref(workspace);
    }

    return dist;
}

static void
average_octagonal_dt(GwyNield *nield, GwyField *distances, gboolean merged, gboolean from_border)
{
    GwyNield *intdist = gwy_nield_new_alike(nield);
    gsize n = (gsize)nield->xres * (gsize)nield->yres;

    PixelQueue inqueue, outqueue;
    gwy_clear1(inqueue);
    gwy_clear1(outqueue);

    _gwy_simple_dist_trans(nield, intdist, merged, from_border, GWY_DISTANCE_TRANSFORM_OCTAGONAL48,
                           &inqueue, &outqueue);
    gdouble *d = distances->priv->data;
    const gint *dd = intdist->priv->data;
    for (gsize i = 0; i < n; i++)
        d[i] = dd[i];

    _gwy_simple_dist_trans(nield, intdist, merged, from_border, GWY_DISTANCE_TRANSFORM_OCTAGONAL84,
                           &inqueue, &outqueue);
    g_free(inqueue.points);
    g_free(outqueue.points);

    dd = intdist->priv->data;
    for (gsize i = 0; i < n; i++)
        d[i] = 0.5*(dd[i] + d[i]);

    g_object_unref(intdist);
}

/**
 * gwy_nield_distance_transform:
 * @nield: A number field with zeros in empty space and nonzeros in marked areas.
 * @distances: Output data field which will be resized and filled with the distances.
 * @dtype: Type of simple distance to use.
 * @merged: %TRUE to only distinguish marked/unmarked; %FALSE to distinuish grain numbers.
 * @from_border: %TRUE to consider image edges to be grain boundaries.
 *
 * Performs a distance transform of a number field.
 *
 * For each non-zero value, a distance to the grain boundary is calculated, measured in pixels. For unmarked pixels in
 * @nield, the distance is set to zero.
 *
 * If @merged is TRUE, there is no boundary between touching grains. The distance is calculated from unmarked area
 * (and possibly from image edges). If @merged is %FALSE, regions filled with different numbers in @nield are
 * considered individually, creating boundaries also where different grains touch.
 *
 * If the entire @nield is marked, @from_border is %FALSE and either @merged is %TRUE or @nield is filled with the
 * same value, there are no boundaries to calculate the distance from. In such case @distances is filled with a huge
 * positive value (like %G_MAXDOUBLE).
 **/
void
gwy_nield_distance_transform(GwyNield *nield,
                             GwyField *distances,
                             GwyDistanceTransformType dtype,
                             gboolean merged,
                             gboolean from_border)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    g_return_if_fail(GWY_IS_FIELD(distances));

    gint xres = nield->xres, yres = nield->yres;
    gwy_field_resize(distances, xres, yres);

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

    /* Handle the infinite distance case once for all here so that all methods return consitently the same
     * huge number and do not need special-casing. */
    if (!from_border && gwy_nield_count(nield) == n) {
        if (merged) {
            gwy_field_fill(distances, G_MAXDOUBLE);
            return;
        }

        // The non-merged case is messier. We need to check the nield is filled with a single value. Otherwise there
        // are boundaries.
        gint gno = g[0];
        gsize i = 1;
        while (i < n && g[i] == gno)
            i++;
        if (i == n) {
            gwy_field_fill(distances, G_MAXDOUBLE);
            return;
        }
    }

    if (dtype == GWY_DISTANCE_TRANSFORM_EUCLIDEAN) {
        euclidean_distance_transform(nield, distances, merged, from_border);
        return;
    }
    if (dtype == GWY_DISTANCE_TRANSFORM_OCTAGONAL) {
        average_octagonal_dt(nield, distances, merged, from_border);
        return;
    }

    /* The other distances are always integers. */
    g_return_if_fail(dtype <= GWY_DISTANCE_TRANSFORM_OCTAGONAL84);

    PixelQueue inqueue, outqueue;
    gwy_clear1(inqueue);
    gwy_clear1(outqueue);
    GwyNield *intdist = gwy_nield_new_alike(nield);

    _gwy_simple_dist_trans(nield, intdist, merged, from_border, dtype, &inqueue, &outqueue);

    gdouble *d = distances->priv->data;
    const gint *w = intdist->priv->data;
    for (gsize k = 0; k < n; k++)
        d[k] = w[k];
    gwy_field_invalidate(distances);

    g_object_unref(intdist);
    g_free(inqueue.points);
    g_free(outqueue.points);
}

/**
 * gwy_nield_shrink:
 * @nield: A number field with zeros in empty space and nonzeros in grains.
 * @amount: How much the grains should be reduced, in pixels.  It is inclusive, i.e. pixels that are @amount far from
 *          the border will be removed.
 * @dtype: Type of simple distance to use.
 * @merged: %TRUE to only distinguish marked/unmarked; %FALSE to distinuish grain numbers.
 * @from_border: %TRUE to consider image edges to be grain boundaries. %FALSE to reduce grains touching field
 *               boundaries only along the boundaries.
 *
 * Erodes a number field containing mask by specified amount using a distance measure.
 *
 * Non-zero pixels in @nield will be replaced with zeros if they are not farther than @amount from the grain
 * boundary as defined by @dtype.
 **/
void
gwy_nield_shrink(GwyNield *nield,
                 gdouble amount,
                 GwyDistanceTransformType dtype,
                 gboolean merged,
                 gboolean from_border)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    g_return_if_fail(dtype <= GWY_DISTANCE_TRANSFORM_EUCLIDEAN);

    if (amount < 0.5)
        return;

    amount += 1e-9;
    gint xres = nield->xres, yres = nield->yres;
    if (from_border && amount > MAX(xres/2, yres/2) + 1) {
        gwy_nield_clear(nield);
        return;
    }

    gsize n = (gsize)xres * (gsize)yres;
    GwyField *distances = gwy_field_new(xres, yres, xres, yres, FALSE);
    gwy_nield_distance_transform(nield, distances, dtype, merged, from_border);
    gint *g = nield->priv->data;
    const gdouble *d = distances->priv->data;
    for (gsize k = 0; k < n; k++) {
        if (d[k] <= amount)
            g[k] = 0;
    }

    g_object_unref(distances);
    gwy_nield_invalidate(nield);
}

static gint
compare_distant_points(gconstpointer pa, gconstpointer pb)
{
    const DistantPoint *a = (const DistantPoint*)pa;
    const DistantPoint *b = (const DistantPoint*)pb;

    if (a->distance < b->distance)
        return -1;
    if (a->distance > b->distance)
        return 1;
    if (a->i < b->i)
        return -1;
    if (a->i > b->i)
        return 1;
    if (a->j < b->j)
        return -1;
    if (a->j > b->j)
        return 1;
    return 0;
}

static void
grow_without_touching(GwyNield *nield, GwyField *distances, gdouble amount)
{
    if (!gwy_nield_number_contiguous(nield))
        return;

    gint xres = nield->xres, yres = nield->yres;
    gint *d = nield->priv->data;
    const gdouble *e = distances->priv->data;

    GArray *array = g_array_sized_new(FALSE, FALSE, sizeof(DistantPoint), 1000);
    for (gint i = 0; i < yres; i++) {
        for (gint j = 0; j < xres; j++) {
            gdouble eij = e[i*xres + j];
            if (eij > 0.0 && eij <= amount) {
                DistantPoint dp = { eij, i, j };
                g_array_append_val(array, dp);
            }
        }
    }
    g_array_sort(array, compare_distant_points);

    for (guint m = 0; m < array->len; m++) {
        const DistantPoint *dp = &g_array_index(array, DistantPoint, m);
        gint k = dp->i*xres + dp->j;
        gint g1 = dp->i > 0      ? d[k-xres] : 0;
        gint g2 = dp->j > 0      ? d[k-1]    : 0;
        gint g3 = dp->j < xres-1 ? d[k+1]    : 0;
        gint g4 = dp->i < yres-1 ? d[k+xres] : 0;
        /* If all are equal or zeros then bitwise or gives us the nonzero value sought. */
        gint gno = g1 | g2 | g3 | g4;
        if ((!g1 || g1 == gno) && (!g2 || g2 == gno) && (!g3 || g3 == gno) && (!g4 || g4 == gno))
            d[k] = gno;
    }

    g_array_free(array, TRUE);
}

// FIXME: Some kind of @merged option might be useful also here. However, the grains can already touching in the
// input, creating a bunch of ill-defined cases.
/**
 * gwy_nield_grow:
 * @nield: A number field with zeros in empty space and nonzeros in grains.
 * @amount: How much the grains should be expanded, in pixels.  It is inclusive, i.e. exterior pixels that are @amount
 *          far from the border will be filled.
 * @dtype: Type of simple distance to use.
 * @prevent_touching: %TRUE to prevent grain touching, i.e. the growth stops where two grains would touch.  %FALSE to
 *                    simply expand the grains, without regard to grain connectivity.
 *
 * Dilates a number field containing mask by specified amount using a distance measure.
 *
 * Non-positive pixels in @field will be replaced with ones if they are not farther than @amount from the grain
 * boundary as defined by @dtype.
 **/
void
gwy_nield_grow(GwyNield *nield,
               gdouble amount,
               GwyDistanceTransformType dtype,
               gboolean prevent_touching)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    g_return_if_fail(dtype <= GWY_DISTANCE_TRANSFORM_EUCLIDEAN);

    if (amount < 0.5)
        return;

    amount += 1e-9;
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    GwyField *distances = gwy_field_new(xres, yres, xres, yres, FALSE);
    gwy_nield_invert(nield);
    gwy_nield_distance_transform(nield, distances, dtype, TRUE, FALSE);
    gwy_nield_invert(nield);
    if (prevent_touching)
        grow_without_touching(nield, distances, amount);
    else {
        const gdouble *d = distances->priv->data;
        gint *g = nield->priv->data;
        for (gsize k = 0; k < n; k++) {
            if (d[k] <= amount)
                g[k] = 1.0;
        }
    }
    g_object_unref(distances);
    gwy_nield_invalidate(nield);
}

static gint
compare_candidate(gconstpointer pa, gconstpointer pb)
{
    const ThinCandidate *a = (const ThinCandidate*)pa;
    const ThinCandidate *b = (const ThinCandidate*)pb;

    /* Take pixels with lowest Euclidean distances first. */
    if (a->dist < b->dist)
        return -1;
    if (a->dist > b->dist)
        return 1;

    /* If equal, take pixels with largest Euclidean distance *of their neighbours* first.  This essentially mean flat
     * edges go before corners, preserving useful branches. */
    if (a->ndist > b->ndist)
        return -1;
    if (a->ndist < b->ndist)
        return 1;

    /* When desperate, sort bottom and right coordinates first so that we try to remove them first.  Anyway we must
     * impose some rule to make the sort stable. */
    if (a->i > b->i)
        return -1;
    if (a->i < b->i)
        return 1;
    if (a->j > b->j)
        return -1;
    if (a->j < b->j)
        return 1;

    return 0;
}

/**
 * gwy_nield_thin:
 * @nield: A number field with zeros in empty space and nonzeros in grains.
 *
 * Performs thinning of a data field containing mask.
 *
 * The result of thinning is a ‘skeleton’ mask consisting of single-pixel thin lines.
 **/
void
gwy_nield_thin(GwyNield *nield)
{
    /* TRUE means removing the central pixel in a 3x3 pixel configuration does not break any currently connected
     * parts. */
    static const gboolean ok_to_remove[0x100] = {
        FALSE, TRUE,  FALSE, TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  FALSE, TRUE,  FALSE, FALSE, TRUE,  TRUE,  TRUE,  TRUE,
        TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  FALSE, FALSE, TRUE,  TRUE,  TRUE,  TRUE,
        FALSE, TRUE,  FALSE, FALSE, TRUE,  TRUE,  FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
        TRUE,  TRUE,  FALSE, FALSE, TRUE,  TRUE,  FALSE, FALSE, TRUE,  TRUE,  FALSE, FALSE, TRUE,  TRUE,  TRUE,  TRUE,
        TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  FALSE, FALSE, TRUE,  TRUE,  TRUE,  TRUE,
        TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  TRUE,  FALSE, FALSE, TRUE,  TRUE,  TRUE,  TRUE,
        TRUE,  TRUE,  FALSE, FALSE, TRUE,  TRUE,  FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
        TRUE,  TRUE,  FALSE, FALSE, TRUE,  TRUE,  FALSE, FALSE, TRUE,  TRUE,  FALSE, FALSE, TRUE,  TRUE,  TRUE,  TRUE,
        FALSE, TRUE,  FALSE, TRUE,  TRUE,  TRUE,  FALSE, TRUE,  FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE,
        TRUE,  TRUE,  FALSE, TRUE,  TRUE,  TRUE,  FALSE, TRUE,  FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE,
        FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
        FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE,
        TRUE,  TRUE,  FALSE, TRUE,  TRUE,  TRUE,  FALSE, TRUE,  FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE,
        TRUE,  TRUE,  FALSE, TRUE,  TRUE,  TRUE,  FALSE, TRUE,  FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE,
        TRUE,  TRUE,  FALSE, TRUE,  TRUE,  TRUE,  FALSE, TRUE,  FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE,
        TRUE,  TRUE,  FALSE, TRUE,  TRUE,  TRUE,  FALSE, TRUE,  TRUE,  TRUE,  FALSE, TRUE,  TRUE,  TRUE,  TRUE,  TRUE,
    };

    g_return_if_fail(GWY_IS_NIELD(nield));

    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    GwyField *distances = gwy_field_new(xres, yres, xres, yres, FALSE);
    gwy_nield_distance_transform(nield, distances, GWY_DISTANCE_TRANSFORM_EUCLIDEAN, TRUE, TRUE);
    gdouble *d = distances->priv->data;
    gint *g = nield->priv->data;

    gint ncand = 0;
    for (gsize k = 0; k < n; k++) {
        if (d[k] > 0.0)
            ncand++;
    }

    if (ncand < 2) {
        /* There are no mask pixels or just a single pixel.  In either case we do not have to do anything. */
        g_object_unref(distances);
        return;
    }

    ThinCandidate *candidates = g_new(ThinCandidate, ncand);
    gint k = 0;
    for (gint i = 0; i < yres; i++) {
        for (gint j = 0; j < xres; j++) {
            gint m = i*xres + j;
            if (d[m] > 0.0) {
                gdouble nd, ndist = 0.0, maxndist = 0.0;
                candidates[k].i = i;
                candidates[k].j = j;
                candidates[k].dist = d[m];

                if (i && j) {
                    nd = d[m-xres-1];
                    ndist += nd;
                    if (nd > maxndist)
                        maxndist = nd;
                }
                if (i) {
                    nd = d[m-xres];
                    ndist += nd;
                    if (nd > maxndist)
                        maxndist = nd;
                }
                if (i && j < xres-1) {
                    nd = d[m-xres+1];
                    ndist += nd;
                    if (nd > maxndist)
                        maxndist = nd;
                }
                if (j < xres-1) {
                    nd = d[m+1];
                    ndist += nd;
                    if (nd > maxndist)
                        maxndist = nd;
                }
                if (i < yres-1 && j < xres-1) {
                    nd = d[m+xres+1];
                    ndist += nd;
                    if (nd > maxndist)
                        maxndist = nd;
                }
                if (i < yres-1) {
                    nd = d[m+xres];
                    ndist += nd;
                    if (nd > maxndist)
                        maxndist = nd;
                }
                if (i < yres-1 && j) {
                    nd = d[m+xres-1];
                    ndist += nd;
                    if (nd > maxndist)
                        maxndist = nd;
                }
                if (j) {
                    nd = d[m-1];
                    ndist += nd;
                    if (nd > maxndist)
                        maxndist = nd;
                }

                /* If the point is farther from the border than any neighbour we never remove it. */
                if (candidates[k].dist < 0.999*maxndist) {
                    candidates[k].ndist = ndist;
                    k++;
                }
            }
        }
    }
    ncand = k;

    if (ncand) {
        qsort(candidates, ncand, sizeof(ThinCandidate), &compare_candidate);

        for (k = 0; k < ncand; k++) {
            guint b = 0;

            gint i = candidates[k].i;
            gint j = candidates[k].j;
            if (i && j && d[(i-1)*xres + (j-1)] > 0.0)
                b |= 1;
            if (i && d[(i-1)*xres + j] > 0.0)
                b |= 2;
            if (i && j < xres-1 && d[(i-1)*xres + (j+1)] > 0.0)
                b |= 4;
            if (j < xres-1 && d[i*xres + (j+1)] > 0.0)
                b |= 8;
            if (i < yres-1 && j < xres-1 && d[(i+1)*xres + (j+1)] > 0.0)
                b |= 16;
            if (i < yres-1 && d[(i+1)*xres + j] > 0.0)
                b |= 32;
            if (i < yres-1 && j && d[(i+1)*xres + (j-1)] > 0.0)
                b |= 64;
            if (j && d[i*xres + (j-1)] > 0.0)
                b |= 128;

            if (ok_to_remove[b]) {
                d[i*xres + j] = 0.0;
                g[i*xres + j] = 0;
            }
        }
    }

    g_free(candidates);
    g_object_unref(distances);
    gwy_nield_invalidate(nield);
}

/**
 * SECTION: distance-transform
 * @title: Distance transform
 * @short_description: Distance transform and related morphological operations
 **/

/**
 * GwyDistanceTransformType:
 * @GWY_DISTANCE_TRANSFORM_CITYBLOCK: City-block distance (sum of horizontal and vertical distances).
 * @GWY_DISTANCE_TRANSFORM_CONN4: Four-connectivity distance; another name for city-block distance.
 * @GWY_DISTANCE_TRANSFORM_CHESS: Chessboard distance (maximum of horizontal and vertical distance).
 * @GWY_DISTANCE_TRANSFORM_CONN8: Eight-connectivity distance; another name for chessboard distance.
 * @GWY_DISTANCE_TRANSFORM_OCTAGONAL48: Octagonal distance beginning from city-block.
 * @GWY_DISTANCE_TRANSFORM_OCTAGONAL84: Octagonal distance beginning from chess.
 * @GWY_DISTANCE_TRANSFORM_OCTAGONAL: Average octagonal distance, i.e. the mean of the 48 and 84 distances.
 * @GWY_DISTANCE_TRANSFORM_EUCLIDEAN: True Euclidean distance.
 *
 * Type of distance transform.
 **/

/* 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 : */
