/*
 *  $Id: nield.c 30088 2026-06-18 18:12:12Z yeti-dn $
 *  Copyright (C) 2025-2026 David Nečas (Yeti).
 *  E-mail: yeti@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 "tests/testlibgwy.h"

enum {
    SOME_NUMBER = 3157,
};

static void fill_field_with_a_coordinate_power(GwyField *field,
                                               gint powerx,
                                               gint powery,
                                               gboolean pixel_coords,
                                               GwyNield *centre_at);

G_GNUC_UNUSED
static void
dump_nield(GwyNield *nield, const gchar *name)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    g_printerr("=====[ %s (%dx%d) ]=====\n", name, xres, yres);
    const gint *d = gwy_nield_get_data_const(nield);
    for (gint i = 0; i < yres; i++) {
        for (gint j = 0; j < xres; j++) {
            gint v = d[i*xres + j];
            if (v < 0)
                g_printerr("-");
            else if (v == 0)
                g_printerr(" ");
            else if (v < 10)
                g_printerr("%c", '0' + v);
            else
                g_printerr("%c", 'a' + (v - 10));
        }
        g_printerr("\n");
    }
}

G_GNUC_UNUSED
static void
dump_field(GwyField *field, gint digits, const gchar *name)
{
    gint xres = gwy_field_get_xres(field), yres = gwy_field_get_yres(field);
    g_printerr("=====[ %s (%dx%d) ]=====\n", name, xres, yres);
    for (guint i = 0; i < yres; i++) {
        for (guint j = 0; j < xres; j++) {
            g_printerr("% .*f ", digits, gwy_field_get_val(field, j, i));
        }
        g_printerr("\n");
    }
    g_printerr("\n");
}

static void
assert_nield_content(GwyNield *nield, const gint *ref_data, guint ref_n)
{
    g_assert_true(GWY_IS_NIELD(nield));
    guint xres = gwy_nield_get_xres(nield);
    guint yres = gwy_nield_get_yres(nield);
    g_assert_cmpuint(xres*yres, ==, ref_n);
    const gint *data = gwy_nield_get_data_const(nield);
    g_assert_nonnull(data);

    for (guint i = 0; i < ref_n; i++)
        g_assert_cmpint(data[i], ==, ref_data[i]);
}

void
nield_assert_equal(GObject *object, GObject *reference)
{
    g_assert_true(GWY_IS_NIELD(object));
    g_assert_true(GWY_IS_NIELD(reference));

    GwyNield *nield = GWY_NIELD(object), *nield_ref = GWY_NIELD(reference);
    g_assert_cmpint(gwy_nield_get_xres(nield), ==, gwy_nield_get_xres(nield_ref));
    g_assert_cmpint(gwy_nield_get_yres(nield), ==, gwy_nield_get_yres(nield_ref));
    assert_nield_content(nield, gwy_nield_get_data(nield_ref),
                         gwy_nield_get_xres(nield_ref)*gwy_nield_get_yres(nield_ref));
}

void
test_nield_basic(void)
{
    const gint zeros[6] = { 0, 0, 0, 0, 0, 0 };
    const gint twos[6] = { 2, 2, 2, 2, 2, 2 };

    GwyNield *nield = gwy_nield_new(2, 3);
    assert_nield_content(nield, zeros, 6);

    gwy_nield_fill(nield, 2);
    assert_nield_content(nield, twos, 6);

    gwy_nield_clear(nield);
    assert_nield_content(nield, zeros, 6);

    g_assert_finalize_object(nield);
}

void
test_nield_data_changed(void)
{
    GwyNield *nield = gwy_nield_new(1, 1);
    guint item_changed = 0;
    g_signal_connect_swapped(nield, "data-changed", G_CALLBACK(record_signal), &item_changed);
    gwy_nield_data_changed(nield);
    g_assert_cmpuint(item_changed, ==, 1);
    gwy_nield_data_changed(nield);
    g_assert_cmpuint(item_changed, ==, 2);
    g_assert_finalize_object(nield);
}

static GwyNield*
create_nield_for_serialisation(void)
{
    GwyNield *nield = gwy_nield_new(13, 7);
    gint *data = gwy_nield_get_data(nield);
    for (guint i = 0; i < 13*7; i++)
        data[i] = (i*i) % 11;

    return nield;
}

void
test_nield_serialization(void)
{
    GwyNield *nield = create_nield_for_serialisation();
    serialize_object_and_back(G_OBJECT(nield), nield_assert_equal, FALSE, NULL);

    g_assert_finalize_object(nield);
}

void
test_nield_copy(void)
{
    GwyNield *nield = create_nield_for_serialisation();
    serializable_test_copy(GWY_SERIALIZABLE(nield), nield_assert_equal);

    g_assert_finalize_object(nield);
}

void
test_nield_assign(void)
{
    GwyNield *nield = create_nield_for_serialisation();
    serializable_test_assign(GWY_SERIALIZABLE(nield), NULL, nield_assert_equal);

    GwyNield *another = gwy_nield_new(4, 5);
    serializable_test_assign(GWY_SERIALIZABLE(nield), GWY_SERIALIZABLE(another), nield_assert_equal);
    g_assert_finalize_object(another);

    g_assert_finalize_object(nield);
}

void
test_nield_resize(void)
{
    guint n = g_test_slow() ? 10000 : 1000;

    GwyNield *nield = gwy_nield_new(1, 1);
    for (guint k = 0; k < n; k++) {
        gint width = g_test_rand_int_range(1, 12), height = g_test_rand_int_range(1, 12);
        gwy_nield_resize(nield, width, height);
        g_assert_cmpint(gwy_nield_get_xres(nield), ==, width);
        g_assert_cmpint(gwy_nield_get_yres(nield), ==, height);
        /* Try to crash if the memory is not allocated correctly. */
        gint *d = gwy_nield_get_data(nield);
        gwy_clear(d, width*height);
    }
    g_assert_finalize_object(nield);
}

GwyNield*
make_ij_nield(void)
{
    gint xres = g_test_rand_int_range(2, 20);
    gint yres = g_test_rand_int_range(2, 20);
    GwyNield *nield = gwy_nield_new(xres, yres);
    gint *d = gwy_nield_get_data(nield);

    for (gint i = 0; i < yres; i++) {
        for (gint j = 0; j < xres; j++)
            d[i*xres + j] = SOME_NUMBER*i + j;
    }

    return nield;
}

void
test_nield_area_extract(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_ij_nield();
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gint width = g_test_rand_int_range(1, xres), height = g_test_rand_int_range(1, yres);
        gint col = g_test_rand_int_range(0, xres-width), row = g_test_rand_int_range(0, yres-height);
        GwyNield *extracted = gwy_nield_area_extract(nield, col, row, width, height);
        const gint *d = gwy_nield_get_data_const(extracted);
        for (gint i = 0; i < height; i++) {
            for (gint j = 0; j < width; j++) {
                g_assert_cmpint(d[i*width + j], ==, SOME_NUMBER*(i + row) + (j + col));
            }
        }
        g_assert_finalize_object(extracted);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_crop(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_ij_nield();
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gint width = g_test_rand_int_range(1, xres), height = g_test_rand_int_range(1, yres);
        gint col = g_test_rand_int_range(0, xres-width), row = g_test_rand_int_range(0, yres-height);
        gwy_nield_crop(nield, col, row, width, height);
        g_assert_cmpint(gwy_nield_get_xres(nield), ==, width);
        g_assert_cmpint(gwy_nield_get_yres(nield), ==, height);
        const gint *d = gwy_nield_get_data_const(nield);
        for (gint i = 0; i < height; i++) {
            for (gint j = 0; j < width; j++) {
                g_assert_cmpint(d[i*width + j], ==, SOME_NUMBER*(i + row) + (j + col));
            }
        }
        g_assert_finalize_object(nield);
    }
}

void
test_nield_area_copy(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_ij_nield();
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gint width = g_test_rand_int_range(1, xres), height = g_test_rand_int_range(1, yres);
        gint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        gint xres2 = g_test_rand_int_range(width, 25), yres2 = g_test_rand_int_range(height, 25);
        gint destcol = g_test_rand_int_range(0, xres2-width+1), destrow = g_test_rand_int_range(0, yres2-height+1);
        GwyNield *destnield = gwy_nield_new(xres2, yres2);
        gwy_nield_fill(destnield, G_PI);
        gwy_nield_area_copy(nield, destnield, col, row, width, height, destcol, destrow);
        const gint *d = gwy_nield_get_data_const(destnield);
        for (gint i = 0; i < yres2; i++) {
            for (gint j = 0; j < xres2; j++) {
                if (i >= destrow && i-destrow < height && j >= destcol && j-destcol < width)
                    g_assert_cmpint(d[i*xres2 + j], ==, SOME_NUMBER*(i + row - destrow) + (j + col - destcol));
                else
                    g_assert_cmpint(d[i*xres2 + j], ==, G_PI);
            }
        }
        g_assert_finalize_object(destnield);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_flip_x(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_ij_nield();
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gwy_nield_flip(nield, TRUE, FALSE);
        const gint *d = gwy_nield_get_data_const(nield);
        for (gint i = 0; i < yres; i++) {
            for (gint j = 0; j < xres; j++) {
                gint flipped_j = xres-1 - j;
                g_assert_cmpint(d[i*xres + j], ==, SOME_NUMBER*i + flipped_j);
            }
        }
        g_assert_finalize_object(nield);
    }
}

void
test_nield_flip_y(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_ij_nield();
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gwy_nield_flip(nield, FALSE, TRUE);
        const gint *d = gwy_nield_get_data_const(nield);
        for (gint i = 0; i < yres; i++) {
            gint flipped_i = yres-1 - i;
            for (gint j = 0; j < xres; j++) {
                g_assert_cmpint(d[i*xres + j], ==, SOME_NUMBER*flipped_i + j);
            }
        }
        g_assert_finalize_object(nield);
    }
}

void
test_nield_flip_xy(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_ij_nield();
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gwy_nield_flip(nield, TRUE, TRUE);
        const gint *d = gwy_nield_get_data_const(nield);
        for (gint i = 0; i < yres; i++) {
            gint flipped_i = yres-1 - i;
            for (gint j = 0; j < xres; j++) {
                gint flipped_j = xres-1 - j;
                g_assert_cmpint(d[i*xres + j], ==, SOME_NUMBER*flipped_i + flipped_j);
            }
        }
        g_assert_finalize_object(nield);
    }
}

void
test_nield_setval(void)
{
    gint xres = g_test_rand_int_range(2, 20);
    gint yres = g_test_rand_int_range(2, 20);
    GwyNield *nield = gwy_nield_new(xres, yres);
    gwy_nield_fill(nield, G_PI);

    for (gint i = 0; i < yres; i++) {
        for (gint j = 0; j < xres; j++)
            gwy_nield_set_val(nield, j, i, i*SOME_NUMBER + j);
    }
    for (gint i = 0; i < yres; i++) {
        for (gint j = 0; j < xres; j++)
            g_assert_cmpint(gwy_nield_get_val(nield, j, i), ==, i*SOME_NUMBER + j);
    }
    g_assert_finalize_object(nield);
}

void
test_nield_transpose_major(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_ij_nield();
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        GwyNield *transposed = gwy_nield_new(1, 1);
        gwy_nield_transpose(nield, transposed, FALSE);
        g_assert_cmpint(gwy_nield_get_xres(transposed), ==, yres);
        g_assert_cmpint(gwy_nield_get_yres(transposed), ==, xres);
        const gint *d = gwy_nield_get_data_const(transposed);
        for (gint i = 0; i < xres; i++) {
            for (gint j = 0; j < yres; j++)
                g_assert_cmpint(d[i*yres + j], ==, SOME_NUMBER*j + i);
        }
        GwyNield *transposed2 = gwy_nield_new(1, 1);
        gwy_nield_transpose(transposed, transposed2, FALSE);
        nield_assert_equal(G_OBJECT(nield), G_OBJECT(transposed2));
        g_assert_finalize_object(transposed2);
        g_assert_finalize_object(transposed);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_transpose_minor(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_ij_nield();
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        GwyNield *transposed = gwy_nield_new(1, 1);
        gwy_nield_transpose(nield, transposed, TRUE);
        g_assert_cmpint(gwy_nield_get_xres(transposed), ==, yres);
        g_assert_cmpint(gwy_nield_get_yres(transposed), ==, xres);
        const gint *d = gwy_nield_get_data_const(transposed);
        for (gint i = 0; i < xres; i++) {
            for (gint j = 0; j < yres; j++)
                g_assert_cmpint(d[i*yres + j], ==, SOME_NUMBER*(yres-1 - j) + (xres-1 - i));
        }
        GwyNield *transposed2 = gwy_nield_new(1, 1);
        gwy_nield_transpose(transposed, transposed2, TRUE);
        nield_assert_equal(G_OBJECT(nield), G_OBJECT(transposed2));
        g_assert_finalize_object(transposed2);
        g_assert_finalize_object(transposed);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_count_full(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        gint xres = g_test_rand_int_range(2, 20);
        gint yres = g_test_rand_int_range(2, 20);
        GwyNield *nield = gwy_nield_new(xres, yres);
        gint count = g_test_rand_int_range(0, 2*xres*yres/3);
        gint cnt = 0;
        while (cnt < count) {
            gint j = g_test_rand_int_range(0, xres);
            gint i = g_test_rand_int_range(0, yres);
            if (gwy_nield_get_val(nield, j, i) <= 0) {
                gwy_nield_set_val(nield, j, i, g_test_rand_int_range(1, 5));
                cnt++;
            }
        }
        g_assert_cmpint(gwy_nield_count(nield), ==, count);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_count_area(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        gint xres = g_test_rand_int_range(3, 25);
        gint yres = g_test_rand_int_range(3, 25);
        gint width = g_test_rand_int_range(1, xres), height = g_test_rand_int_range(1, yres);
        gint col = g_test_rand_int_range(0, xres-width), row = g_test_rand_int_range(0, yres-height);
        GwyNield *nield = gwy_nield_new(xres, yres);
        gwy_nield_fill(nield, 1);
        gwy_nield_area_clear(nield, col, row, width, height);
        gint count = g_test_rand_int_range(0, MAX(2*width*height/3, 1));
        gint cnt = 0;
        while (cnt < count) {
            gint j = g_test_rand_int_range(0, width);
            gint i = g_test_rand_int_range(0, height);
            if (gwy_nield_get_val(nield, col+j, row+i) <= 0) {
                gwy_nield_set_val(nield, col+j, row+i, g_test_rand_int_range(1, 5));
                cnt++;
            }
        }
        g_assert_cmpint(gwy_nield_area_count(nield, NULL, GWY_MASK_IGNORE, col, row, width, height), ==, count);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_count_masked(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        gint xres = g_test_rand_int_range(3, 25);
        gint yres = g_test_rand_int_range(3, 25);
        gint width = g_test_rand_int_range(1, xres), height = g_test_rand_int_range(1, yres);
        gint col = g_test_rand_int_range(0, xres-width), row = g_test_rand_int_range(0, yres-height);
        GwyNield *nield = gwy_nield_new(xres, yres);
        GwyNield *mask = gwy_nield_new(xres, yres);
        gwy_nield_fill(nield, 1);
        gwy_nield_area_clear(nield, col, row, width, height);
        gwy_nield_fill(mask, 1);
        gint count = g_test_rand_int_range(0, MAX(2*width*height/3, 1));
        gint cnt = 0, expected = 0;
        while (cnt < count) {
            gint j = g_test_rand_int_range(0, width);
            gint i = g_test_rand_int_range(0, height);
            if (gwy_nield_get_val(nield, col+j, row+i) <= 0) {
                gwy_nield_set_val(nield, col+j, row+i, g_test_rand_int_range(1, 5));
                if (g_test_rand_int() % 2) {
                    gwy_nield_set_val(mask, col+j, row+i, -1);
                    expected++;
                }
                cnt++;
            }
        }
        g_assert_cmpint(gwy_nield_area_count(nield, mask, GWY_MASK_EXCLUDE, col, row, width, height), ==, expected);
        g_assert_finalize_object(nield);
        g_assert_finalize_object(mask);
    }
}

static void
assert_nield_compatibility(gint xres1, gint yres1,
                           gint xres2, gint yres2,
                           GwyDataMismatchFlags flags_to_test,
                           GwyDataMismatchFlags expected_result)
{
    GwyNield *nield1 = gwy_nield_new(xres1, yres1);
    GwyNield *nield2 = gwy_nield_new(xres2, yres2);

    g_assert_cmphex(gwy_nield_is_incompatible(nield1, nield2, flags_to_test), ==, expected_result);
    g_assert_finalize_object(nield1);
    g_assert_finalize_object(nield2);
}

void
test_nield_compatibility_res(void)
{
    assert_nield_compatibility(1, 2,
                               1, 2,
                               GWY_DATA_MISMATCH_RES, 0);
    assert_nield_compatibility(1, 2,
                               1, 1,
                               GWY_DATA_MISMATCH_RES, GWY_DATA_MISMATCH_RES);
    assert_nield_compatibility(1, 2,
                               2, 2,
                               GWY_DATA_MISMATCH_RES, GWY_DATA_MISMATCH_RES);
    assert_nield_compatibility(1, 2,
                               2, 1,
                               GWY_DATA_MISMATCH_RES, GWY_DATA_MISMATCH_RES);
}

void
test_nield_grain_number_contiguous_simple(void)
{
    static const gchar src[] =
        "  1                           22  2         \n"
        "  11  11111111  1   33   22  2222222222  2  \n"
        " 11   1  1  11111   3  3  22222222   22222  \n"
        "  1 111 11  11      3  3   22222222    2    \n"
        "111         11    333333    22  222         \n"
        "1 111111111111    3   3           2      4  \n"
        "1                333333  555 555      6     \n"
        "   7   88   555  3       5 5 5 5    66666666\n"
        " 777        5 5  3   555 5 5 5 5           6\n"
        "     9  555 5 5  3   5 5 5 5 5 5 555   66666\n"
        "        5 5 5 5  3   5 5 5 555 5 5 5       6\n"
        "aaaaaa  5 555 5  3   5 555     5 5 5 555    \n"
        "a    a  5     5      5         555 5 5 5   b\n"
        "a    a  5  5  55555555   c         5 5     b\n"
        "aaaaaa  5555             cccc  d   555  e   ";

    GwyNield *reference = parse_nield(src);
    GwyNield *nield = gwy_nield_copy(reference);
    gwy_nield_flatten(nield);
    g_assert_cmpint(gwy_nield_max(nield), ==, 1);

    gwy_nield_number_contiguous(nield);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(reference));
    g_assert_cmpint(gwy_nield_max(nield), ==, 14);

    gwy_nield_flatten(nield);
    g_assert_cmpint(gwy_nield_max(nield), ==, 1);

    gwy_nield_split_noncontiguous(nield);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(reference));
    g_assert_cmpint(gwy_nield_max(nield), ==, 14);

    g_assert_finalize_object(nield);
    g_assert_finalize_object(reference);
}

void
test_nield_grain_number_contiguous_spiral(void)
{
    static const gchar src[] =
        "1 2222222222222222222222222 3\n"
        "  2                       2  \n"
        "2 2 22222222222222222222  2 4\n"
        "2 2 2                  2  2  \n"
        "2 2 2 222222222222222  2  2 5\n"
        "2 2 2 2             2  2  2  \n"
        "2 2 2 2 2222222222  2  2  2 6\n"
        "2 2 2 2 2        2  2  2  2  \n"
        "2 2 2 2 2 22222  2  2  2  2 7\n"
        "2 2 2 2 2     2  2  2  2  2  \n"
        "2 2 2 2 2222222  2  2  2  2 8\n"
        "2 2 2 2          2  2  2  2  \n"
        "2 2 2 222222222222  2  2  2 9\n"
        "2 2 2               2  2  2  \n"
        "2 2 22222222222222222  2  2 a\n"
        "2 2                    2  2  \n"
        "2 2222222222222222222222  2 b\n"
        "2                         2  \n"
        "222222222222222222222222222 c";

    GwyNield *reference = parse_nield(src);
    GwyNield *nield = gwy_nield_copy(reference);
    gwy_nield_flatten(nield);
    g_assert_cmpint(gwy_nield_max(nield), ==, 1);

    gwy_nield_number_contiguous(nield);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(reference));
    g_assert_cmpint(gwy_nield_max(nield), ==, 12);

    gwy_nield_flatten(nield);
    g_assert_cmpint(gwy_nield_max(nield), ==, 1);

    gwy_nield_split_noncontiguous(nield);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(reference));
    g_assert_cmpint(gwy_nield_max(nield), ==, 12);

    g_assert_finalize_object(nield);
    g_assert_finalize_object(reference);
}

void
test_nield_grain_number_contiguous_negative(void)
{
    static const gchar src[] =
        " -- 11-222    \n"
        "    1    2    \n"
        "11111    2    \n"
        " -       2222 \n"
        " 3----------- \n"
        " 33333333333  \n"
        "   3     3 3  ";

    GwyNield *reference = parse_nield(src);
    GwyNield *nield = gwy_nield_copy(reference);
    gint *d = gwy_nield_get_data(nield);
    for (gint i = 0; i < 14*7; i++) {
        if (d[i] > 0)
            d[i] = 1;
    }
    /* Implicit invalidate. */
    g_assert_cmpint(gwy_nield_max(nield), ==, 1);

    gwy_nield_number_contiguous(nield);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(reference));
    g_assert_cmpint(gwy_nield_max(nield), ==, 3);

    d = gwy_nield_get_data(nield);
    for (gint i = 0; i < 14*7; i++) {
        if (d[i] > 0)
            d[i] = 1;
    }
    /* Implicit invalidate. */
    g_assert_cmpint(gwy_nield_max(nield), ==, 1);

    gwy_nield_split_noncontiguous(nield);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(reference));
    g_assert_cmpint(gwy_nield_max(nield), ==, 3);

    g_assert_finalize_object(nield);
    g_assert_finalize_object(reference);
}

static GwyNield*
make_random_nield(gboolean also_negative, gint k_empty)
{
    gint xres = g_test_rand_int_range(2, 20);
    gint yres = g_test_rand_int_range(2, 20);
    GwyNield *nield = gwy_nield_new(xres, yres);
    gint *d = gwy_nield_get_data(nield);

    for (gint i = 0; i < yres; i++) {
        for (gint j = 0; j < xres; j++) {
            if (g_test_rand_int() % k_empty == 0) {
                if (also_negative && g_test_rand_int() % 10 == 0)
                    d[i*xres + j] = -1;
                else
                    d[i*xres + j] = 0;
            }
            else {
                gint k = 1;
                while (g_test_rand_int() % 2)
                    k++;
                d[i*xres + j] = k;
            }
        }
    }

    return nield;
}

GwyNield*
make_random_mask(gint xres, gint yres, gboolean also_negative, gboolean nonempty, gint maxgno)
{
    GwyNield *nield = gwy_nield_new(xres, yres);

    do {
        gint *d = gwy_nield_get_data(nield);
        for (gint i = 0; i < yres; i++) {
            for (gint j = 0; j < xres; j++) {
                if (g_test_rand_int() % 2) {
                    if (also_negative && g_test_rand_int() % 3 == 1)
                        d[i*xres + j] = -1;
                    else
                        d[i*xres + j] = 0;
                }
                else
                    d[i*xres + j] = g_test_rand_int_range(1, maxgno+1);
            }
        }
    } while (nonempty && gwy_nield_max(nield) <= 0);

    return nield;
}

void
test_nield_grain_isolate_reconstruction(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_random_nield(FALSE, 3);
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gint m = gwy_nield_max(nield);
        GwyNield *isolated = gwy_nield_new_alike(nield);
        GwyNield *another = gwy_nield_new_alike(nield);

        /* Rebuild it grain by grain. */
        for (gint i = 1; i <= m; i++) {
            gwy_nield_copy_data(nield, isolated);
            gwy_nield_isolate(isolated, i, NULL);
            g_assert_cmpint(gwy_nield_max(isolated), >=, 0);
            g_assert_cmpint(gwy_nield_max(isolated), <=, 1);
            gint *d = gwy_nield_get_data(isolated);
            for (gint j = 0; j < xres*yres; j++) {
                if (d[j] > 0)
                    d[j] = i;
            }
            gwy_nield_union(another, isolated);
        }

        nield_assert_equal(G_OBJECT(another), G_OBJECT(nield));

        g_assert_finalize_object(another);
        g_assert_finalize_object(isolated);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_grain_isolate_bbox(void)
{
    guint n = g_test_slow() ? 10000 : 1000;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_random_nield(FALSE, 2);
        gint ngrains = gwy_nield_max(nield);
        gint gno = ngrains ? g_test_rand_int_range(1, ngrains+1) : 0;

        gint *bboxes = gwy_nield_bounding_boxes(nield, NULL);
        gint gbox[4];
        gwy_nield_isolate(nield, gno, gbox);
        if (bboxes[4*gno + 2] < 0) {
            g_assert_cmpint(gbox[2], <, 0);
            g_assert_cmpint(gbox[3], <, 0);
        }
        else {
            g_assert_cmpint(gbox[0], ==, bboxes[4*gno + 0]);
            g_assert_cmpint(gbox[1], ==, bboxes[4*gno + 1]);
            g_assert_cmpint(gbox[2], ==, bboxes[4*gno + 2]);
            g_assert_cmpint(gbox[3], ==, bboxes[4*gno + 3]);
        }

        g_free(bboxes);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_grain_clear_at(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield;

        do {
            nield = make_random_nield(TRUE, 3);
        }
        while (gwy_nield_max(nield) <= 0);

        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gint i, j;
        do {
            j = g_test_rand_int_range(0, xres);
            i = g_test_rand_int_range(0, yres);
        } while (gwy_nield_get_val(nield, j, i) <= 0);

        GwyNield *another = gwy_nield_copy(nield);
        gwy_nield_split_noncontiguous(another);
        gwy_nield_clear_by_number(another, gwy_nield_get_val(another, j, i), FALSE);
        gwy_nield_clear_at(nield, j, i);
        gwy_nield_flatten(nield);
        gwy_nield_flatten(another);

        nield_assert_equal(G_OBJECT(another), G_OBJECT(nield));

        g_assert_finalize_object(another);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_grain_isolate_at_reconstruction(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_random_nield(FALSE, 3);
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        GwyNield *workspace = gwy_nield_copy(nield);
        GwyNield *isolated = gwy_nield_new_alike(nield);
        GwyNield *another = gwy_nield_new_alike(nield);

        /* Rebuild it grain piece by grain piece. */
        while (gwy_nield_max(workspace) > 0) {
            gint *w = gwy_nield_get_data(workspace);
            gint i, j;
            for (i = 0; i < yres; i++) {
                for (j = 0; j < xres; j++) {
                    if (w[i*xres + j] > 0)
                        break;
                }
                if (j < xres)
                    break;
            }
            gint v = w[i*xres + j];
            gwy_nield_copy_data(workspace, isolated);
            gwy_nield_clear_at(workspace, j, i);
            gwy_nield_isolate_at(isolated, j, i, NULL);
            g_assert_cmpint(gwy_nield_max(isolated), ==, 1);
            gint *d = gwy_nield_get_data(isolated);
            for (j = 0; j < xres*yres; j++) {
                if (d[j] > 0)
                    d[j] = v;
            }
            gwy_nield_union(another, isolated);
        }

        nield_assert_equal(G_OBJECT(another), G_OBJECT(nield));

        g_assert_finalize_object(another);
        g_assert_finalize_object(isolated);
        g_assert_finalize_object(workspace);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_grain_isolate_at_bbox(void)
{
    guint n = g_test_slow() ? 2000 : 200;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_random_nield(FALSE, 2);
        gint ngrains = gwy_nield_number_contiguous(nield);
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);

        gint j = g_test_rand_int_range(0, xres);
        gint i = g_test_rand_int_range(0, yres);
        gint gbox[4];
        if (!ngrains) {
            gwy_nield_isolate_at(nield, j, i, gbox);
            /* These are guaranteed. The other two we set somehow, but do not specify. */
            g_assert_cmpint(gbox[2], <, 0);
            g_assert_cmpint(gbox[3], <, 0);
        }
        else {
            while (gwy_nield_get_val(nield, j, i) <= 0) {
                j = g_test_rand_int_range(0, xres);
                i = g_test_rand_int_range(0, yres);
            }

            gint gno = gwy_nield_get_val(nield, j, i);
            gint *bboxes = gwy_nield_bounding_boxes(nield, NULL);
            gwy_nield_isolate_at(nield, j, i, gbox);
            g_assert_cmpint(gbox[0], ==, bboxes[4*gno + 0]);
            g_assert_cmpint(gbox[1], ==, bboxes[4*gno + 1]);
            g_assert_cmpint(gbox[2], ==, bboxes[4*gno + 2]);
            g_assert_cmpint(gbox[3], ==, bboxes[4*gno + 3]);
            g_free(bboxes);
        }

        g_assert_finalize_object(nield);
    }
}

typedef gdouble (*DistanceFunc)(gint j1, gint i1, gint j2, gint i2);

static gdouble
distance_4connect(gint j1, gint i1, gint j2, gint i2)
{
    return ABS(j1 - j2) + ABS(i1 - i2);
}

static gdouble
distance_8connect(gint j1, gint i1, gint j2, gint i2)
{
    return MAX(ABS(j1 - j2), ABS(i1 - i2));
}

static gdouble
distance_octagonal48(gint j1, gint i1, gint j2, gint i2)
{
    gint di = ABS(i1 - i2), dj = ABS(j1 - j2);
    gint m = MIN(di, dj);
    gint M = MAX(di, dj);
    if (M >= 2*m)
        return M;
    return 2*(di + dj + 1)/3;
}

static gdouble
distance_octagonal84(gint j1, gint i1, gint j2, gint i2)
{
    gint di = ABS(i1 - i2), dj = ABS(j1 - j2);
    gint m = MIN(di, dj);
    gint M = MAX(di, dj);
    if (M >= 2*m)
        return M;
    return (2*(di + dj) + 1)/3;
}

static gdouble
distance_octagonal(gint j1, gint i1, gint j2, gint i2)
{
    gint di = ABS(i1 - i2), dj = ABS(j1 - j2);
    gint m = MIN(di, dj);
    gint M = MAX(di, dj);
    if (M >= 2*m)
        return M;
    return ((2*(di + dj) + 1)/3 + 2*(di + dj + 1)/3)/2.0;
}

static gdouble
distance_euclidean(gint j1, gint i1, gint j2, gint i2)
{
    return hypot(j1 - j2, i1 - i2);
}

static void
test_one_distance(GwyDistanceTransformType dist_type,
                  gboolean merged, gboolean from_border,
                  DistanceFunc distance_func)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint k = 0; k < n; k++) {
        GwyNield *nield = make_random_nield(FALSE, 5);
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        GwyField *distances = gwy_field_new(1, 1, 1, 1, FALSE);

        gwy_nield_distance_transform(nield, distances, dist_type, merged, from_border);
        g_assert_cmpint(gwy_field_get_xres(distances), ==, xres);
        g_assert_cmpint(gwy_field_get_yres(distances), ==, yres);

        const gdouble *dist = gwy_field_get_data(distances);
        const gint *grains = gwy_nield_get_data(nield);

        for (gint i1 = 0; i1 < yres; i1++) {
            for (gint j1 = 0; j1 < xres; j1++) {
                gint gno = grains[i1*xres + j1];
                gdouble d = dist[i1*xres + j1];
                if (gno <= 0) {
                    g_assert_cmpfloat(d, ==, 0.0);
                    continue;
                }
                gdouble d_bruteforce = G_MAXDOUBLE;
                for (gint i2 = 0; i2 < yres; i2++) {
                    for (gint j2 = 0; j2 < xres; j2++) {
                        // For merged, non-positive values are outside. For non-merged, anything not matching the
                        // grain number is outside and we consider the distance from it.
                        if ((merged && grains[i2*xres + j2] <= 0) || (!merged && grains[i2*xres + j2] != gno))
                            d_bruteforce = fmin(d_bruteforce, distance_func(j1, i1, j2, i2));
                    }
                }
                /* Empty space around the image. */
                if (from_border) {
                    for (gint i2 = 0; i2 < yres; i2++) {
                        d_bruteforce = fmin(d_bruteforce, distance_func(j1, i1, -1, i2));
                        d_bruteforce = fmin(d_bruteforce, distance_func(j1, i1, xres, i2));
                    }
                    for (gint j2 = 0; j2 < xres; j2++) {
                        d_bruteforce = fmin(d_bruteforce, distance_func(j1, i1, j2, -1));
                        d_bruteforce = fmin(d_bruteforce, distance_func(j1, i1, j2, yres));
                    }
                }

                if (d_bruteforce == G_MAXDOUBLE)
                    g_assert_cmpfloat(d, >=, G_MAXDOUBLE);
                else
                    g_assert_cmpfloat_with_epsilon(d, d_bruteforce, 2*d_bruteforce*DBL_EPSILON);
            }
        }

        g_assert_finalize_object(distances);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_distance_transform_conn4_merged_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_CONN4, TRUE, FALSE, distance_4connect);
}

void
test_nield_distance_transform_conn4_merged_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_CONN4, TRUE, TRUE, distance_4connect);
}

void
test_nield_distance_transform_conn8_merged_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_CONN8, TRUE, FALSE, distance_8connect);
}

void
test_nield_distance_transform_conn8_merged_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_CONN8, TRUE, TRUE, distance_8connect);
}

void
test_nield_distance_transform_octagonal48_merged_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL48, TRUE, FALSE, distance_octagonal48);
}

void
test_nield_distance_transform_octagonal48_merged_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL48, TRUE, TRUE, distance_octagonal48);
}

void
test_nield_distance_transform_octagonal84_merged_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL84, TRUE, FALSE, distance_octagonal84);
}

void
test_nield_distance_transform_octagonal84_merged_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL84, TRUE, TRUE, distance_octagonal84);
}

void
test_nield_distance_transform_octagonal_merged_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL, TRUE, FALSE, distance_octagonal);
}

void
test_nield_distance_transform_octagonal_merged_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL, TRUE, TRUE, distance_octagonal);
}

void
test_nield_distance_transform_euclidean_merged_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_EUCLIDEAN, TRUE, FALSE, distance_euclidean);
}

void
test_nield_distance_transform_euclidean_merged_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_EUCLIDEAN, TRUE, TRUE, distance_euclidean);
}

void
test_nield_distance_transform_conn4_split_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_CONN4, FALSE, FALSE, distance_4connect);
}

void
test_nield_distance_transform_conn4_split_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_CONN4, FALSE, TRUE, distance_4connect);
}

void
test_nield_distance_transform_conn8_split_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_CONN8, FALSE, FALSE, distance_8connect);
}

void
test_nield_distance_transform_conn8_split_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_CONN8, FALSE, TRUE, distance_8connect);
}

void
test_nield_distance_transform_octagonal48_split_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL48, FALSE, FALSE, distance_octagonal48);
}

void
test_nield_distance_transform_octagonal48_split_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL48, FALSE, TRUE, distance_octagonal48);
}

void
test_nield_distance_transform_octagonal84_split_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL84, FALSE, FALSE, distance_octagonal84);
}

void
test_nield_distance_transform_octagonal84_split_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL84, FALSE, TRUE, distance_octagonal84);
}

void
test_nield_distance_transform_octagonal_split_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL, FALSE, FALSE, distance_octagonal);
}

void
test_nield_distance_transform_octagonal_split_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_OCTAGONAL, FALSE, TRUE, distance_octagonal);
}

void
test_nield_distance_transform_euclidean_split_noborder(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_EUCLIDEAN, FALSE, FALSE, distance_euclidean);
}

void
test_nield_distance_transform_euclidean_split_border(void)
{
    test_one_distance(GWY_DISTANCE_TRANSFORM_EUCLIDEAN, FALSE, TRUE, distance_euclidean);
}

void
test_nield_resample_down(void)
{
    static const gchar src[] =
        "123  \n"
        "  4 5\n"
        " 67 8\n"
        " 9ab ";
    static const gchar esrc[] =
        "11 \n"
        " 11\n"
        " 1 ";

    GwyNield *nield = parse_nield(src);
    GwyNield *expected = parse_nield(esrc);

    GwyNield *resampled = gwy_nield_new_resampled(nield, gwy_nield_get_xres(expected), gwy_nield_get_yres(expected));
    nield_assert_equal(G_OBJECT(resampled), G_OBJECT(expected));

    g_assert_finalize_object(nield);
    g_assert_finalize_object(resampled);
    g_assert_finalize_object(expected);
}

void
test_nield_resample_up(void)
{
    static const gchar src[] =
        "1 23 \n"
        "   4 \n"
        "  567";
    static const gchar esrc[] =
        "1  111 \n"
        "1  111 \n"
        "    11 \n"
        "   1111\n"
        "   1111";

    GwyNield *nield = parse_nield(src);
    GwyNield *expected = parse_nield(esrc);

    GwyNield *resampled = gwy_nield_new_resampled(nield, gwy_nield_get_xres(expected), gwy_nield_get_yres(expected));
    nield_assert_equal(G_OBJECT(resampled), G_OBJECT(expected));

    g_assert_finalize_object(nield);
    g_assert_finalize_object(resampled);
    g_assert_finalize_object(expected);
}

static void
test_nield_extend(const gint *bigdata, guint bigxres, guint bigyres,
                  guint xres, guint yres, guint col, guint row,
                  GwyExteriorType exterior, gint fill_value)
{
    GwyNield *bignield = gwy_nield_new(bigxres, bigyres);
    gwy_assign(gwy_nield_get_data(bignield), bigdata, bigxres*bigyres);

    GwyNield *nield = gwy_nield_area_extract(bignield, col, row, xres, yres);
    for (gint left = 0; left <= col; left++) {
        for (gint right = 0; right <= bigxres - (col + xres); right++) {
            for (gint up = 0; up <= row; up++) {
                for (gint down = 0; down <= bigyres - (row + yres); down++) {
                    //g_printerr("ext [%d,%d]x[%d,%d]\n", left, right, up, down);
                    GwyNield *expected = gwy_nield_area_extract(bignield,
                                                                col - left, row - up,
                                                                xres + left + right, yres + up + down);
                    GwyNield *extended = gwy_nield_extend(nield, left, right, up, down,
                                                          exterior, fill_value);
                    nield_assert_equal(G_OBJECT(extended), G_OBJECT(expected));

                    g_assert_finalize_object(extended);
                    g_assert_finalize_object(expected);
                }
            }
        }
    }

    g_assert_finalize_object(nield);
    g_assert_finalize_object(bignield);
}

void
test_nield_extend_border(void)
{
    enum { bigxres = 9, bigyres = 9 };
    enum { xres = 3, yres = 2 };
    enum { col = 3, row = 4 };
    const gint bigdata[bigxres*bigyres] = {
          4,   4,   4,     4, -1,   0,     0,   0,   0,
          4,   4,   4,     4, -1,   0,     0,   0,   0,
          4,   4,   4,     4, -1,   0,     0,   0,   0,
          4,   4,   4,     4, -1,   0,     0,   0,   0,

          4,   4,  4,      4, -1,   0,     0,   0,   0,
        314, 314, 314,   314,  5, 1e5,   1e5, 1e5, 1e5,

        314, 314, 314,   314,  5, 1e5,   1e5, 1e5, 1e5,
        314, 314, 314,   314,  5, 1e5,   1e5, 1e5, 1e5,
        314, 314, 314,   314,  5, 1e5,   1e5, 1e5, 1e5,
    };

    test_nield_extend(bigdata, bigxres, bigyres, xres, yres, col, row, GWY_EXTERIOR_BORDER, 1234567);
}

void
test_nield_extend_mirror(void)
{
    enum { bigxres = 11, bigyres = 8 };
    enum { xres = 3, yres = 2 };
    enum { col = 4, row = 3 };
    const gint bigdata[bigxres*bigyres] = {
        105, 105,  5, 314,   314,  5, 105,   105,  5, 314, 314,
        105, 105,  5, 314,   314,  5, 105,   105,  5, 314, 314,
        0,     0, -1,   4,     4, -1,   0,     0, -1,   4,   4,

          0,   0, -1,   4,     4, -1,   0,     0, -1,   4,   4,
        105, 105,  5, 314,   314,  5, 105,   105,  5, 314, 314,

        105, 105,  5, 314,   314,  5, 105,   105,  5, 314, 314,
          0,   0, -1,   4,     4, -1,   0,     0, -1,   4,   4,
          0,   0, -1,   4,     4, -1,   0,     0, -1,   4,   4,
    };

    test_nield_extend(bigdata, bigxres, bigyres, xres, yres, col, row, GWY_EXTERIOR_MIRROR, 1234);
}

void
test_nield_extend_periodic(void)
{
    enum { bigxres = 11, bigyres = 8 };
    enum { xres = 3, yres = 2 };
    enum { col = 4, row = 3 };
    const gint bigdata[bigxres*bigyres] = {
        105, 314,  5, 105,   314,  5, 105,   314,  5, 105, 314,
          0,   4, -1,   0,     4, -1,   0,     4, -1,   0,   4,
        105, 314,  5, 105,   314,  5, 105,   314,  5, 105, 314,

          0,   4, -1,   0,     4, -1,   0,     4, -1,   0,   4,
        105, 314,  5, 105,   314,  5, 105,   314,  5, 105, 314,

          0,   4, -1,   0,     4, -1,   0,     4, -1,   0,   4,
        105, 314,  5, 105,   314,  5, 105,   314,  5, 105, 314,
          0,   4, -1,   0,     4, -1,   0,     4, -1,   0,   4,
    };

    test_nield_extend(bigdata, bigxres, bigyres, xres, yres, col, row, GWY_EXTERIOR_PERIODIC, 1234);
}

void
test_nield_extend_fixed(void)
{
    enum { bigxres = 11, bigyres = 8 };
    enum { xres = 3, yres = 2 };
    enum { col = 4, row = 3 };
    const gint bigdata[bigxres*bigyres] = {
        14, 14, 14, 14,    14, 14, 14,   14, 14, 14, 14,
        14, 14, 14, 14,    14, 14, 14,   14, 14, 14, 14,
        14, 14, 14, 14,    14, 14, 14,   14, 14, 14, 14,

        14, 14, 14, 14,    4, -1,   0,   14, 14, 14, 14,
        14, 14, 14, 14,  314,  5, 105,   14, 14, 14, 14,

        14, 14, 14, 14,    14, 14, 14,   14, 14, 14, 14,
        14, 14, 14, 14,    14, 14, 14,   14, 14, 14, 14,
        14, 14, 14, 14,    14, 14, 14,   14, 14, 14, 14,
    };

    test_nield_extend(bigdata, bigxres, bigyres, xres, yres, col, row, GWY_EXTERIOR_FIXED_VALUE, 14);
}

void
test_nield_extend_undefined(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint i = 0; i < n; i++) {
        GwyNield *nield = make_random_nield(TRUE, 1000);
        gint left = g_test_rand_int_range(0, 10);
        gint right = g_test_rand_int_range(0, 10);
        gint up = g_test_rand_int_range(0, 10);
        gint down = g_test_rand_int_range(0, 10);
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        GwyNield *extended = gwy_nield_extend(nield, left, right, up, down,
                                              GWY_EXTERIOR_UNDEFINED, 1234567);
        GwyNield *extracted = gwy_nield_area_extract(extended, left, up, xres, yres);
        nield_assert_equal(G_OBJECT(extracted), G_OBJECT(nield));

        g_assert_finalize_object(nield);
        g_assert_finalize_object(extracted);
        g_assert_finalize_object(extended);
    }
}

/* It is basically a Nield function. Test it nield.c. */
void
test_field_fill_mask_01(void)
{
    guint n = g_test_slow() ? 100 : 10;

    for (guint k = 0; k < n; k++) {
        gint xres = g_test_rand_int_range(3, 15);
        gint yres = g_test_rand_int_range(3, 15);
        gint npix = xres*yres;
        GwyField *field = gwy_field_new(xres, yres, xres, yres, FALSE);
        gwy_field_fill(field, -G_PI);

        GwyNield *nield = gwy_field_new_nield_alike(field);
        for (gint i = 0; i < 2*npix/3; i++) {
            gint col = g_test_rand_int_range(0, xres);
            gint row = g_test_rand_int_range(0, yres);
            gwy_nield_set_val(nield, col, row, 5);
        }

        gint count = gwy_nield_count(nield);
        gwy_field_fill_mask(field, nield, 0.0, 1.0);
        g_assert_cmpfloat(gwy_field_max(field), ==, 1.0);
        g_assert_cmpfloat(gwy_field_min(field), ==, 0.0);
        g_assert_cmpfloat_with_epsilon(gwy_field_mean(field), (gdouble)count/npix, DBL_EPSILON*npix);

        gwy_nield_flatten(nield);
        GwyNield *restored = gwy_nield_new_alike(nield);
        gwy_nield_mark_by_threshold(restored, field, 0.5, TRUE);
        nield_assert_equal(G_OBJECT(restored), G_OBJECT(nield));

        g_assert_finalize_object(restored);
        g_assert_finalize_object(nield);
        g_assert_finalize_object(field);
    }
}

void
test_nield_grain_sizes(void)
{
    // Include non-contiguous grains, non-contiguous numbering, touching grains and all kinds of nonsense.
    static const gchar src[] =
        "  1 -     --- -             7--\n"
        "  1 -     -3 ----    2222    7-\n"
        "  11-     -----         2     7\n"
        "    -                   2      \n"
        "    -          -    1   222    \n"
        "    -      6                   \n"
        "8888888                 ---    \n"
        "8   - 8           6       - -  \n"
        "8888888-        666  ---   -   \n"
        "    --8-          6   a        \n"
        "           -      6-         --\n"
        "3               --6           3";
    enum { expected_ngrains = 10 };
    static const gint expected_sizes[expected_ngrains + 1] = {
        0, 5, 9, 3, 0, 0, 8, 3, 17, 0, 1
    };

    GwyNield *nield = parse_nield(src);
    gint ngrains;
    gint *sizes = gwy_nield_sizes(nield, &ngrains);
    g_assert_cmpint(ngrains, ==, expected_ngrains);

    for (gint i = 1; i <= expected_ngrains; i++)
        g_assert_cmpint(sizes[i], ==, expected_sizes[i]);

    g_free(sizes);
    g_assert_finalize_object(nield);
}

void
test_nield_grain_bounding_boxes(void)
{
    // Include non-contiguous grains, non-contiguous numbering, touching grains and all kinds of nonsense.
    static const gchar src[] =
        " 111          ---           9    - \n"
        "  11111        -   2       999     \n"
        "      1      -   d          9      \n"
        "      -   -      d          9      \n"
        "          cccccccd      99999999   \n"
        "   4       -     d      9  e   9   \n"
        "   4          -  d      9  eeee9   \n"
        "   4   666       d      9----- 9   \n"
        "   4          -----     99999999   \n"
        "   4444444       6   -  -     --   \n"
        "--                    -      ----  \n"
        "6-                                 ";
    enum { expected_ngrains = 14 };
    static const gint expected_boxes[4*(expected_ngrains + 1)] = {
         0,  0,  0,  0,
         1,  0,  6,  3, // 1
        19,  1,  1,  1, // 2
        -1, -1, -1, -1, // 3
         3,  5,  7,  5, // 4
        -1, -1, -1, -1, // 5
         0,  7, 18,  5, // 6
        -1, -1, -1, -1, // 7
        -1, -1, -1, -1, // 8
        24,  0,  8,  9, // 9
        -1, -1, -1, -1, // a
        -1, -1, -1, -1, // b
        10,  4,  7,  1, // c
        17,  2,  1,  6, // d
        27,  5,  4,  2, // e
    };

    GwyNield *nield = parse_nield(src);
    gint ngrains;
    gint *boxes = gwy_nield_bounding_boxes(nield, &ngrains);
    g_assert_cmpint(ngrains, ==, expected_ngrains);

    for (gint i = 1; i <= expected_ngrains; i++) {
        if (expected_boxes[4*i + 2] < 0) {
            /* For empty grains we only promise the dimensions are negative. Anything else is undefined. */
            g_assert_cmpint(boxes[4*i + 2], <, 0);
            g_assert_cmpint(boxes[4*i + 3], <, 0);
        }
        else {
            g_assert_cmpint(boxes[4*i + 0], ==, expected_boxes[4*i + 0]);
            g_assert_cmpint(boxes[4*i + 1], ==, expected_boxes[4*i + 1]);
            g_assert_cmpint(boxes[4*i + 2], ==, expected_boxes[4*i + 2]);
            g_assert_cmpint(boxes[4*i + 3], ==, expected_boxes[4*i + 3]);
        }
    }

    g_free(boxes);
    g_assert_finalize_object(nield);
}

void
test_nield_grain_bounding_boxes_periodic(void)
{
    static const gchar src[] =
        "9      72     2 5      2    2     9\n"
        "        2     2  5     2    2      \n"
        "111               5             111\n"
        "   1               5               \n"
        "44441444444444444444444444444444444\n"
        "                                   \n"
        " 7               55                \n"
        "33333333333333333333333333333333333\n"
        "   7              5                \n"
        "    7  222         5       66666666\n"
        "     7  2           5            99\n"
        "9     7 2            5           99";
    enum { expected_ngrains = 9 };
    static const gint expected_boxes[4*(expected_ngrains + 1)] = {
         0,  0,  0,  0,
        32,  2,  8,  3, // 1
         7,  9, 22,  5, // 2
         0,  7, 35,  1, // 3
         5,  4, 34,  1, // 4
        16,  6,  6, 10, // 5
        27,  9,  8,  1, // 6
         1,  6,  7,  7, // 7
        -1, -1, -1, -1, // 8
        33, 10,  3,  3, // 9
    };

    GwyNield *nield = parse_nield(src);
    gint ngrains;
    gint *boxes = gwy_nield_bounding_boxes_periodic(nield, &ngrains);
    g_assert_cmpint(ngrains, ==, expected_ngrains);

    for (gint i = 1; i <= expected_ngrains; i++) {
        if (expected_boxes[4*i + 2] < 0) {
            /* For empty grains we only promise the dimensions are negative. Anything else is undefined. */
            g_assert_cmpint(boxes[4*i + 2], <, 0);
            g_assert_cmpint(boxes[4*i + 3], <, 0);
        }
        else {
            g_assert_cmpint(boxes[4*i + 0], ==, expected_boxes[4*i + 0]);
            g_assert_cmpint(boxes[4*i + 1], ==, expected_boxes[4*i + 1]);
            g_assert_cmpint(boxes[4*i + 2], ==, expected_boxes[4*i + 2]);
            g_assert_cmpint(boxes[4*i + 3], ==, expected_boxes[4*i + 3]);
        }
    }

    g_free(boxes);
    g_assert_finalize_object(nield);
}

void
test_nield_grain_inscribed_boxes(void)
{
    // Include non-contiguous grains, non-contiguous numbering, touching grains and all kinds of nonsense.
    static const gchar src[] =
        "11111111              -444444444444\n"
        " 1111111       2222      --       4\n"
        "   111         2222         -  44 4\n"
        "   111- -      222-       -   44444\n"
        "   111   -             2222   444  \n"
        "  -----   -    7       2222        \n"
        "           --777       2222        \n"
        "      1    777                     \n"
        "           7-    2222    ------    \n"
        "         777-    2222    ------    \n"
        "        77--- -   222    ------    \n"
        "        77               ------    ";
    enum { expected_ngrains = 7 };
    static const gint expected_boxes[4*(expected_ngrains + 1)] = {
         0,  0,  0,  0,
         3,  0,  3,  5, // 1
        23,  4,  4,  3, // 2
        -1, -1, -1, -1, // 3
        23,  0, 12,  1, // 4
        -1, -1, -1, -1, // 5
        -1, -1, -1, -1, // 6
         8, 10,  2,  2, // 7
    };

    GwyNield *nield = parse_nield(src);
    gint ngrains;
    gint *boxes = gwy_nield_inscribed_boxes(nield, &ngrains);
    g_assert_cmpint(ngrains, ==, expected_ngrains);

    for (gint i = 1; i <= expected_ngrains; i++) {
        if (expected_boxes[4*i + 2] < 0) {
            /* For empty grains we only promise the dimensions are negative. Anything else is undefined. */
            g_assert_cmpint(boxes[4*i + 2], <, 0);
            g_assert_cmpint(boxes[4*i + 3], <, 0);
        }
        else {
            g_assert_cmpint(boxes[4*i + 0], ==, expected_boxes[4*i + 0]);
            g_assert_cmpint(boxes[4*i + 1], ==, expected_boxes[4*i + 1]);
            g_assert_cmpint(boxes[4*i + 2], ==, expected_boxes[4*i + 2]);
            g_assert_cmpint(boxes[4*i + 3], ==, expected_boxes[4*i + 3]);
        }
    }

    g_free(boxes);
    g_assert_finalize_object(nield);
}

void
test_nield_grain_fill_voids_simple(void)
{
    static const gchar src[] =
        " 11111                    5555555\n"
        "11   1                    5     5\n"
        " 1   1           222222   5  5  5\n"
        " 111111          2    2   5  5  5\n"
        "   1  11111      22222    5  5  5\n"
        "   1  1   1               5  5  5\n"
        "   11111111  33333333333  5  5  5\n"
        "             3         3  5  5  5\n"
        "             3  33333  3  5  5  5\n"
        "      4444   3  3   3  3  5  5  5\n"
        "444   4 44   3  33333  3  5     5\n"
        "  4   44 4   3         3  5555  5\n"
        "  44444444   33333333333     5555";
    static const gchar esrc[] =
        " 11111                    1111111\n"
        "111111                    1     1\n"
        " 11111           111111   1  1  1\n"
        " 111111          1    1   1  1  1\n"
        "   11111111      11111    1  1  1\n"
        "   11111111               1  1  1\n"
        "   11111111  11111111111  1  1  1\n"
        "             1         1  1  1  1\n"
        "             1  11111  1  1  1  1\n"
        "      1111   1  11111  1  1  1  1\n"
        "111   1111   1  11111  1  1     1\n"
        "  1   1111   1         1  1111  1\n"
        "  11111111   11111111111     1111";

    GwyNield *nield = parse_nield(src);
    GwyNield *expected = parse_nield(esrc);
    GwyNield *result = gwy_nield_copy(nield);
    gwy_nield_fill_voids(result, FALSE);
    gwy_nield_flatten(result);
    nield_assert_equal(G_OBJECT(result), G_OBJECT(expected));
    g_assert_finalize_object(result);
    g_assert_finalize_object(nield);
    g_assert_finalize_object(expected);
}

void
test_nield_grain_fill_voids_nonsimple(void)
{
    static const gchar src[] =
        " 11111                    5555555\n"
        "11   1                    5     5\n"
        " 1   1           222222   5  5  5\n"
        " 111111          2    2   5  5  5\n"
        "   1  11111      22222    5  5  5\n"
        "   1  1   1               5  5  5\n"
        "   11111111  33333333333  5  5  5\n"
        "             3         3  5  5  5\n"
        "             3  33333  3  5  5  5\n"
        "      4444   3  3   3  3  5  5  5\n"
        "444   4 44   3  33333  3  5     5\n"
        "  4   44 4   3         3  5555  5\n"
        "  44444444   33333333333     5555";
    static const gchar esrc[] =
        " 11111                    1111111\n"
        "111111                    1111111\n"
        " 11111           111111   1111111\n"
        " 111111          1    1   1111111\n"
        "   11111111      11111    1111111\n"
        "   11111111               1111111\n"
        "   11111111  11111111111  1111111\n"
        "             11111111111  1111111\n"
        "             11111111111  1111111\n"
        "      1111   11111111111  1111111\n"
        "111   1111   11111111111  1111111\n"
        "  1   1111   11111111111  1111111\n"
        "  11111111   11111111111     1111";

    GwyNield *nield = parse_nield(src);
    GwyNield *expected = parse_nield(esrc);
    GwyNield *result = gwy_nield_copy(nield);
    gwy_nield_fill_voids(result, TRUE);
    gwy_nield_flatten(result);
    nield_assert_equal(G_OBJECT(result), G_OBJECT(expected));
    g_assert_finalize_object(result);
    g_assert_finalize_object(nield);
    g_assert_finalize_object(expected);
}

void
test_nield_grain_fill_voids_unchanged(void)
{
    static const gchar src1[] =
        "122234\n"
        "74448a\n"
        "cc9a16";

    GwyNield *nield;

    nield = parse_nield(src1);
    g_assert_false(gwy_nield_fill_voids(nield, TRUE));
    g_assert_false(gwy_nield_fill_voids(nield, FALSE));
    g_assert_finalize_object(nield);

    static const gchar src2[] =
        "      \n"
        "      \n"
        "      ";
    nield = parse_nield(src2);
    g_assert_false(gwy_nield_fill_voids(nield, TRUE));
    g_assert_false(gwy_nield_fill_voids(nield, FALSE));
    g_assert_finalize_object(nield);

    static const gchar src3[] =
        "112  3\n"
        "   2  \n"
        " 3    ";
    nield = parse_nield(src3);
    g_assert_false(gwy_nield_fill_voids(nield, TRUE));
    g_assert_false(gwy_nield_fill_voids(nield, FALSE));
    g_assert_finalize_object(nield);

    static const gchar src4[] =
        "111111\n"
        "1    1\n"
        "1 11 1\n"
        "1    1\n"
        "111111";
    nield = parse_nield(src4);
    g_assert_false(gwy_nield_fill_voids(nield, FALSE));
    g_assert_true(gwy_nield_fill_voids(nield, TRUE));
    g_assert_finalize_object(nield);
}

void
test_nield_clear_touching_border_touching(void)
{
    static const gchar src1[] =
        "6  5      4\n"
        " 44111 9 3 \n"
        "   1 999 3 \n"
        "111    33  \n"
        " 71    3 3 \n"
        " 7  55555  \n"
        " 777777    \n"
        " 2 222222 2\n"
        "2 2   3333 ";
    static const gchar expected1[] =
        "           \n"
        " 44111 9 3 \n"
        "   1 999 3 \n"
        "       33  \n"
        " 7     3 3 \n"
        " 7  55555  \n"
        " 777777    \n"
        " 2 222222  \n"
        "           ";

    GwyNield *nield, *expected;

    nield = parse_nield(src1);
    expected = parse_nield(expected1);
    gwy_nield_clear_touching_border(nield, FALSE);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(expected));
    g_assert_finalize_object(expected);
    g_assert_finalize_object(nield);
}

void
test_nield_clear_touching_border_completely(void)
{
    static const gchar src1[] =
        "6  5      4\n"
        " 44111 9 3 \n"
        "   1 999 3 \n"
        "111    33  \n"
        " 71    3 3 \n"
        " 7  55555  \n"
        " 777777    \n"
        " 2 222222 2\n"
        "2 2   3333 ";
    static const gchar expected1[] =
        "           \n"
        "       9   \n"
        "     999   \n"
        "           \n"
        " 7         \n"
        " 7         \n"
        " 777777    \n"
        "           \n"
        "           ";

    GwyNield *nield, *expected;

    nield = parse_nield(src1);
    expected = parse_nield(expected1);
    gwy_nield_clear_touching_border(nield, TRUE);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(expected));
    g_assert_finalize_object(expected);
    g_assert_finalize_object(nield);
}

void
test_nield_clear_by_size(void)
{
    const gchar src1[] =
        "2222 2     77       4 \n"
        "           7          \n"
        "        2    2   4    \n"
        "1                     \n"
        "     2           66666\n"
        "            2    66666\n"
        " 2   666       7 66666\n"
        "  111111111      66666\n"
        "          333        6\n"
        "      2         25    ";
    const gchar expected1[] =
        "2222 2                \n"
        "                      \n"
        "        2    2        \n"
        "1                     \n"
        "     2           66666\n"
        "            2    66666\n"
        " 2   666         66666\n"
        "  111111111      66666\n"
        "                     6\n"
        "      2         2     ";

    GwyNield *nield, *expected;

    nield = parse_nield(src1);
    expected = parse_nield(expected1);
    gwy_nield_clear_by_size(nield, 10);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(expected));
    g_assert_finalize_object(expected);
    g_assert_finalize_object(nield);
}

static void
test_autocrop_case(const gchar *src, gboolean symmetric,
                   const gchar *expected_src,
                   gint expected_left, gint expected_right, gint expected_up, gint expected_down)
{
    GwyNield *nield = parse_nield(src);
    GwyNield *expected = parse_nield(expected_src);
    gint left, right, up, down;
    gwy_nield_autocrop(nield, symmetric, &left, &right, &up, &down);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(expected));
    g_assert_cmpint(left, ==, expected_left);
    g_assert_cmpint(right, ==, expected_right);
    g_assert_cmpint(up, ==, expected_up);
    g_assert_cmpint(down, ==, expected_down);
    g_assert_finalize_object(nield);
    g_assert_finalize_object(expected);
}

void
test_nield_autocrop_normal(void)
{
    static const gchar src1[] =
        "   \n"
        " 2 \n"
        "  1\n"
        "   \n"
        "---";
    static const gchar sym1[] =
        " 2 \n"
        "  1\n"
        "   \n";
    static const gchar asym1[] =
        "2 \n"
        " 1";

    static const gchar src2[] =
        "  -    \n"
        "   5   \n"
        " 1     \n"
        "  5  --";
    static const gchar sym2[] =
        " -   \n"
        "  5  \n"
        "1    \n"
        " 5  -";
    static const gchar asym2[] =
        "  5\n"
        "1  \n"
        " 5 ";

    test_autocrop_case(src1, TRUE, sym1, 0, 0, 1, 1);
    test_autocrop_case(src1, FALSE, asym1, 1, 0, 1, 2);
    test_autocrop_case(src2, TRUE, sym2, 1, 1, 0, 0);
    test_autocrop_case(src2, FALSE, asym2, 1, 3, 1, 0);
}

void
test_nield_autocrop_empty(void)
{
    static const gchar src1[] =
        "     \n"
        "     \n"
        "     \n"
        "     \n"
        "     ";
    static const gchar sym1[] =
        " ";
    static const gchar asym1[] =
        " ";

    static const gchar src2[] =
        "    \n"
        "    \n"
        "    \n"
        "    ";
    static const gchar sym2[] =
        "  \n"
        "  ";
    static const gchar asym2[] =
        " ";

    test_autocrop_case(src1, TRUE, sym1, 2, 2, 2, 2);
    test_autocrop_case(src1, FALSE, asym1, 2, 2, 2, 2);
    test_autocrop_case(src2, TRUE, sym2, 1, 1, 1, 1);
    test_autocrop_case(src2, FALSE, asym2, 1, 2, 1, 2);
}

static gint
minmax_the_hard_way(GwyNield *nield, GwyNield *kernel, gint col, gint row,
                    GwyMinMaxFilterType filtertype)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    gint kxres = gwy_nield_get_xres(kernel), kyres = gwy_nield_get_yres(kernel);

    gint koffx = (kxres - 1)/2, koffy = (kyres - 1)/2;

    const gint *data = gwy_nield_get_data_const(nield);
    const gint *kdata = gwy_nield_get_data(kernel);
    gint m = G_MAXINT, M = G_MININT;
    gint v0 = data[row*xres + col];
    gboolean do_flip = (filtertype == GWY_MIN_MAX_FILTER_FROSION || filtertype == GWY_MIN_MAX_FILTER_DILATION);
    for (gint ki = 0; ki < kyres; ki++) {
        gint i = (do_flip ? row - ki + koffy : row + ki - koffy);
        /* The function documentation specifies border-extend behaviour. */
        i = CLAMP(i, 0, yres-1);
        for (gint kj = 0; kj < kxres; kj++) {
            gint j = (do_flip ? col - kj + koffx : col + kj - koffx);
            j = CLAMP(j, 0, xres-1);
            if (kdata[ki*kxres + kj] <= 0)
                continue;

            gint v = data[i*xres + j];
            m = MIN(m, v);
            M = MAX(M, v);
        }
    }

    if (filtertype == GWY_MIN_MAX_FILTER_MINIMUM || filtertype == GWY_MIN_MAX_FILTER_FROSION)
        return (M >= m ? m : v0);
    if (filtertype == GWY_MIN_MAX_FILTER_MAXIMUM || filtertype == GWY_MIN_MAX_FILTER_DILATION)
        return (M >= m ? M : v0);
    if (filtertype == GWY_MIN_MAX_FILTER_RANGE)
        return (M >= m ? M - m : 0);

    g_return_val_if_reached(0);
}

static void
test_nield_filter_minmax(GwyMinMaxFilterType filtertype)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint itr = 0; itr < n; itr++) {
        gint xres = g_test_rand_int_range(5, 12), yres = g_test_rand_int_range(5, 12);
        gint width = g_test_rand_int_range(1, xres), height = g_test_rand_int_range(1, yres);
        gint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        gint kxres = g_test_rand_int_range(1, width+1), kyres = g_test_rand_int_range(1, height+1);
        GwyNield *nield = gwy_nield_new(xres, yres);
        gint *d = gwy_nield_get_data(nield);
        for (gint i = 0; i < xres*yres; i++)
            d[i] = g_test_rand_int_range(-10, 30);

        GwyNield *kernel = gwy_nield_new(kxres, kyres);
        do {
            gint *m = gwy_nield_get_data(kernel);
            for (gint i = 0; i < kxres*kyres; i++) {
                gint v = g_test_rand_int_range(-1, 3);
                m[i] = MAX(v, m[i]);
            }
            gwy_nield_invalidate(kernel);
        } while (!gwy_nield_count(kernel));

        GwyNield *filtered = gwy_nield_copy(nield);
        gwy_nield_area_filter_min_max(filtered, kernel, filtertype, col, row, width, height);

        GwyNield *reference = gwy_nield_copy(nield);
        for (gint i = 0; i < height; i++) {
            for (gint j = 0; j < width; j++) {
                gint v = minmax_the_hard_way(nield, kernel, col + j, row + i, filtertype);
                gwy_nield_set_val(reference, col + j, row + i, v);
            }
        }

        nield_assert_equal(G_OBJECT(filtered), G_OBJECT(reference));

        g_assert_finalize_object(reference);
        g_assert_finalize_object(kernel);
        g_assert_finalize_object(filtered);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_filter_minmax_minimum(void)
{
    test_nield_filter_minmax(GWY_MIN_MAX_FILTER_MINIMUM);
}

void
test_nield_filter_minmax_maximum(void)
{
    test_nield_filter_minmax(GWY_MIN_MAX_FILTER_MAXIMUM);
}

void
test_nield_filter_minmax_range(void)
{
    test_nield_filter_minmax(GWY_MIN_MAX_FILTER_RANGE);
}

void
test_nield_filter_minmax_dilation(void)
{
    test_nield_filter_minmax(GWY_MIN_MAX_FILTER_DILATION);
}

void
test_nield_filter_minmax_frosion(void)
{
    test_nield_filter_minmax(GWY_MIN_MAX_FILTER_FROSION);
}

static void
test_nield_boundary_pixels(gboolean from_border)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint itr = 0; itr < n; itr++) {
        GwyNield *nield = make_random_nield(TRUE, 3);
        if (!g_test_rand_int_range(0, 20))
            gwy_nield_crop(nield, 0, 0, gwy_nield_get_xres(nield), 1);
        if (!g_test_rand_int_range(0, 20))
            gwy_nield_crop(nield, 0, 0, 1, gwy_nield_get_yres(nield));
        gint maxgno = gwy_nield_max(nield);
        maxgno = MAX(maxgno, 0);

        guint *bindex = g_new(guint, maxgno+2);
        gint *bpixels = gwy_nield_boundary_pixels(nield, bindex, from_border);

        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gint *grains = gwy_nield_get_data(nield);

        // Check that everything in the list is actually a boundary pixel.
        for (gint gno = 1; gno <= maxgno; gno++) {
            for (guint m = bindex[gno]; m < bindex[gno+1]; m++) {
                gint k = bpixels[m];
                g_assert_cmpint(grains[k], ==, gno);

                gint j = k % xres, i = k/xres;
                gboolean is_on_edge = FALSE;

                if (i && grains[k-xres] != gno)
                    is_on_edge = TRUE;
                if (j && grains[k-1] != gno)
                    is_on_edge = TRUE;
                if (j < xres-1 && grains[k+1] != gno)
                    is_on_edge = TRUE;
                if (i < yres-1 && grains[k+xres] != gno)
                    is_on_edge = TRUE;
                if (from_border && (!i || !j || i == yres-1 || j == xres-1))
                    is_on_edge = TRUE;

                g_assert_true(is_on_edge);
            }
        }

        // Check that all boundary pixels are in the list.
        for (gint i = 0; i < yres; i++) {
            for (gint j = 0; j < xres; j++) {
                gint k = i*xres + j;
                gint gno;
                if ((gno = grains[k]) <= 0)
                    continue;

                gboolean is_on_edge = FALSE;
                if (i && grains[k-xres] != gno)
                    is_on_edge = TRUE;
                if (j && grains[k-1] != gno)
                    is_on_edge = TRUE;
                if (j < xres-1 && grains[k+1] != gno)
                    is_on_edge = TRUE;
                if (i < yres-1 && grains[k+xres] != gno)
                    is_on_edge = TRUE;
                if (from_border && (!i || !j || i == yres-1 || j == xres-1))
                    is_on_edge = TRUE;

                if (is_on_edge) {
                    gboolean found_in_list = FALSE;
                    for (guint m = bindex[gno]; m < bindex[gno+1]; m++) {
                        if (bpixels[m] == k) {
                            found_in_list = TRUE;
                            break;
                        }
                    }
                    g_assert_true(found_in_list);
                }
            }
        }

        // Check that nothing is listed twice.
        for (gint gno = 1; gno <= maxgno; gno++) {
            for (guint m = bindex[gno]; m < bindex[gno+1]; m++) {
                gint k = bpixels[m];
                g_assert_cmpint(grains[k], ==, gno);
                grains[k] = 0;
            }
        }

        g_free(bpixels);
        g_free(bindex);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_boundary_pixels_border(void)
{
    test_nield_boundary_pixels(TRUE);
}

void
test_nield_boundary_pixels_noborder(void)
{
    test_nield_boundary_pixels(FALSE);
}

// Check that convex hull vertices are corners of some pixels with the corresponding grain number. If it fails the
// convex hull could be smaller. It should verify minimality toegether with the other two tests (but obviously it not
// verify it alone).
void
test_nield_convex_hull_tightness(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint itr = 0; itr < n; itr++) {
        GwyNield *nield = make_random_nield(TRUE, 3);
        gint maxgno = gwy_nield_max(nield);
        maxgno = MAX(maxgno, 0);

        guint *cindex = g_new(guint, maxgno+2);
        gint *chull = gwy_nield_convex_hulls(nield, cindex);

        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        const gint *grains = gwy_nield_get_data_const(nield);
        for (gint gno = 1; gno <= maxgno; gno++) {
            for (guint k = cindex[gno]; k < cindex[gno+1]; k++) {
                gint j = chull[2*k], i = chull[2*k + 1];
                gboolean is_grain_corner = FALSE;

                if (i && j && grains[(i-1)*xres + j-1] == gno)
                    is_grain_corner = TRUE;
                if (i && j < xres && grains[(i-1)*xres + j] == gno)
                    is_grain_corner = TRUE;
                if (i < yres && j && grains[i*xres + j-1] == gno)
                    is_grain_corner = TRUE;
                if (i < yres && j < xres && grains[i*xres + j] == gno)
                    is_grain_corner = TRUE;

                g_assert_true(is_grain_corner);
            }
        }

        g_free(chull);
        g_free(cindex);
        g_assert_finalize_object(nield);
    }
}

// Check that the convex hull is convex and goes anti-clockwise.
void
test_nield_convex_hull_convexity(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint itr = 0; itr < n; itr++) {
        GwyNield *nield = make_random_nield(TRUE, 3);
        gint maxgno = gwy_nield_max(nield);
        maxgno = MAX(maxgno, 0);

        guint *cindex = g_new(guint, maxgno+2);
        gint *chull = gwy_nield_convex_hulls(nield, cindex);

        for (gint gno = 1; gno <= maxgno; gno++) {
            guint kfrom = cindex[gno], len = cindex[gno+1] - kfrom;
            gint *chullblock = chull + 2*kfrom;
            for (guint k = 0; k < len; k++) {
                guint kprev = (k + len-1) % len, knext = (k + 1) % len;
                gint jprev = chullblock[2*kprev], iprev = chullblock[2*kprev + 1];
                gint j = chullblock[2*k], i = chullblock[2*k + 1];
                gint jnext = chullblock[2*knext], inext = chullblock[2*knext + 1];

                gint crossprod = (jprev - j)*(inext - i) - (jnext - j)*(iprev - i);
                g_assert_cmpint(crossprod, >, 0);
            }
        }

        g_free(chull);
        g_free(cindex);
        g_assert_finalize_object(nield);
    }
}

// Check that the convex hull contains all grain vertices.
void
test_nield_convex_hull_coverage(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint itr = 0; itr < n; itr++) {
        GwyNield *nield = make_random_nield(TRUE, 3);
        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gint maxgno = gwy_nield_max(nield);
        maxgno = MAX(maxgno, 0);

        guint *cindex = g_new(guint, maxgno+2);
        gint *chull = gwy_nield_convex_hulls(nield, cindex);

        // Convert to doubles to use gwy_math_is_in_polygon().
        gdouble *polygons = g_new(gdouble, 2*cindex[maxgno+1]);
        for (guint k = 0; k < 2*cindex[maxgno+1]; k++)
            polygons[k] = chull[k];
        g_free(chull);

        // Calculate some sort of midpoints to shrink grain vertices slighty towards them to get defined results for
        // vertices on edges (which there are usually many).
        GwyXY *midpoints = g_new0(GwyXY, maxgno+1);
        for (gint gno = 1; gno <= maxgno; gno++) {
            guint kfrom = cindex[gno], len = cindex[gno+1] - kfrom;
            if (!len)
                continue;

            gdouble *chullblock = polygons + 2*kfrom;
            gdouble x = 0.0, y = 0.0;
            for (guint k = 0; k < len; k++) {
                x += chullblock[2*k];
                y += chullblock[2*k + 1];
            }
            midpoints[gno].x = x/len;
            midpoints[gno].y = y/len;
        }

        const gint *grains = gwy_nield_get_data_const(nield);
        for (gint i = 0; i < yres; i++) {
            for (gint j = 0; j < xres; j++) {
                gint gno = grains[i*xres + j];
                if (gno <= 0)
                    continue;

                gdouble *poly = polygons + 2*cindex[gno];
                guint npoints = cindex[gno+1] - cindex[gno];
                for (guint cc = 0; cc < 4; cc++) {
                    gint ioff = cc/2, joff = cc % 2;
                    // Shrink vertex slightly towards grain centre.
                    gdouble x = (1.0 - 1e-9)*(j + joff - midpoints[gno].x) + midpoints[gno].x;
                    gdouble y = (1.0 - 1e-9)*(i + ioff - midpoints[gno].y) + midpoints[gno].y;
                    g_assert_true(gwy_math_is_in_polygon(x, y, poly, npoints));
                }
            }
        }

        g_free(midpoints);
        g_free(polygons);
        g_free(cindex);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_mark_range_inclusive(void)
{
    const gdouble data[2*3] = {
        0.0, 1.0,
        G_MAXDOUBLE, -G_MAXDOUBLE,
        1.0, 2.0,
    };
    const gchar expected_src[] =
        "11\n"
        "  \n"
        "1 ";

    GwyField *field = gwy_field_new(2, 3, 1.0, 1.0, FALSE);
    gwy_assign(gwy_field_get_data(field), data, 2*3);

    GwyNield *nield = gwy_nield_new(2, 3);
    gwy_nield_mark_range(nield, field, -G_MINDOUBLE, 1.0);
    GwyNield *expected = parse_nield(expected_src);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(expected));

    g_assert_finalize_object(expected);
    g_assert_finalize_object(nield);
    g_assert_finalize_object(field);
}

void
test_nield_mark_range_exclusive(void)
{
    const gdouble data[2*3] = {
        0.0, 1.0,
        G_MAXDOUBLE, -G_MAXDOUBLE,
        1.0, 0.5,
    };
    const gchar expected_src[] =
        " 1\n"
        "11\n"
        "1 ";

    GwyField *field = gwy_field_new(2, 3, 1.0, 1.0, FALSE);
    gwy_assign(gwy_field_get_data(field), data, 2*3);

    GwyNield *nield = gwy_nield_new(2, 3);
    gwy_nield_mark_range(nield, field, 1.0, -G_MINDOUBLE);
    GwyNield *expected = parse_nield(expected_src);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(expected));

    g_assert_finalize_object(expected);
    g_assert_finalize_object(nield);
    g_assert_finalize_object(field);
}

void
test_nield_mark_by_threshold_above(void)
{
    const gdouble data[2*4] = {
        -1.0, 1.0,
        G_MAXDOUBLE, -G_MAXDOUBLE,
        -G_MINDOUBLE, G_MINDOUBLE,
        0.0, 2.0,
    };
    const gchar expected_src[] =
        " 1\n"
        "1 \n"
        " 1\n"
        "11";

    GwyField *field = gwy_field_new(2, 4, 1.0, 1.0, FALSE);
    gwy_assign(gwy_field_get_data(field), data, 2*4);

    GwyNield *nield = gwy_nield_new(2, 4);
    gwy_nield_mark_by_threshold(nield, field, 0.0, TRUE);
    GwyNield *expected = parse_nield(expected_src);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(expected));

    g_assert_finalize_object(expected);
    g_assert_finalize_object(nield);
    g_assert_finalize_object(field);
}

void
test_nield_mark_by_threshold_below(void)
{
    const gdouble data[2*4] = {
        -1.0, 1.0,
        G_MAXDOUBLE, -G_MAXDOUBLE,
        -G_MINDOUBLE, G_MINDOUBLE,
        0.0, -2.0,
    };
    const gchar expected_src[] =
        "1 \n"
        " 1\n"
        "1 \n"
        "11";

    GwyField *field = gwy_field_new(2, 4, 1.0, 1.0, FALSE);
    gwy_assign(gwy_field_get_data(field), data, 2*4);

    GwyNield *nield = gwy_nield_new(2, 4);
    gwy_nield_mark_by_threshold(nield, field, 0.0, FALSE);
    GwyNield *expected = parse_nield(expected_src);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(expected));

    g_assert_finalize_object(expected);
    g_assert_finalize_object(nield);
    g_assert_finalize_object(field);
}

static void
test_nield_merge(GwyMergeType operation, const gchar *expected_src)
{
    const gchar src_nield[] =
        "    \n"
        "    \n"
        "1234\n"
        "5678";
    const gchar src_operand[] =
        " 1 5\n"
        "2 6 \n"
        " 3 7\n"
        "4 8 ";

    GwyNield *nield = parse_nield(src_nield);
    GwyNield *operand = parse_nield(src_operand);
    GwyNield *expected = parse_nield(expected_src);
    gwy_nield_merge(nield, operand, operation);
    nield_assert_equal(G_OBJECT(nield), G_OBJECT(expected));

    g_assert_finalize_object(expected);
    g_assert_finalize_object(operand);
    g_assert_finalize_object(nield);
}

void
test_nield_merge_clear(void)
{
    const gchar expected_src[] =
        "    \n"
        "    \n"
        "    \n"
        "    ";
    test_nield_merge(GWY_MERGE_CLEAR, expected_src);
}

void
test_nield_merge_fill(void)
{
    const gchar expected_src[] =
        "1111\n"
        "1111\n"
        "1111\n"
        "1111";
    test_nield_merge(GWY_MERGE_FILL, expected_src);
}

void
test_nield_merge_max(void)
{
    const gchar expected_src[] =
        " 1 5\n"
        "2 6 \n"
        "1337\n"
        "5688";
    test_nield_merge(GWY_MERGE_MAX, expected_src);
}

void
test_nield_merge_min(void)
{
    const gchar expected_src[] =
        "    \n"
        "    \n"
        " 2 4\n"
        "4 7 ";
    test_nield_merge(GWY_MERGE_MIN, expected_src);
}

void
test_nield_merge_keep(void)
{
    const gchar expected_src[] =
        "    \n"
        "    \n"
        "1234\n"
        "5678";
    test_nield_merge(GWY_MERGE_KEEP, expected_src);
}

void
test_nield_merge_copy(void)
{
    const gchar expected_src[] =
        " 1 5\n"
        "2 6 \n"
        " 3 7\n"
        "4 8 ";
    test_nield_merge(GWY_MERGE_COPY, expected_src);
}

void
test_nield_merge_subtract(void)
{
    const gchar expected_src[] =
        "    \n"
        "    \n"
        "1 3 \n"
        " 6 8";
    test_nield_merge(GWY_MERGE_SUBTRACT, expected_src);
}

void
test_nield_merge_revsubtract(void)
{
    const gchar expected_src[] =
        " 1 5\n"
        "2 6 \n"
        "    \n"
        "    ";
    test_nield_merge(GWY_MERGE_REVSUBTRACT, expected_src);
}

void
test_nield_merge_xor(void)
{
    const gchar expected_src[] =
        " 1 5\n"
        "2 6 \n"
        "1 3 \n"
        " 6 8";
    test_nield_merge(GWY_MERGE_XOR, expected_src);
}

void
test_nield_merge_equal(void)
{
    const gchar expected_src[] =
        "1 1 \n"
        " 1 1\n"
        " 3 7\n"
        "5 8 ";
    test_nield_merge(GWY_MERGE_EQUAL, expected_src);
}

void
test_nield_merge_outside(void)
{
    const gchar expected_src[] =
        "1 1 \n"
        " 1 1\n"
        "    \n"
        "    ";
    test_nield_merge(GWY_MERGE_OUTSIDE, expected_src);
}

void
test_nield_merge_not(void)
{
    const gchar expected_src[] =
        "1111\n"
        "1111\n"
        "    \n"
        "    ";
    test_nield_merge(GWY_MERGE_NOT, expected_src);
}

void
test_nield_merge_ncopy(void)
{
    const gchar expected_src[] =
        "1 1 \n"
        " 1 1\n"
        "1 1 \n"
        " 1 1";
    test_nield_merge(GWY_MERGE_NCOPY, expected_src);
}

void
test_nield_merge_nor(void)
{
    const gchar expected_src[] =
        "1 1 \n"
        " 1 1\n"
        "    \n"
        "    ";
    test_nield_merge(GWY_MERGE_NOR, expected_src);
}

void
test_nield_merge_nand(void)
{
    const gchar expected_src[] =
        "1111\n"
        "1111\n"
        "1 1 \n"
        " 1 1";
    test_nield_merge(GWY_MERGE_NAND, expected_src);
}

void
test_nield_merge_implies(void)
{
    const gchar expected_src[] =
        "1115\n"
        "2161\n"
        " 3 7\n"
        "4 8 ";
    test_nield_merge(GWY_MERGE_IMPLIES, expected_src);
}

void
test_nield_merge_converse(void)
{
    const gchar expected_src[] =
        "1 1 \n"
        " 1 1\n"
        "1234\n"
        "5678";
    test_nield_merge(GWY_MERGE_CONVERSE, expected_src);
}

typedef double (*IsolatedEvalFunction)(GwyField *field, GwyNield *mask);

static void
add_noise_to_field(GwyField *field)
{
    gint n = gwy_field_get_xres(field)*gwy_field_get_yres(field);
    gdouble *d = gwy_field_get_data(field);
    gdouble s = exp(2*g_test_rand_double() - 1);
    for (gint k = 0; k < n; k++)
        d[k] = s*(0.1*d[k] + g_test_rand_double());
}

static gdouble
make_ref_angle_compatible(gdouble angle, gdouble ref_angle)
{
    if (angle - ref_angle > 0.5*G_PI)
        ref_angle += G_PI;
    else if (ref_angle - angle > 0.5*G_PI)
        ref_angle -= G_PI;
    return ref_angle;
}

static void
test_nield_grain_quantity(GwyGrainQuantity quantity,
                          IsolatedEvalFunction eval_func,
                          gdouble empty_value)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint i = 0; i < n; i++) {
        GwyNield *nield = NULL;

        do {
            g_clear_object(&nield);
            nield = make_random_nield(TRUE, 3);
        } while (gwy_nield_max(nield) <= 0);

        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gdouble dx = 0.5 + g_test_rand_double(), dy = 0.5 + g_test_rand_double();
        GwyField *field = gwy_field_new(xres, yres, dx*xres, dy*yres, TRUE);

        add_noise_to_field(field);
        gint qngrains, sngrains;
        gdouble *values = gwy_nield_grain_quantity(nield, field, quantity, &qngrains);
        gint *sizes = gwy_nield_sizes(nield, &sngrains);
        g_assert_cmpint(qngrains, ==, sngrains);

        GwyNield *isolated = gwy_nield_new_alike(nield);
        for (gint gno = 1; gno <= sngrains; gno++) {
            gwy_nield_copy_data(nield, isolated);
            gboolean has_grain = gwy_nield_isolate(isolated, gno, NULL);
            if (!sizes[gno]) {
                g_assert_false(has_grain);
                g_assert_cmpfloat(values[gno], ==, empty_value);
                continue;
            }
            g_assert_true(has_grain);
            gdouble field_value = eval_func(field, isolated);
            // The possible rounding errors for individual quantities can vary a lot. Be lenient. However, the empty
            // value may be almost infinite, so we have to be a bit careful.
            gdouble eps = 100*DBL_EPSILON*(1.0 + fabs(field_value))*xres*yres;
            if (quantity == GWY_GRAIN_SLOPE_PHI)
                field_value = make_ref_angle_compatible(values[gno], field_value);
            else if (quantity == GWY_GRAIN_CONVEX_HULL_AREA) {
                // Our reference integration method is dumb.
                eps = 0.02*dx*dy*(xres + yres);
            }
            //g_printerr("[%d] %.16g vs %.16g (delta=%g, eps=%g)\n",
            //           gno, values[gno], field_value, values[gno] - field_value, eps);
            g_assert_cmpfloat_with_epsilon(values[gno], field_value, eps);
        }

        g_free(values);
        g_free(sizes);
        g_assert_finalize_object(isolated);
        g_assert_finalize_object(field);
        g_assert_finalize_object(nield);
    }
}

static void
fill_field_with_a_coordinate_power(GwyField *field, gint powerx, gint powery,
                                   gboolean pixel_coords, GwyNield *centre_at)
{
    if (!powerx && !powery) {
        gwy_field_fill(field, 1.0);
        return;
    }

    gint xres = gwy_field_get_xres(field), yres = gwy_field_get_yres(field);
    gdouble xc = 0.0, yc = 0.0;
    if (centre_at) {
        fill_field_with_a_coordinate_power(field, 1, 0, pixel_coords, FALSE);
        xc = gwy_field_area_mean(field, centre_at, GWY_MASK_INCLUDE, 0, 0, xres, yres);
        fill_field_with_a_coordinate_power(field, 0, 1, pixel_coords, FALSE);
        yc = gwy_field_area_mean(field, centre_at, GWY_MASK_INCLUDE, 0, 0, xres, yres);
    }

    gdouble dx = (pixel_coords ? 1.0 : gwy_field_get_dx(field));
    gdouble dy = (pixel_coords ? 1.0 : gwy_field_get_dy(field));
    gdouble *d = gwy_field_get_data(field);
    for (gint i = 0; i < yres; i++) {
        gdouble y = dy*(i + 0.5) - yc;
        gdouble py = gwy_powi(y, powery);
        for (gint j = 0; j < xres; j++) {
            gdouble x = dx*(j + 0.5) - xc;
            gdouble px = gwy_powi(x, powerx);
            d[i*xres + j] = px*py;
        }
    }
}

static gdouble
eval_pixel_area(G_GNUC_UNUSED GwyField *field, GwyNield *nield)
{
    return gwy_nield_count(nield);
}

void
test_nield_grain_quantity_pixel_area(void)
{
    test_nield_grain_quantity(GWY_GRAIN_PIXEL_AREA, eval_pixel_area, 0.0);
}

static gdouble
eval_projected_area(GwyField *field, GwyNield *nield)
{
    gdouble dA = gwy_field_get_dx(field)*gwy_field_get_dy(field);
    return dA*gwy_nield_count(nield);
}

void
test_nield_grain_quantity_projected_area(void)
{
    test_nield_grain_quantity(GWY_GRAIN_PROJECTED_AREA, eval_projected_area, 0.0);
}

static gdouble
eval_equiv_disc_radius(GwyField *field, GwyNield *nield)
{
    gdouble dA = gwy_field_get_dx(field)*gwy_field_get_dy(field);
    return sqrt(dA*gwy_nield_count(nield)/G_PI);
}

void
test_nield_grain_quantity_equiv_disc_radius(void)
{
    test_nield_grain_quantity(GWY_GRAIN_EQUIV_DISC_RADIUS, eval_equiv_disc_radius, 0.0);
}

static gdouble
eval_maximum(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    return gwy_field_area_max(field, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres);
}

void
test_nield_grain_quantity_maximum(void)
{
    test_nield_grain_quantity(GWY_GRAIN_MAXIMUM, eval_maximum, -G_MAXDOUBLE);
}

static gdouble
eval_minimum(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    return gwy_field_area_min(field, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres);
}

void
test_nield_grain_quantity_minimum(void)
{
    test_nield_grain_quantity(GWY_GRAIN_MINIMUM, eval_minimum, G_MAXDOUBLE);
}

static gdouble
eval_mean(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    return gwy_field_area_mean(field, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres);
}

void
test_nield_grain_quantity_mean(void)
{
    test_nield_grain_quantity(GWY_GRAIN_MEAN, eval_mean, 0.0);
}

static gdouble
eval_median(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    return gwy_field_area_median(field, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres);
}

void
test_nield_grain_quantity_median(void)
{
    test_nield_grain_quantity(GWY_GRAIN_MEDIAN, eval_median, 0.0);
}

static gdouble
eval_rms(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    return gwy_field_area_rms(field, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres);
}

void
test_nield_grain_quantity_rms(void)
{
    test_nield_grain_quantity(GWY_GRAIN_RMS, eval_rms, 0.0);
}

static gdouble
eval_surface_area(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    return gwy_field_area_surface_area(field, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres);
}

void
test_nield_grain_quantity_surface_area(void)
{
    test_nield_grain_quantity(GWY_GRAIN_SURFACE_AREA, eval_surface_area, 0.0);
}

static gdouble
eval_center_x(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    GwyField *cfield = gwy_field_new_alike(field, FALSE);
    fill_field_with_a_coordinate_power(cfield, 1, 0, FALSE, NULL);
    gdouble retval = gwy_field_area_mean(cfield, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres);
    g_object_unref(cfield);
    return retval;
}

void
test_nield_grain_quantity_center_x(void)
{
    test_nield_grain_quantity(GWY_GRAIN_CENTER_X, eval_center_x, 0.0);
}

static gdouble
eval_center_y(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    GwyField *cfield = gwy_field_new_alike(field, FALSE);
    fill_field_with_a_coordinate_power(cfield, 0, 1, FALSE, NULL);
    gdouble retval = gwy_field_area_mean(cfield, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres);
    g_object_unref(cfield);
    return retval;
}

void
test_nield_grain_quantity_center_y(void)
{
    test_nield_grain_quantity(GWY_GRAIN_CENTER_Y, eval_center_y, 0.0);
}

static gdouble
eval_slope_theta(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    gdouble bx, by;
    gwy_field_area_fit_plane(field, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres, NULL, &bx, &by);
    bx /= gwy_field_get_dx(field);
    by /= gwy_field_get_dy(field);
    return atan(hypot(bx, by));
}

void
test_nield_grain_quantity_slope_theta(void)
{
    test_nield_grain_quantity(GWY_GRAIN_SLOPE_THETA, eval_slope_theta, 0.0);
}

static gdouble
eval_slope_phi(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    gdouble bx, by;
    gwy_field_area_fit_plane(field, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres, NULL, &bx, &by);
    bx /= gwy_field_get_dx(field);
    by /= gwy_field_get_dy(field);
    // XXX: The angle sign conventions are a mess.
    return atan2(by, -bx);
}

void
test_nield_grain_quantity_slope_phi(void)
{
    test_nield_grain_quantity(GWY_GRAIN_SLOPE_PHI, eval_slope_phi, 0.0);
}

static gdouble
eval_volume_0(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    return gwy_field_area_volume(field, nield, GWY_MASK_INCLUDE, GWY_FIELD_VOLUME_DEFAULT, 0, 0, xres, yres);
}

void
test_nield_grain_quantity_volume_0(void)
{
    test_nield_grain_quantity(GWY_GRAIN_VOLUME_0, eval_volume_0, 0.0);
}

static gdouble
eval_volume_min(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    gdouble min = gwy_field_area_min(field, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres);
    GwyField *rebased = gwy_field_copy(field);
    gwy_field_add(rebased, -min);
    gdouble vol = gwy_field_area_volume(rebased, nield, GWY_MASK_INCLUDE, GWY_FIELD_VOLUME_DEFAULT, 0, 0, xres, yres);
    g_assert_finalize_object(rebased);
    return vol;
}

void
test_nield_grain_quantity_volume_min(void)
{
    test_nield_grain_quantity(GWY_GRAIN_VOLUME_MIN, eval_volume_min, 0.0);
}

static gdouble
eval_volume_laplace(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    GwyField *rebased = gwy_field_copy(field);
    // XXX: The exact result depends on the quality/precision factor. We use the same value as in grain-quantities,
    // which is not great from the standpoint of the test being independent. The other option would be to increase the
    // tolerance by a few orders of magnitude. In any case, we must use the same volume integration weight set.
    gwy_field_laplace_solve(rebased, nield, GWY_LAPLACE_MASKED, 0.4);
    gwy_field_subtract_fields(rebased, field, rebased);
    gdouble vol = gwy_field_area_volume(rebased, nield, GWY_MASK_INCLUDE, GWY_FIELD_VOLUME_DEFAULT, 0, 0, xres, yres);
    g_assert_finalize_object(rebased);
    return vol;
}

void
test_nield_grain_quantity_volume_laplace(void)
{
    test_nield_grain_quantity(GWY_GRAIN_VOLUME_LAPLACE, eval_volume_laplace, 0.0);
}

static gdouble
eval_half_height_area(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    gdouble dA = gwy_field_get_dx(field)*gwy_field_get_dy(field);
    gdouble min, max;
    gwy_field_area_min_max(field, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres, &min, &max);
    // The quantity specification says strictly above half-height, which is nothing in a completely flat grain.
    if (min >= max)
        return 0.0;

    gdouble mid = 0.5*(min + max);
    gint nabove;
    gwy_field_area_count_in_range(field, nield, GWY_MASK_INCLUDE, 0, 0, xres, yres, G_MAXDOUBLE, mid, NULL, &nabove);
    return dA*nabove;
}

void
test_nield_grain_quantity_half_height_area(void)
{
    test_nield_grain_quantity(GWY_GRAIN_HALF_HEIGHT_AREA, eval_half_height_area, 0.0);
}

static gdouble
eval_boundary_minimum(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    GwyNield *edge = gwy_nield_copy(nield);
    gwy_nield_shrink(edge, 1.0, GWY_DISTANCE_TRANSFORM_CONN4, TRUE, TRUE);
    gwy_nield_invert(edge);
    gwy_nield_intersect(edge, nield);
    gdouble min = gwy_field_area_min(field, edge, GWY_MASK_INCLUDE, 0, 0, xres, yres);
    g_assert_finalize_object(edge);
    return min;
}

void
test_nield_grain_quantity_boundary_minimum(void)
{
    test_nield_grain_quantity(GWY_GRAIN_BOUNDARY_MINIMUM, eval_boundary_minimum, G_MAXDOUBLE);
}

static gdouble
eval_boundary_maximum(GwyField *field, GwyNield *nield)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    GwyNield *edge = gwy_nield_copy(nield);
    gwy_nield_shrink(edge, 1.0, GWY_DISTANCE_TRANSFORM_CONN4, TRUE, TRUE);
    gwy_nield_invert(edge);
    gwy_nield_intersect(edge, nield);
    gdouble max = gwy_field_area_max(field, edge, GWY_MASK_INCLUDE, 0, 0, xres, yres);
    g_assert_finalize_object(edge);
    return max;
}

void
test_nield_grain_quantity_boundary_maximum(void)
{
    test_nield_grain_quantity(GWY_GRAIN_BOUNDARY_MAXIMUM, eval_boundary_maximum, -G_MAXDOUBLE);
}

static gboolean
all_points_in_circle(gdouble cx, gdouble cy, gdouble r2,
                     const gdouble *xy, guint n)
{
    for (guint i = 0; i < n; i++) {
        gdouble x = xy[2*i], y = xy[2*i + 1];
        gdouble d2 = (x - cx)*(x - cx) + (y - cy)*(y - cy);
        if (d2 > r2)
            return FALSE;
    }
    return TRUE;
}

static void
find_circumcircle(GwyField *field, GwyNield *nield,
                  gdouble *px, gdouble *py, gdouble *pr)
{
    // We trust gwy_nield_convex_hulls() here as already tested, and just find the circumcircle by brute force.
    g_assert_cmpint(gwy_nield_max(nield), ==, 1);

    gint cindex[3];
    gint *chull = gwy_nield_convex_hulls(nield, cindex);
    guint npoints = cindex[2] - cindex[1];
    gdouble *polygon = g_new(gdouble, 2*npoints);

    gdouble dx = gwy_field_get_dx(field), dy = gwy_field_get_dy(field);
    for (gint i = 0; i < npoints; i++) {
        polygon[2*i] = dx*chull[2*(cindex[1] + i)];
        polygon[2*i + 1] = dy*chull[2*(cindex[1] + i) + 1];
    }
    g_free(chull);

    gdouble mr2 = G_MAXDOUBLE, mx = 0.0, my = 0.0;
    gdouble eps = 1e-9*(dx + dy);

    for (guint i = 0; i < npoints-1; i++) {
        gdouble ax = polygon[2*i], ay = polygon[2*i + 1];
        for (guint j = i+1; j < npoints; j++) {
            gdouble bx = polygon[2*j], by = polygon[2*j + 1];

            // Make a circle with the two convex hull points as the diameter. The farthest two would suffice, but
            // whatever.
            gdouble r2 = 0.25*((bx - ax)*(bx - ax) + (by - ay)*(by - ay));
            gdouble Sx = 0.5*(ax + bx), Sy = 0.5*(ay + by);

            if (all_points_in_circle(Sx, Sy, r2 + eps, polygon, npoints)) {
                mr2 = r2;
                mx = Sx;
                my = Sy;
            }
        }
    }
    if (mr2 >= G_MAXDOUBLE) {
        for (guint i = 0; i < npoints-2; i++) {
            gdouble ax = polygon[2*i], ay = polygon[2*i + 1];
            for (guint j = i+1; j < npoints-1; j++) {
                gdouble bx = polygon[2*j], by = polygon[2*j + 1];
                gdouble bx_a = bx - ax, by_a = by - ay;
                for (guint k = j+1; k < npoints; k++) {
                    gdouble cx = polygon[2*k], cy = polygon[2*k + 1];
                    // Make a circle circumscribed to the three convex hull points.
                    gdouble cx_a = cx - ax, cy_a = cy - ay;
                    gdouble b2_a = bx_a*bx_a + by_a*by_a;
                    gdouble c2_a = cx_a*cx_a + cy_a*cy_a;

                    gdouble D = 2*(bx_a*cy_a - cx_a*by_a);
                    gdouble Sx = (cy_a*b2_a - by_a*c2_a)/D;
                    gdouble Sy = (bx_a*c2_a - cx_a*b2_a)/D;

                    gdouble r2 = Sx*Sx + Sy*Sy;
                    // Do not bother trying too large circles.
                    if (r2 >= mr2)
                        continue;

                    Sx += ax;
                    Sy += ay;
                    if (all_points_in_circle(Sx, Sy, r2 + eps, polygon, npoints)) {
                        mr2 = r2;
                        mx = Sx;
                        my = Sy;
                    }
                }
            }
        }
    }
    g_assert_cmpfloat(mr2, <, G_MAXDOUBLE);

    g_free(polygon);

    *pr = sqrt(mr2);
    *px = mx + gwy_field_get_xoffset(field);
    *py = my + gwy_field_get_yoffset(field);
}

static gdouble
eval_circumcircle_r(GwyField *field, GwyNield *nield)
{
    gdouble x, y, r;
    find_circumcircle(field, nield, &x, &y, &r);
    return r;
}

void
test_nield_grain_quantity_circumcircle_r(void)
{
    test_nield_grain_quantity(GWY_GRAIN_CIRCUMCIRCLE_R, eval_circumcircle_r, 0.0);
}

static gdouble
eval_circumcircle_x(GwyField *field, GwyNield *nield)
{
    gdouble x, y, r;
    find_circumcircle(field, nield, &x, &y, &r);
    return x;
}

void
test_nield_grain_quantity_circumcircle_x(void)
{
    test_nield_grain_quantity(GWY_GRAIN_CIRCUMCIRCLE_X, eval_circumcircle_x, 0.0);
}

static gdouble
eval_circumcircle_y(GwyField *field, GwyNield *nield)
{
    gdouble x, y, r;
    find_circumcircle(field, nield, &x, &y, &r);
    return y;
}

void
test_nield_grain_quantity_circumcircle_y(void)
{
    test_nield_grain_quantity(GWY_GRAIN_CIRCUMCIRCLE_Y, eval_circumcircle_y, 0.0);
}

static gdouble
eval_convex_hull_area(GwyField *field, GwyNield *nield)
{
    // This is still rough, with the maximum error of order of 1/upscale.
    enum { upscale = 25 };

    gdouble dx = gwy_field_get_dx(field), dy = gwy_field_get_dy(field);
    // We trust gwy_nield_convex_hulls() here as already tested, and just check the area.
    g_assert_cmpint(gwy_nield_max(nield), ==, 1);

    gint *bboxes = gwy_nield_bounding_boxes(nield, NULL);
    gint col = bboxes[4], row = bboxes[5], width = bboxes[6], height = bboxes[7];
    g_free(bboxes);

    gint cindex[3];
    gint *chull = gwy_nield_convex_hulls(nield, cindex);
    guint npoints = cindex[2] - cindex[1];
    gdouble *polygon = g_new(gdouble, 2*npoints);
    for (gint i = 0; i < npoints; i++) {
        polygon[2*i] = chull[2*(cindex[1] + i)] - col;
        polygon[2*i + 1] = chull[2*(cindex[1] + i) + 1] - row;
    }
    g_free(chull);

    gint s = 0;
    for (gint i = 0; i < height*upscale; i++) {
        gdouble y = (i + 0.5)/upscale;
        for (gint j = 0; j < width*upscale; j++) {
            gdouble x = (j + 0.5)/upscale;
            if (gwy_math_is_in_polygon(x, y, polygon, npoints))
                s++;
        }
    }

    g_free(polygon);

    return dx*dy*s/(upscale*upscale);
}

void
test_nield_grain_quantity_convex_hull_area(void)
{
    test_nield_grain_quantity(GWY_GRAIN_CONVEX_HULL_AREA, eval_convex_hull_area, 0.0);
}

static gdouble
grain_projection_length(GwyField *field, GwyNield *nield,
                        const gint *bpixels, guint npixels,
                        gdouble phi)
{
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    gdouble dx = gwy_field_get_dx(field), dy = gwy_field_get_dy(field);

    gdouble c = cos(phi), s = sin(phi);
    gdouble lower = G_MAXDOUBLE, upper = -G_MAXDOUBLE;
    if (bpixels) {
        for (guint p = 0; p < npixels; p++) {
            gint i = bpixels[p]/xres, j = bpixels[p] % xres;
            for (guint cc = 0; cc < 4; cc++) {
                gint ioff = cc/2, joff = cc % 2;
                gdouble x = dx*(j + joff), y = dy*(i + ioff);
                gdouble proj = x*c - y*s;
                lower = fmin(lower, proj);
                upper = fmax(upper, proj);
            }
        }
    }
    else {
        const gint *m = gwy_nield_get_data_const(nield);
        for (gint i = 0; i < yres; i++) {
            for (gint j = 0; j < xres; j++) {
                if (m[i*xres + j] <= 0)
                    continue;

                for (guint cc = 0; cc < 4; cc++) {
                    gint ioff = cc/2, joff = cc % 2;
                    gdouble x = dx*(j + joff), y = dy*(i + ioff);
                    gdouble proj = x*c - y*s;
                    lower = fmin(lower, proj);
                    upper = fmax(upper, proj);
                }
            }
        }
    }
    return upper - lower;
}

static gdouble
eval_minimum_project_size(GwyField *field, GwyNield *nield)
{
    gint maxgno = gwy_nield_max(nield);
    g_assert_cmpint(maxgno, ==, 1);
    gint bindex[3];
    gint *bpixels = gwy_nield_boundary_pixels(nield, bindex, TRUE);

    gdouble phimin = 0.0, phimax = G_PI;
    gdouble phibest = 0.5*(phimin + phimax);
    gdouble best = G_MAXDOUBLE;
    gint ndiv = 40000;
    // Brute-force scan for the shortest grain projection.
    // The minima are corner-like, so use very a fine step (it can still fail with bad luck, albeit rarely).
    while (phimax - phimin > 1e-13) {
        for (gint k = 0; k < ndiv; k++) {
            gdouble phi = k*(phimax - phimin)/(ndiv - 1) + phimin;
            gdouble bound = grain_projection_length(field, nield, bpixels + bindex[1], bindex[2] - bindex[1], phi);
            if (bound < best) {
                phibest = phi;
                best = bound;
            }
        }
        gdouble phirange = (phimax - phimin)/ndiv;
        phimin = phibest - phirange;
        phimax = phibest + phirange;
        // After a thorough initial scan, just zoom in.
        ndiv = 10;
    }
    g_free(bpixels);

    return best;
}

void
test_nield_grain_quantity_minimum_project_size(void)
{
    test_nield_grain_quantity(GWY_GRAIN_MINIMUM_PROJECT_SIZE, eval_minimum_project_size, 0.0);
}

static gdouble
eval_maximum_project_size(GwyField *field, GwyNield *nield)
{
    gint maxgno = gwy_nield_max(nield);
    g_assert_cmpint(maxgno, ==, 1);
    gint bindex[3];
    gint *bpixels = gwy_nield_boundary_pixels(nield, bindex, TRUE);

    gdouble phimin = 0.0, phimax = G_PI;
    gdouble phibest = 0.5*(phimin + phimax);
    gdouble best = -G_MAXDOUBLE;
    gint ndiv = 2000;
    // Brute-force scan for the longest grain projection.
    while (phimax - phimin > 1e-13) {
        for (gint k = 0; k < ndiv; k++) {
            gdouble phi = k*(phimax - phimin)/ndiv + phimin;
            gdouble bound = grain_projection_length(field, nield, bpixels + bindex[1], bindex[2] - bindex[1], phi);
            if (bound > best) {
                phibest = phi;
                best = bound;
            }
        }
        gdouble phirange = (phimax - phimin)/ndiv;
        phimin = phibest - phirange;
        phimax = phibest + phirange;
        // After a thorough initial scan, just zoom in.
        ndiv = 10;
    }
    g_free(bpixels);

    return best;
}

void
test_nield_grain_quantity_maximum_project_size(void)
{
    test_nield_grain_quantity(GWY_GRAIN_MAXIMUM_PROJECT_SIZE, eval_maximum_project_size, 0.0);
}

static void
test_nield_grain_quantity_project_angle(GwyGrainQuantity size_quantity,
                                        GwyGrainQuantity angle_quantity)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint i = 0; i < n; i++) {
        GwyNield *nield = NULL;

        do {
            g_clear_object(&nield);
            nield = make_random_nield(TRUE, 3);
        } while (gwy_nield_max(nield) <= 0);

        gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
        gdouble dx = 0.5 + g_test_rand_double(), dy = 0.5 + g_test_rand_double();
        GwyField *field = gwy_field_new(xres, yres, dx*xres, dy*yres, TRUE);

        add_noise_to_field(field);
        gint qngrains, sngrains;
        gdouble *lengths = gwy_nield_grain_quantity(nield, field, size_quantity, &qngrains);
        gdouble *angles = gwy_nield_grain_quantity(nield, field, angle_quantity, NULL);
        gint *sizes = gwy_nield_sizes(nield, &sngrains);
        g_assert_cmpint(qngrains, ==, sngrains);

        GwyNield *isolated = gwy_nield_new_alike(nield);
        for (gint gno = 1; gno <= sngrains; gno++) {
            gwy_nield_copy_data(nield, isolated);
            gboolean has_grain = gwy_nield_isolate(isolated, gno, NULL);
            if (!sizes[gno]) {
                g_assert_false(has_grain);
                g_assert_cmpfloat(angles[gno], ==, 0.0);
                continue;
            }
            g_assert_true(has_grain);

            // Here we assume that the size is correct because it has an independent test. Check that the projection
            // to the reported angle is equal to the reported size, i.e. the angle is one of the possible angles in
            // the ambiguous cases.
            gdouble size = grain_projection_length(field, isolated, NULL, 0, angles[gno]);
            gdouble eps = 100*DBL_EPSILON*(1.0 + lengths[gno])*xres*yres;
            g_assert_cmpfloat_with_epsilon(size, lengths[gno], eps);
        }

        g_free(lengths);
        g_free(angles);
        g_free(sizes);
        g_assert_finalize_object(isolated);
        g_assert_finalize_object(field);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_grain_quantity_maximum_project_angle(void)
{
    test_nield_grain_quantity_project_angle(GWY_GRAIN_MAXIMUM_PROJECT_SIZE, GWY_GRAIN_MAXIMUM_PROJECT_ANGLE);
}

void
test_nield_grain_quantity_minimum_project_angle(void)
{
    test_nield_grain_quantity_project_angle(GWY_GRAIN_MINIMUM_PROJECT_SIZE, GWY_GRAIN_MINIMUM_PROJECT_ANGLE);
}

void
test_nield_grain_quantity_equiv_ellipse(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint itr = 0; itr < n; itr++) {
        // Make always one big ellipse for comparison with smooth ellipse values.
        gint xres = g_test_rand_int_range(16, 50), yres = g_test_rand_int_range(16, 50);
        gint width = g_test_rand_int_range(11, xres-4), height = g_test_rand_int_range(11, yres-4);
        gdouble dx = 0.5 + g_test_rand_double(), dy = 0.5 + g_test_rand_double();
        gdouble m = MIN(width*dx, height*dy);
        gdouble a = g_test_rand_double_range(0.3*m, 0.5*m);
        gdouble b = g_test_rand_double_range(0.3*m, 0.5*m);
        gdouble xc = dx*(0.5*(xres - 1)) + (2.0*fmax(a, b) - m)*g_test_rand_double_range(-1.0, 1.0);
        gdouble yc = dy*(0.5*(yres - 1)) + (2.0*fmax(a, b) - m)*g_test_rand_double_range(-1.0, 1.0);
        gdouble phi = g_test_rand_double_range(-0.5*G_PI, 0.5*G_PI);

        GwyNield *nield = gwy_nield_new(xres, yres);
        GwyField *field = gwy_field_new(xres, yres, dx*xres, dy*yres, FALSE);
        for (gint i = 0; i < yres; i++) {
            gdouble y = dy*(i + 0.5) - yc;
            for (gint j = 0; j < xres; j++) {
                gdouble x = dx*(j + 0.5) - xc;
                // −φ ∈ (0, π/2) means major semi-axis in the first quadrant (minus is there because we the angle
                // is counterclocwise visually, not in our silly coordinates).
                // We must calculate in real coordinates, otherwise the ellipse is squished, changing the parameters!
                gdouble u = x*cos(phi) - y*sin(phi);
                gdouble v = x*sin(phi) + y*cos(phi);
                if ((u*u)/(a*a) + (v*v)/(b*b) <= 1.0)
                    gwy_nield_set_val(nield, j, i, 1);
            }
        }

        gint ngrains;
        if (a < b) {
            GWY_SWAP(gdouble, a, b);
            phi = gwy_canonicalize_angle(phi + 0.5*G_PI, FALSE, FALSE);
        }

        gdouble *majors = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_EQUIV_ELLIPSE_MAJOR, &ngrains);
        g_assert_cmpint(ngrains, ==, 1);
        gdouble atol = 0.25*(1.0 + 5.0/sqrt(gwy_nield_count(nield)));
        g_assert_cmpfloat_with_epsilon(majors[1], a, atol);
        g_free(majors);

        gdouble *minors = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_EQUIV_ELLIPSE_MINOR, &ngrains);
        g_assert_cmpint(ngrains, ==, 1);
        gdouble btol = 0.25*(1.0 + 5.0/sqrt(gwy_nield_count(nield)));
        g_assert_cmpfloat_with_epsilon(minors[1], b, btol);
        g_free(minors);

        gdouble *angles = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_EQUIV_ELLIPSE_ANGLE, &ngrains);
        g_assert_cmpint(ngrains, ==, 1);
        // The angle is only comparable if the eccentricity is noticeable. Otherwise it is given by discretisation and
        // completely arbitrary. Use large tolerances if a ≈ b or the ellipse is relatively small.
        if (a/b > 1.3) {
            phi = make_ref_angle_compatible(angles[1], phi);
            gdouble tol = 0.25*(b/a + 5.0/sqrt(gwy_nield_count(nield)));
            g_assert_cmpfloat_with_epsilon(angles[1], phi, tol);
        }
        g_free(angles);

        g_assert_finalize_object(nield);
        g_assert_finalize_object(field);
    }
}

void
test_nield_grain_quantity_boundary_length(void)
{
    static const gchar src1[] =
        "1         8\n"
        "222   44444\n"
        "3  3  46664\n"
        " 33   46664\n"
        "833   44444";

    enum { expected_ngrains = 8 };
    static const gdouble expected_lengths[expected_ngrains + 1] = {
        0.0,
        4*5.0,
        4*5.0 + 8*4.0,
        12*5.0 + 4*4.0 + 4*3.0,
        8*5.0 + 24*4.0 + 16*3.0,
        0.0,
        4*5.0 + 8*4.0 + 4*3.0,
        0.0,
        8*5.0,
    };

    GwyNield *nield = parse_nield(src1);
    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    // Use a Pythagorean triangle for pixel size to get nice round diagonals.
    GwyField *field = gwy_field_new(xres, yres, 8.0*xres, 6.0*yres, FALSE);

    gint ngrains;
    gdouble *lengths = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_PERIMETER, &ngrains);
    g_assert_cmpint(ngrains, ==, expected_ngrains);

    for (gint i = 1; i <= expected_ngrains; i++)
        g_assert_cmpfloat_with_epsilon(lengths[i], expected_lengths[i], 8*DBL_EPSILON);

    g_free(lengths);

    g_assert_finalize_object(field);
    g_assert_finalize_object(nield);
}

static void
test_nield_grain_quantity_inscribed_disc_rectangular(gboolean tall, gboolean contiguous)
{
    static const gchar src[] =
        "11111    22\n"
        "11111 3  22\n"
        "      3  22\n"
        "44    3  22\n"
        "   55    22\n"
        "   55  6 22";

    enum { expected_ngrains = 6 };
    // The centres are independent of pixel aspect ratio, so keep the reference values in pixels.
    static const gdouble expected_x[expected_ngrains + 1] = {
        0.0, 2.5, 10.0, 6.5, 1.0, 4.0, 7.5,
    };
    static const gdouble expected_y[expected_ngrains + 1] = {
        0.0, 1.0, 3.0, 2.5, 3.5, 5.0, 5.5,
    };
    // For pixels with aspect ratio 2.5 : 1.
    static const gdouble expected_r_flat[expected_ngrains + 1] = {
        0.0, 1.0, 2.5, 1.25, 0.5, 1.0, 0.5,
    };
    // For pixels with aspect ratio 1 : 2.5.
    static const gdouble expected_r_tall[expected_ngrains + 1] = {
        0.0, 2.5, 1.0, 0.5, 1.0, 1.0, 0.5,
    };

    GwyNield *nield = parse_nield(src);
    // This should not change the numbers, but it changes whether the object knows the grains are numbered in the
    // standard manner, or whether it must treat them as non-contiguous.
    if (contiguous)
        gwy_nield_number_contiguous(nield);

    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    gdouble dx = tall ? 1.0 : 2.5, dy = tall ? 2.5 : 1.0;
    GwyField *field = gwy_field_new(xres, yres, dx*xres, dy*yres, FALSE);
    gdouble xoff = 0.0; g_test_rand_double_range(-5.0, 5.0);
    gdouble yoff = 0.0; g_test_rand_double_range(-5.0, 5.0);
    gwy_field_set_xoffset(field, xoff);
    gwy_field_set_yoffset(field, yoff);

    gint ngrains;

    gdouble *xcentres = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_INSCRIBED_DISC_X, &ngrains);
    g_assert_cmpint(ngrains, ==, expected_ngrains);
    for (gint i = 1; i <= expected_ngrains; i++)
        g_assert_cmpfloat_with_epsilon(xcentres[i], dx*expected_x[i] + xoff, 20*DBL_EPSILON);
    g_free(xcentres);

    gdouble *ycentres = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_INSCRIBED_DISC_Y, &ngrains);
    g_assert_cmpint(ngrains, ==, expected_ngrains);
    for (gint i = 1; i <= expected_ngrains; i++)
        g_assert_cmpfloat_with_epsilon(ycentres[i], dy*expected_y[i] + yoff, 20*DBL_EPSILON);
    g_free(ycentres);

    const gdouble *expected_r = (tall ? expected_r_tall : expected_r_flat);
    gdouble *radii = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_INSCRIBED_DISC_R, &ngrains);
    g_assert_cmpint(ngrains, ==, expected_ngrains);
    for (gint i = 1; i <= expected_ngrains; i++)
        g_assert_cmpfloat_with_epsilon(radii[i], expected_r[i], 1e-13);
    g_free(radii);

    g_assert_finalize_object(field);
    g_assert_finalize_object(nield);
}

void
test_nield_grain_quantity_inscribed_disc_rectangular_tall_contiguous(void)
{
    test_nield_grain_quantity_inscribed_disc_rectangular(TRUE, TRUE);
}

void
test_nield_grain_quantity_inscribed_disc_rectangular_tall_split(void)
{
    test_nield_grain_quantity_inscribed_disc_rectangular(TRUE, FALSE);
}

void
test_nield_grain_quantity_inscribed_disc_rectangular_flat_contiguous(void)
{
    test_nield_grain_quantity_inscribed_disc_rectangular(FALSE, TRUE);
}

void
test_nield_grain_quantity_inscribed_disc_rectangular_flat_split(void)
{
    test_nield_grain_quantity_inscribed_disc_rectangular(FALSE, FALSE);
}

// For an L-shaped corner, there are two distinct cases. Assuming dx ≥ dy (the other situation is symmetrical), then
// a) 2dy < dx, in such case R = dy and the centre has a horizontal freedom of movement because it is not restricted
//    by the long dx side, it just needs to be at least dy from each side: y = R, x ∈ [R, dx - R]
// b) 2dy ≥ dx, in such case R = dx + dy - sqrt(2 dx dy) and the position in unique x = y = R (assuming an L shape
//    with the corner in lower left, as the letter L)
//
// Tall-ish and flat-ish functions would test the case b); tall and flat test case a).
//
// However, the inscribed disc candidate finding can fail in all sorts of weird ways for thin non-1:1 grains. For
// large grains, it may mist the absolutely best position, but the distance transform has a real shape to work with
// and its maximum is always at least a reasonable candidate. But not for thin grains. No point testing cases that
// have never worked reliably…
#if 0
static void
test_nield_grain_quantity_inscribed_disc_l_shaped_tallish(gboolean contiguous)
{
    static const gchar src[] =
        "11  2 3  44\n"
        "1  22 33  4\n"
        "           \n"
        "5555555555 \n"
        "         5 \n"
        "         5 ";

    enum { expected_ngrains = 5 };
    double dx = 1.0, dy = 0.5*(sqrt(5) + 1);
    double R = dx + dy - sqrt(2*dx*dy);
    const gdouble expected_x[expected_ngrains + 1] = {
        0.0, R, 5.0 - R, 6.0 + R, 11.0 - R, 10.0 - R,
    };
    const gdouble expected_y[expected_ngrains + 1] = {
        0.0, R, 2.0*dy - R, 2.0*dy - R, R, 3.0*dy + R,
    };

    GwyNield *nield = parse_nield(src);
    // This should not change the numbers, but it changes whether the object knows the grains are numbered in the
    // standard manner, or whether it must treat them as non-contiguous.
    if (contiguous)
        gwy_nield_number_contiguous(nield);

    gint xres = gwy_nield_get_xres(nield), yres = gwy_nield_get_yres(nield);
    GwyField *field = gwy_field_new(xres, yres, dx*xres, dy*yres, FALSE);
    gdouble xoff = g_test_rand_double_range(-5.0, 5.0);
    gdouble yoff = g_test_rand_double_range(-5.0, 5.0);
    gwy_field_set_xoffset(field, xoff);
    gwy_field_set_yoffset(field, yoff);

    gint ngrains;

    gdouble *xcentres = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_INSCRIBED_DISC_X, &ngrains);
    g_assert_cmpint(ngrains, ==, expected_ngrains);
    for (gint i = 1; i <= expected_ngrains; i++)
        g_assert_cmpfloat_with_epsilon(xcentres[i], expected_x[i] + xoff, 1e-4);
    g_free(xcentres);

    gdouble *ycentres = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_INSCRIBED_DISC_Y, &ngrains);
    g_assert_cmpint(ngrains, ==, expected_ngrains);
    for (gint i = 1; i <= expected_ngrains; i++)
        g_assert_cmpfloat_with_epsilon(ycentres[i], expected_y[i] + yoff, 1e-4);
    g_free(ycentres);

    gdouble *radii = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_INSCRIBED_DISC_R, &ngrains);
    g_assert_cmpint(ngrains, ==, expected_ngrains);
    for (gint i = 1; i <= expected_ngrains; i++)
        g_assert_cmpfloat_with_epsilon(radii[i], R, 1e-4);
    g_free(radii);

    g_assert_finalize_object(field);
    g_assert_finalize_object(nield);
}

void
test_nield_grain_quantity_inscribed_disc_l_shaped_tallish_contiguous(void)
{
    test_nield_grain_quantity_inscribed_disc_l_shaped_tallish(TRUE);
}

void
test_nield_grain_quantity_inscribed_disc_l_shaped_tallish_split(void)
{
    test_nield_grain_quantity_inscribed_disc_l_shaped_tallish(FALSE);
}
#endif

void
test_nield_grain_quantity_mean_boundary_distance_circular(void)
{
    for (gint size = 4; size < 200; size = MAX(8*size/7, size+1)) {
        gint extleft = g_test_rand_int_range(0, 5);
        gint extright = g_test_rand_int_range(0, 5);
        gint extup = g_test_rand_int_range(0, 5);
        gint extdown = g_test_rand_int_range(0, 5);
        gdouble dx = g_test_rand_double_range(0.5, 2.0), dy = dx;

        GwyNield *nield = gwy_nield_new(size + extleft + extright, size + extup + extdown);
        gwy_nield_elliptic_area_fill(nield, extleft, extup, size, size, 1);
        GwyField *field = gwy_field_new(size + extleft + extright, size + extup + extdown,
                                        dx*(size + extleft + extright), dy*(size + extup + extdown),
                                        TRUE);

        gint ngrains;

        gdouble *edist = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_MEAN_BOUNDARY_DISTANCE, &ngrains);
        g_assert_cmpint(ngrains, ==, 1);
        gdouble expected_edist = dx*size/6.0;
        gdouble rtol = 0.005 + 2.0/(4 + size);
        g_assert_cmpfloat_with_epsilon(edist[1], expected_edist, expected_edist*rtol);
        g_free(edist);

        g_assert_finalize_object(field);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_grain_quantity_mean_boundary_distance_rectangular(void)
{
    for (gint sizea = 3; sizea < 60; sizea = MAX(4*sizea/3, sizea+1)) {
        for (gint sizeb = 3; sizeb < 60; sizeb = MAX(4*sizeb/3, sizeb+1)) {
            gint extleft = g_test_rand_int_range(0, 5);
            gint extright = g_test_rand_int_range(0, 5);
            gint extup = g_test_rand_int_range(0, 5);
            gint extdown = g_test_rand_int_range(0, 5);
            gdouble dx = g_test_rand_double_range(0.5, 2.0), dy = dx;

            GwyNield *nield = gwy_nield_new(sizea + extleft + extright, sizeb + extup + extdown);
            gwy_nield_area_fill(nield, NULL, GWY_MASK_IGNORE, extleft, extup, sizea, sizeb, 1);
            GwyField *field = gwy_field_new(sizea + extleft + extright, sizeb + extup + extdown,
                                            dx*(sizea + extleft + extright), dy*(sizeb + extup + extdown),
                                            TRUE);

            gint ngrains;

            gdouble *edist = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_MEAN_BOUNDARY_DISTANCE, &ngrains);
            g_assert_cmpint(ngrains, ==, 1);
            gdouble m = MIN(dx*sizea, dy*sizeb), M = MAX(dx*sizea, dy*sizeb);
            gdouble h = 2*m*M/(m + M);
            gdouble expected_edist = 0.25*m - m*m/(12.0*M);
            gdouble rtol = 0.005 + 1.5/(3 + h);
            g_assert_cmpfloat_with_epsilon(edist[1], expected_edist, expected_edist*rtol);
            g_free(edist);

            g_assert_finalize_object(field);
            g_assert_finalize_object(nield);
        }
    }
}

void
test_nield_grain_quantity_shape_number_circular(void)
{
    for (gint size = 4; size < 200; size = MAX(8*size/7, size+1)) {
        gint extleft = g_test_rand_int_range(0, 5);
        gint extright = g_test_rand_int_range(0, 5);
        gint extup = g_test_rand_int_range(0, 5);
        gint extdown = g_test_rand_int_range(0, 5);
        gdouble dx = g_test_rand_double_range(0.5, 2.0), dy = dx;

        GwyNield *nield = gwy_nield_new(size + extleft + extright, size + extup + extdown);
        gwy_nield_elliptic_area_fill(nield, extleft, extup, size, size, 1);
        GwyField *field = gwy_field_new(size + extleft + extright, size + extup + extdown,
                                        dx*(size + extleft + extright), dy*(size + extup + extdown),
                                        TRUE);

        gint ngrains;

        gdouble *shapeno = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_SHAPE_NUMBER, &ngrains);
        g_assert_cmpint(ngrains, ==, 1);
        gdouble expected_shapeno = 1.0;
        gdouble rtol = 5.0/size;
        g_assert_cmpfloat_with_epsilon(shapeno[1], expected_shapeno, expected_shapeno*rtol);
        g_free(shapeno);

        g_assert_finalize_object(field);
        g_assert_finalize_object(nield);
    }
}

void
test_nield_grain_quantity_shape_number_rectangular(void)
{
    for (gint sizea = 3; sizea < 60; sizea = MAX(4*sizea/3, sizea+1)) {
        for (gint sizeb = 3; sizeb < 60; sizeb = MAX(4*sizeb/3, sizeb+1)) {
            gint extleft = g_test_rand_int_range(0, 5);
            gint extright = g_test_rand_int_range(0, 5);
            gint extup = g_test_rand_int_range(0, 5);
            gint extdown = g_test_rand_int_range(0, 5);
            gdouble dx = g_test_rand_double_range(0.5, 2.0), dy = dx;

            GwyNield *nield = gwy_nield_new(sizea + extleft + extright, sizeb + extup + extdown);
            gwy_nield_area_fill(nield, NULL, GWY_MASK_IGNORE, extleft, extup, sizea, sizeb, 1);
            GwyField *field = gwy_field_new(sizea + extleft + extright, sizeb + extup + extdown,
                                            dx*(sizea + extleft + extright), dy*(sizeb + extup + extdown),
                                            TRUE);

            gint ngrains;

            gdouble *shapeno = gwy_nield_grain_quantity(nield, field, GWY_GRAIN_SHAPE_NUMBER, &ngrains);
            g_assert_cmpint(ngrains, ==, 1);
            gdouble m = MIN(dx*sizea, dy*sizeb), M = MAX(dx*sizea, dy*sizeb);
            gdouble h = 2*m*M/(m + M);
            gdouble expected_edist = 0.25*m - m*m/(12.0*M);
            gdouble expected_shapeno = dx*dy*sizea*sizeb/(expected_edist*expected_edist*9*G_PI);
            gdouble rtol = 0.01 + 2.0/(3 + h);
            g_assert_cmpfloat_with_epsilon(shapeno[1], expected_shapeno, expected_shapeno*rtol);
            g_free(shapeno);

            g_assert_finalize_object(field);
            g_assert_finalize_object(nield);
        }
    }
}

/* Can be done using full-field functions: */
/* GWY_GRAIN_CURVATURE_WHATEVER: Would use the same maths function. The coefficients can be obtained in a silly way by
 * filling a field with the corresponding powers of coordinates and summing – we already do a similar thing for X and
 * Y centre. But it is a weird way to do this. */

/* Probably fixed test cases, no idea how to get the value independently using full-field functions: */
/* GWY_GRAIN_INSCRIBED_DISC_WHATEVER: This is a heuristic, not guaranteed to give 100% the best disc and often there
 * are many equally good best discs. */
/* GWY_GRAIN_MEAN_RADIUS: ??? */
/* GWY_GRAIN_WHATEVER_MARTIN_WHATEVER: ??? */
/* GWY_GRAIN_FERET_LENGTH: Simple length of projection in orthogonal direction. How to test it without simply
 * implementing the same thing twice? */

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