/*
 *  $Id: shaped.c 29416 2026-01-30 16:49:54Z yeti-dn $
 *  Copyright (C) 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"

GwyNield*
parse_nield(const gchar *s)
{
    gint xres = -1, yres = 0;

    const gchar *lstart = s, *lend;
    while ((lend = strchr(lstart, '\n'))) {
        if (xres < 0)
            xres = lend - lstart;
        else
            g_assert(xres == lend-lstart);
        lstart = lend+1;
        yres++;
    }
    if (strlen(lstart)) {
        if (xres < 0)
            xres = strlen(lstart);
        else
            g_assert(xres == (gint)strlen(lstart));
        yres++;
    }
    g_assert(xres > 0);
    g_assert(yres > 0);

    GwyNield *nield = gwy_nield_new(xres, yres);
    gint *d = gwy_nield_get_data(nield);
    const gchar *p = s;
    for (gint i = 0; i < xres*yres; i++) {
        while (*p == '\n')
            p++;

        if (g_ascii_isdigit(*p))
            d[i] = (gint)*p - (gint)'0';
        else if (g_ascii_islower(*p))
            d[i] = (gint)*p - (gint)'a' + 10;
        else if (*p == ' ')
            d[i] = 0;
        else if (*p == '-')
            d[i] = -1;
        else {
            g_assert_not_reached();
        }
        p++;
    }
    while (*p == '\n')
        p++;
    g_assert(!*p);

    return nield;
}

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

    for (guint i = 0; i < n; i++) {
        guint xres = g_test_rand_int_range(1, 20+1), yres = g_test_rand_int_range(2, 80+1);
        guint width = 1, height = g_test_rand_int_range(1, yres+1);
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyNield *nield = gwy_nield_new(xres, yres);
        // For width=1 it should simply fill the entire bounding box.
        gsize npix = gwy_nield_elliptic_area_fill(nield, col, row, width, height, 1);
        g_assert_cmpuint(npix, ==, height);
        gsize nfilled = gwy_nield_area_count(nield, NULL, GWY_MASK_IGNORE, col, row, width, height);
        g_assert_cmpuint(nfilled, ==, height);
        gwy_nield_invalidate(nield);
        gsize ntotal = gwy_nield_count(nield);
        g_assert_cmpuint(ntotal, ==, height);
        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        guint xres = g_test_rand_int_range(2, 80+1), yres = g_test_rand_int_range(1, 20+1);
        guint width = g_test_rand_int_range(1, xres+1), height = 1;
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyNield *nield = gwy_nield_new(xres, yres);
        // For height=1 it should simply fill the entire bounding box.
        gsize npix = gwy_nield_elliptic_area_fill(nield, col, row, width, height, 1);
        g_assert_cmpuint(npix, ==, width);
        gsize nfilled = gwy_nield_area_count(nield, NULL, GWY_MASK_IGNORE, col, row, width, height);
        g_assert_cmpuint(nfilled, ==, width);
        gwy_nield_invalidate(nield);
        gsize ntotal = gwy_nield_count(nield);
        g_assert_cmpuint(ntotal, ==, width);
        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        guint xres = g_test_rand_int_range(2, 20+1), yres = g_test_rand_int_range(2, 80+1);
        guint width = 2, height = g_test_rand_int_range(2, yres+1);
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyNield *nield = gwy_nield_new(xres, yres);
        // Even-sized ellipses are weird. We draw them the basically same way as GIMP (for example). It is
        // mathematically correct. So that's what we test here. However, they do not touch the short bounding sides
        // for very long aspect ratios. The expected empty margin width is @margin and we must subtract it.
        guint margin = (guint)floor(0.5*(height*(1.0 - sqrt(1.0 - 1.0/(width*width))) + 1.0));
        gsize npix = gwy_nield_elliptic_area_fill(nield, col, row, width, height, 1);
        g_assert_cmpuint(npix, ==, width*(height - 2*margin));
        gsize nfilled = gwy_nield_area_count(nield, NULL, GWY_MASK_IGNORE, col, row, width, height);
        g_assert_cmpuint(nfilled, ==, width*(height - 2*margin));
        gwy_nield_invalidate(nield);
        gsize ntotal = gwy_nield_count(nield);
        g_assert_cmpuint(ntotal, ==, width*(height - 2*margin));
        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        guint xres = g_test_rand_int_range(2, 80+1), yres = g_test_rand_int_range(2, 20+1);
        guint width = g_test_rand_int_range(2, xres+1), height = 2;
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyNield *nield = gwy_nield_new(xres, yres);
        // Even-sized ellipses are weird. We draw them the basically same way as GIMP (for example). It is
        // mathematically correct. So that's what we test here. However, they do not touch the short bounding sides
        // for very long aspect ratios. The expected empty margin width is @margin and we must subtract it.
        guint margin = (guint)floor(0.5*(width*(1.0 - sqrt(1.0 - 1.0/(height*height))) + 1.0));
        gsize npix = gwy_nield_elliptic_area_fill(nield, col, row, width, height, 1);
        g_assert_cmpuint(npix, ==, (width - 2*margin)*height);
        gsize nfilled = gwy_nield_area_count(nield, NULL, GWY_MASK_IGNORE, col, row, width, height);
        g_assert_cmpuint(nfilled, ==, (width - 2*margin)*height);
        gwy_nield_invalidate(nield);
        gsize ntotal = gwy_nield_count(nield);
        g_assert_cmpuint(ntotal, ==, (width - 2*margin)*height);
        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        guint xres = g_test_rand_int_range(1, 20+1), yres = g_test_rand_int_range(2, 80+1);
        guint width = 1, height = g_test_rand_int_range(1, yres+1);
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);
        // For width=1 it should simply fill the entire bounding box.
        gsize npix = gwy_field_elliptic_area_fill(field, col, row, width, height, 1.0);
        g_assert_cmpuint(npix, ==, height);
        // The functions should return an exactly represented integer, so it should be exactly comparable to an
        // actual integer.
        gdouble nfilled = gwy_field_area_sum(field, NULL, GWY_MASK_IGNORE, col, row, width, height);
        g_assert_cmpfloat(nfilled, ==, height);
        gwy_field_invalidate(field);
        gdouble ntotal = gwy_field_sum(field);
        g_assert_cmpfloat(ntotal, ==, height);
        g_assert_finalize_object(field);
    }
}

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

    for (guint i = 0; i < n; i++) {
        guint xres = g_test_rand_int_range(2, 80+1), yres = g_test_rand_int_range(1, 20+1);
        guint width = g_test_rand_int_range(1, xres+1), height = 1;
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);
        // For height=1 it should simply fill the entire bounding box.
        gsize npix = gwy_field_elliptic_area_fill(field, col, row, width, height, 1.0);
        g_assert_cmpuint(npix, ==, width);
        // The functions should return an exactly represented integer, so it should be exactly comparable to an
        // actual integer.
        gdouble nfilled = gwy_field_area_sum(field, NULL, GWY_MASK_IGNORE, col, row, width, height);
        g_assert_cmpfloat(nfilled, ==, width);
        gwy_field_invalidate(field);
        gdouble ntotal = gwy_field_sum(field);
        g_assert_cmpfloat(ntotal, ==, width);
        g_assert_finalize_object(field);
    }
}

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

    for (guint i = 0; i < n; i++) {
        guint xres = g_test_rand_int_range(2, 20+1), yres = g_test_rand_int_range(2, 80+1);
        guint width = 2, height = g_test_rand_int_range(2, yres+1);
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);
        // Even-sized ellipses are weird. We draw them the basically same way as GIMP (for example). It is
        // mathematically correct. So that's what we test here. However, they do not touch the short bounding sides
        // for very long aspect ratios. The expected empty margin width is @margin and we must subtract it.
        guint margin = (guint)floor(0.5*(height*(1.0 - sqrt(1.0 - 1.0/(width*width))) + 1.0));
        gsize npix = gwy_field_elliptic_area_fill(field, col, row, width, height, 1.0);
        g_assert_cmpuint(npix, ==, width*(height - 2*margin));
        // The functions should return an exactly represented integer, so it should be exactly comparable to an
        // actual integer.
        gdouble nfilled = gwy_field_area_sum(field, NULL, GWY_MASK_IGNORE, col, row, width, height);
        g_assert_cmpfloat(nfilled, ==, width*(height - 2*margin));
        gwy_field_invalidate(field);
        gdouble ntotal = gwy_field_sum(field);
        g_assert_cmpfloat(ntotal, ==, width*(height - 2*margin));
        g_assert_finalize_object(field);
    }
}

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

    for (guint i = 0; i < n; i++) {
        guint xres = g_test_rand_int_range(2, 80+1), yres = g_test_rand_int_range(2, 20+1);
        guint width = g_test_rand_int_range(2, xres+1), height = 2;
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);
        // Even-sized ellipses are weird. We draw them the basically same way as GIMP (for example). It is
        // mathematically correct. So that's what we test here. However, they do not touch the short bounding sides
        // for very long aspect ratios. The expected empty margin width is @margin and we must subtract it.
        guint margin = (guint)floor(0.5*(width*(1.0 - sqrt(1.0 - 1.0/(height*height))) + 1.0));
        gsize npix = gwy_field_elliptic_area_fill(field, col, row, width, height, 1.0);
        g_assert_cmpuint(npix, ==, (width - 2*margin)*height);
        // The functions should return an exactly represented integer, so it should be exactly comparable to an
        // actual integer.
        gdouble nfilled = gwy_field_area_sum(field, NULL, GWY_MASK_IGNORE, col, row, width, height);
        g_assert_cmpfloat(nfilled, ==, (width - 2*margin)*height);
        gwy_field_invalidate(field);
        gdouble ntotal = gwy_field_sum(field);
        g_assert_cmpfloat(ntotal, ==, (width - 2*margin)*height);
        g_assert_finalize_object(field);
    }
}

static GwyNield*
make_small_ellipse(void)
{
    static const gchar src_1x1[] =
        "1";
    static const gchar src_1x2[] =
        "11";
    static const gchar src_2x1[] =
        "1\n"
        "1";
    static const gchar src_2x2[] =
        "11\n"
        "11";
    static const gchar src_3x2[] =
        "111\n"
        "111";
    static const gchar src_2x3[] =
        "11\n"
        "11\n"
        "11";
    static const gchar src_3x3[] =
        "111\n"
        "111\n"
        "111";
    static const gchar src_4x3[] =
        " 1 \n"
        "111\n"
        "111\n"
        " 1 ";
    static const gchar src_3x4[] =
        " 11 \n"
        "1111\n"
        " 11 ";
    static const gchar src_4x4[] =
        " 11 \n"
        "1111\n"
        "1111\n"
        " 11 ";
    static const gchar src_5x3[] =
        " 111 \n"
        "11111\n"
        " 111 ";
    static const gchar src_5x4[] =
        " 111 \n"
        "11111\n"
        "11111\n"
        " 111 ";
    static const gchar src_3x5[] =
        " 111 \n"
        "11111\n"
        " 111 ";
    static const gchar src_4x5[] =
        " 11 \n"
        "1111\n"
        "1111\n"
        "1111\n"
        " 11 ";
    static const gchar src_5x5[] =
        " 111 \n"
        "11111\n"
        "11111\n"
        "11111\n"
        " 111 ";
    static const gchar src_6x5[] =
        " 1111 \n"
        "111111\n"
        "111111\n"
        "111111\n"
        " 1111 ";
    static const gchar src_5x6[] =
        " 111 \n"
        "11111\n"
        "11111\n"
        "11111\n"
        "11111\n"
        " 111 ";
    static const gchar src_7x5[] =
        " 11111 \n"
        "1111111\n"
        "1111111\n"
        "1111111\n"
        " 11111 ";
    static const gchar src_5x7[] =
        " 111 \n"
        "11111\n"
        "11111\n"
        "11111\n"
        "11111\n"
        "11111\n"
        " 111 ";
    static const gchar src_7x6[] =
        "  111  \n"
        "1111111\n"
        "1111111\n"
        "1111111\n"
        "1111111\n"
        "  111  ";
    static const gchar src_6x7[] =
        " 1111 \n"
        " 1111 \n"
        "111111\n"
        "111111\n"
        "111111\n"
        " 1111 \n"
        " 1111 ";
    static const gchar src_7x7[] =
        "  111  \n"
        " 11111 \n"
        "1111111\n"
        "1111111\n"
        "1111111\n"
        " 11111 \n"
        "  111  ";
    static const gchar src_11x3[] =
        " 111111111 \n"
        "11111111111\n"
        " 111111111 ";
    static const gchar src_3x11[] =
        " 1 \n"
        "111\n"
        "111\n"
        "111\n"
        "111\n"
        "111\n"
        "111\n"
        "111\n"
        "111\n"
        "111\n"
        " 1 ";
    static const gchar src_12x3[] =
        "  11111111  \n"
        "111111111111\n"
        "  11111111  ";
    static const gchar src_3x12[] =
        " 1 \n"
        " 1 \n"
        "111\n"
        "111\n"
        "111\n"
        "111\n"
        "111\n"
        "111\n"
        "111\n"
        "111\n"
        " 1 \n"
        " 1 ";
    static const gchar *ellipses[] = {
        src_1x1,
        src_1x2, src_2x1,
        src_2x2,
        src_3x2, src_2x3,
        src_3x3,
        src_4x3, src_3x4,
        src_4x4,
        src_5x3, src_3x5,
        src_5x4, src_4x5,
        src_5x5,
        src_6x5, src_5x6,
        src_7x5, src_5x7,
        src_7x6, src_6x7,
        src_7x7,
        src_11x3, src_3x11,
        src_12x3, src_3x12,
    };

    return parse_nield(ellipses[g_test_rand_int_range(0, G_N_ELEMENTS(ellipses))]);
}

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

    for (guint i = 0; i < n; i++) {
        GwyNield *shape = make_small_ellipse();
        guint width = gwy_nield_get_xres(shape), height = gwy_nield_get_yres(shape);
        guint xres = g_test_rand_int_range(width, width+20), yres = g_test_rand_int_range(height, height+20);
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyNield *nield = gwy_nield_new(xres, yres);
        // Check that filling an ellipse with the same dimensions gives the expected shape.
        gsize npix = gwy_nield_elliptic_area_fill(nield, col, row, width, height, 1);
        g_assert_cmpuint(npix, ==, gwy_nield_count(shape));
        gsize ntotal = gwy_nield_count(nield);
        g_assert_cmpuint(ntotal, ==, npix);
        GwyNield *extracted = gwy_nield_area_extract(nield, col, row, width, height);
        nield_assert_equal(G_OBJECT(extracted), G_OBJECT(shape));
        g_assert_finalize_object(extracted);
        g_assert_finalize_object(nield);
        g_assert_finalize_object(shape);
    }
}

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

    for (guint i = 0; i < n; i++) {
        GwyNield *nshape = make_small_ellipse();
        guint width = gwy_nield_get_xres(nshape), height = gwy_nield_get_yres(nshape);
        GwyField *shape = gwy_field_new(width, height, width, height, FALSE);
        gwy_field_fill_mask(shape, nshape, 0.0, 1.0);
        guint xres = g_test_rand_int_range(width, width+20), yres = g_test_rand_int_range(height, height+20);
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);
        // Check that filling an ellipse with the same dimensions gives the expected shape.
        gsize npix = gwy_field_elliptic_area_fill(field, col, row, width, height, 1.0);
        // The functions should return an exactly represented integer, so it should be exactly comparable to an
        // actual integer.
        g_assert_cmpfloat(npix, ==, gwy_field_sum(shape));
        gsize ntotal = gwy_field_sum(field);
        g_assert_cmpfloat(ntotal, ==, npix);
        GwyField *extracted = gwy_field_area_extract(field, col, row, width, height);
        field_assert_equal(G_OBJECT(extracted), G_OBJECT(shape));
        g_assert_finalize_object(extracted);
        g_assert_finalize_object(field);
        g_assert_finalize_object(shape);
        g_assert_finalize_object(nshape);
    }
}

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

    for (guint i = 0; i < n; i++) {
        guint width = g_test_rand_int_range(1, 30), height = g_test_rand_int_range(1, 30);
        guint xres = g_test_rand_int_range(width, width+20), yres = g_test_rand_int_range(height, height+20);
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyNield *nield = gwy_nield_new(xres, yres);
        gwy_nield_elliptic_area_fill(nield, col, row, width, height, 1);
        // Draw the ellipse into a tranposed rectangle and verify it is the same as transposing the ellipse.
        GwyNield *swapped = gwy_nield_new(yres, xres);
        gwy_nield_elliptic_area_fill(swapped, row, col, height, width, 1);
        GwyNield *transposed = gwy_nield_new(1, 1);
        gwy_nield_transpose(nield, transposed, FALSE);
        nield_assert_equal(G_OBJECT(swapped), G_OBJECT(transposed));
        g_assert_finalize_object(swapped);
        g_assert_finalize_object(transposed);
        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        guint width = g_test_rand_int_range(1, 30), height = g_test_rand_int_range(1, 30);
        guint xres = g_test_rand_int_range(width, width+20), yres = g_test_rand_int_range(height, height+20);
        guint col = g_test_rand_int_range(0, xres-width+1), row = g_test_rand_int_range(0, yres-height+1);
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);
        gwy_field_elliptic_area_fill(field, col, row, width, height, 1.0);
        // Draw the ellipse into a tranposed rectangle and verify it is the same as transposing the ellipse.
        GwyField *swapped = gwy_field_new(yres, xres, yres, xres, TRUE);
        gwy_field_elliptic_area_fill(swapped, row, col, height, width, 1.0);
        GwyField *transposed = gwy_field_new(1, 1, 1, 1, FALSE);
        gwy_field_transpose(field, transposed, FALSE);
        field_assert_equal(G_OBJECT(swapped), G_OBJECT(transposed));
        g_assert_finalize_object(swapped);
        g_assert_finalize_object(transposed);
        g_assert_finalize_object(field);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint width = g_test_rand_int_range(1, 30), height = g_test_rand_int_range(1, 30);
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-25, 25+1), row = g_test_rand_int_range(-25, 25+1);
        GwyNield *nield = gwy_nield_new(xres, yres);
        // Draw whatever wherever. This also checks we do not crash.
        gwy_nield_elliptic_area_fill(nield, col, row, width, height, 1);
        gint top = MIN(0, row), bot = MAX(yres, row+height);
        gint left = MIN(0, col), right = MAX(xres, col+width);

        // Check the result is the same as drawing the full shape and cropping. If there is any intersection at all.
        // Otherwise just check we did not draw anything.
        if (col-left >= xres || col-left + width <= 0
            || row-top >= yres || row-top + height <= 0) {
            gwy_nield_invalidate(nield);
            g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        }
        else {
            GwyNield *bignield = gwy_nield_new(right-left, bot-top);
            gwy_nield_elliptic_area_fill(bignield, col-left, row-top, width, height, 1);
            GwyNield *extracted = gwy_nield_area_extract(bignield, -left, -top, xres, yres);
            nield_assert_equal(G_OBJECT(nield), G_OBJECT(extracted));
            g_assert_finalize_object(extracted);
            g_assert_finalize_object(bignield);
        }

        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint width = g_test_rand_int_range(1, 30), height = g_test_rand_int_range(1, 30);
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-25, 25+1), row = g_test_rand_int_range(-25, 25+1);
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);
        // Draw whatever wherever. This also checks we do not crash.
        gwy_field_elliptic_area_fill(field, col, row, width, height, 1.0);
        gint top = MIN(0, row), bot = MAX(yres, row+height);
        gint left = MIN(0, col), right = MAX(xres, col+width);

        // Check the result is the same as drawing the full shape and cropping. If there is any intersection at all.
        // Otherwise just check we did not draw anything.
        if (col-left >= xres || col-left + width <= 0
            || row-top >= yres || row-top + height <= 0) {
            gwy_field_invalidate(field);
            g_assert_cmpfloat(gwy_field_sum(field), ==, 0.0);
        }
        else {
            GwyField *bigfield = gwy_field_new(right-left, bot-top, right-left, bot-top, TRUE);
            gwy_field_elliptic_area_fill(bigfield, col-left, row-top, width, height, 1.0);
            GwyField *extracted = gwy_field_area_extract(bigfield, -left, -top, xres, yres);
            field_assert_equal(G_OBJECT(field), G_OBJECT(extracted));
            g_assert_finalize_object(extracted);
            g_assert_finalize_object(bigfield);
        }

        g_assert_finalize_object(field);
    }
}

// When we trust fill() has been tested thoroughly, we can use it to check that extract() and insert() work with the
// same set of pixels.
void
test_shaped_elliptic_nield_extract_data(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint i = 0; i < n; i++) {
        gint width = g_test_rand_int_range(1, 30), height = g_test_rand_int_range(1, 30);
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-25, 25+1), row = g_test_rand_int_range(-25, 25+1);
        GwyNield *nield = gwy_nield_new(xres, yres);

        gwy_nield_elliptic_area_fill(nield, col, row, width, height, 12345);
        gsize nmax = gwy_elliptic_area_size(width, height);
        gint *data = g_new(gint, 2*nmax + 100);
        gsize nextracted = gwy_nield_elliptic_area_extract(nield, col, row, width, height, data, NULL, NULL);
        g_assert_cmpuint(nextracted, <=, nmax);
        for (gsize k = 0; k < nextracted; k++)
            g_assert_cmpint(data[k], ==, 12345);
        gwy_clear(data, nextracted);
        gwy_nield_elliptic_area_insert(nield, col, row, width, height, data);
        g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        g_free(data);

        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint width = g_test_rand_int_range(1, 30), height = g_test_rand_int_range(1, 30);
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-25, 25+1), row = g_test_rand_int_range(-25, 25+1);
        GwyNield *nield = gwy_nield_new(xres, yres);

        gwy_nield_elliptic_area_fill(nield, col, row, width, height, 12345);
        gsize nmax = gwy_elliptic_area_size(width, height);
        gint *jpos = g_new(gint, 2*nmax + 100), *ipos = g_new(gint, 2*nmax + 100);
        gsize nextracted = gwy_nield_elliptic_area_extract(nield, col, row, width, height, NULL, jpos, ipos);
        g_assert_cmpuint(nextracted, <=, nmax);
        gint *data = gwy_nield_get_data(nield);
        for (gsize k = 0; k < nextracted; k++) {
            // The positions are relative to the region top left corner. So they must come from the interval
            // [0, width) × [0, height).
            g_assert_cmpint(jpos[k], >=, 0);
            g_assert_cmpint(ipos[k], >=, 0);
            g_assert_cmpint(jpos[k], <, width);
            g_assert_cmpint(ipos[k], <, height);
            // The pixels must also lie inside the field.
            g_assert_cmpint(jpos[k] + col, >=, 0);
            g_assert_cmpint(ipos[k] + row, >=, 0);
            g_assert_cmpint(jpos[k] + col, <, xres);
            g_assert_cmpint(ipos[k] + row, <, yres);

            gint pos = (ipos[k] + row)*xres + jpos[k] + col;
            g_assert_cmpint(data[pos], ==, 12345);
            data[pos] = 0;
        }
        g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        g_free(jpos);
        g_free(ipos);

        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint width = g_test_rand_int_range(1, 30), height = g_test_rand_int_range(1, 30);
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-25, 25+1), row = g_test_rand_int_range(-25, 25+1);
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);

        gwy_field_elliptic_area_fill(field, col, row, width, height, G_PI);
        gsize nmax = gwy_elliptic_area_size(width, height);
        gdouble *data = g_new(gdouble, 2*nmax + 100);
        gsize nextracted = gwy_field_elliptic_area_extract(field, col, row, width, height, data, NULL, NULL);
        g_assert_cmpuint(nextracted, <=, nmax);
        for (gsize k = 0; k < nextracted; k++)
            g_assert_cmpint(data[k], ==, G_PI);
        gwy_clear(data, nextracted);
        gwy_field_elliptic_area_insert(field, col, row, width, height, data);
        g_assert_cmpuint(gwy_field_sum(field), ==, 0.0);
        g_free(data);

        g_assert_finalize_object(field);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint width = g_test_rand_int_range(1, 30), height = g_test_rand_int_range(1, 30);
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-25, 25+1), row = g_test_rand_int_range(-25, 25+1);
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);

        gwy_field_elliptic_area_fill(field, col, row, width, height, G_PI);
        gsize nmax = gwy_elliptic_area_size(width, height);
        gint *jpos = g_new(gint, 2*nmax + 100), *ipos = g_new(gint, 2*nmax + 100);
        gsize nextracted = gwy_field_elliptic_area_extract(field, col, row, width, height, NULL, jpos, ipos);
        g_assert_cmpuint(nextracted, <=, nmax);
        gdouble *data = gwy_field_get_data(field);
        for (gsize k = 0; k < nextracted; k++) {
            // The positions are relative to the region top left corner. So they must come from the interval
            // [0, width) × [0, height).
            g_assert_cmpint(jpos[k], >=, 0);
            g_assert_cmpint(ipos[k], >=, 0);
            g_assert_cmpint(jpos[k], <, width);
            g_assert_cmpint(ipos[k], <, height);
            // The pixels must also lie inside the field.
            g_assert_cmpint(jpos[k] + col, >=, 0);
            g_assert_cmpint(ipos[k] + row, >=, 0);
            g_assert_cmpint(jpos[k] + col, <, xres);
            g_assert_cmpint(ipos[k] + row, <, yres);

            gint pos = (ipos[k] + row)*xres + jpos[k] + col;
            g_assert_cmpfloat(data[pos], ==, G_PI);
            data[pos] = 0;
        }
        g_assert_cmpfloat(gwy_field_sum(field), ==, 0.0);
        g_free(jpos);
        g_free(ipos);

        g_assert_finalize_object(field);
    }
}

static GwyNield*
make_small_circle(void)
{
    // Nice circles with n+1/2 radii. The API recommends using them.
    static const gchar src_0_5[] =
        "1";
    static const gchar src_1_5[] =
        "111\n"
        "111\n"
        "111";
    static const gchar src_2_5[] =
        " 111 \n"
        "11111\n"
        "11111\n"
        "11111\n"
        " 111 ";
    static const gchar src_3_5[] =
        "  111  \n"
        " 11111 \n"
        "1111111\n"
        "1111111\n"
        "1111111\n"
        " 11111 \n"
        "  111  ";
    static const gchar src_4_5[] =
        "  11111  \n"
        " 1111111 \n"
        "111111111\n"
        "111111111\n"
        "111111111\n"
        "111111111\n"
        "111111111\n"
        " 1111111 \n"
        "  11111  ";
    static const gchar src_5_5[] =
        "   11111   \n"
        "  1111111  \n"
        " 111111111 \n"
        "11111111111\n"
        "11111111111\n"
        "11111111111\n"
        "11111111111\n"
        "11111111111\n"
        " 111111111 \n"
        "  1111111  \n"
        "   11111   ";
    static const gchar src_6_5[] =
        "    11111    \n"
        "  111111111  \n"
        " 11111111111 \n"
        " 11111111111 \n"
        "1111111111111\n"
        "1111111111111\n"
        "1111111111111\n"
        "1111111111111\n"
        "1111111111111\n"
        " 11111111111 \n"
        " 11111111111 \n"
        "  111111111  \n"
        "    11111    ";
    static const gchar *circles[] = {
        src_0_5,
        src_1_5,
        src_2_5,
        src_3_5,
        src_4_5,
        src_5_5,
        src_6_5,
    };

    return parse_nield(circles[g_test_rand_int_range(0, G_N_ELEMENTS(circles))]);
}

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

    for (guint i = 0; i < n; i++) {
        GwyNield *shape = make_small_circle();
        guint width = gwy_nield_get_xres(shape), height = gwy_nield_get_yres(shape);
        g_assert_cmpuint(width, ==, height);
        g_assert_cmpuint(width % 2, ==, 1);
        gdouble r = 0.5*width;
        gdouble halfsize = width/2;
        guint xres = g_test_rand_int_range(width, width+20), yres = g_test_rand_int_range(height, height+20);
        guint col = g_test_rand_int_range(halfsize, xres-halfsize);
        guint row = g_test_rand_int_range(halfsize, yres-halfsize);
        GwyNield *nield = gwy_nield_new(xres, yres);
        // Check that filling a circle with the same radius gives the expected shape.
        gsize npix = gwy_nield_circular_area_fill(nield, col, row, r, 1);
        g_assert_cmpuint(npix, ==, gwy_nield_count(shape));
        gsize ntotal = gwy_nield_count(nield);
        g_assert_cmpuint(ntotal, ==, npix);
        GwyNield *extracted = gwy_nield_area_extract(nield, col-halfsize, row-halfsize, width, height);
        nield_assert_equal(G_OBJECT(extracted), G_OBJECT(shape));
        g_assert_finalize_object(extracted);
        g_assert_finalize_object(nield);
        g_assert_finalize_object(shape);
    }
}

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

    for (guint i = 0; i < n; i++) {
        GwyNield *nshape = make_small_circle();
        guint width = gwy_nield_get_xres(nshape), height = gwy_nield_get_yres(nshape);
        GwyField *shape = gwy_field_new(width, height, width, height, FALSE);
        gwy_field_fill_mask(shape, nshape, 0.0, 1.0);
        g_assert_cmpuint(width, ==, height);
        g_assert_cmpuint(width % 2, ==, 1);
        gdouble r = 0.5*width;
        gdouble halfsize = width/2;
        guint xres = g_test_rand_int_range(width, width+20), yres = g_test_rand_int_range(height, height+20);
        guint col = g_test_rand_int_range(halfsize, xres-halfsize);
        guint row = g_test_rand_int_range(halfsize, yres-halfsize);
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);
        // Check that filling a circle with the same radius gives the expected shape.
        gsize npix = gwy_field_circular_area_fill(field, col, row, r, 1.0);
        g_assert_cmpfloat(npix, ==, gwy_field_sum(shape));
        gdouble ntotal = gwy_field_sum(field);
        g_assert_cmpfloat(ntotal, ==, npix);
        GwyField *extracted = gwy_field_area_extract(field, col-halfsize, row-halfsize, width, height);
        field_assert_equal(G_OBJECT(extracted), G_OBJECT(shape));
        g_assert_finalize_object(extracted);
        g_assert_finalize_object(field);
        g_assert_finalize_object(shape);
        g_assert_finalize_object(nshape);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-10, xres+10), row = g_test_rand_int_range(-10, xres+10);
        gdouble r = 20.0*sqrt(g_test_rand_double());
        GwyNield *nield = gwy_nield_new(xres, yres);
        // Draw whatever wherever. This also checks we do not crash.
        gwy_nield_circular_area_fill(nield, col, row, r, 1);
        gint halfsize = (gint)floor(r);
        gint top = MIN(0, row-halfsize), bot = MAX(yres, row+halfsize+1);
        gint left = MIN(0, col-halfsize), right = MAX(xres, col+halfsize+1);

        // Check the result is the same as drawing the full shape and cropping. If there is any intersection at all.
        // Otherwise just check we did not draw anything.
        if (col-halfsize >= xres || col+halfsize < 0
            || row-halfsize >= yres || row+halfsize < 0) {
            gwy_nield_invalidate(nield);
            g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        }
        else {
            GwyNield *bignield = gwy_nield_new(right-left, bot-top);
            gwy_nield_circular_area_fill(bignield, col-left, row-top, r, 1);
            GwyNield *extracted = gwy_nield_area_extract(bignield, -left, -top, xres, yres);
            nield_assert_equal(G_OBJECT(nield), G_OBJECT(extracted));
            g_assert_finalize_object(extracted);
            g_assert_finalize_object(bignield);
        }

        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-10, xres+10), row = g_test_rand_int_range(-10, xres+10);
        gdouble r = 20.0*sqrt(g_test_rand_double());
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);
        // Draw whatever wherever. This also checks we do not crash.
        gwy_field_circular_area_fill(field, col, row, r, 1.0);
        gint halfsize = (gint)floor(r + 1e-12);
        gint top = MIN(0, row-halfsize), bot = MAX(yres, row+halfsize+1);
        gint left = MIN(0, col-halfsize), right = MAX(xres, col+halfsize+1);

        // Check the result is the same as drawing the full shape and cropping. If there is any intersection at all.
        // Otherwise just check we did not draw anything.
        if (col-halfsize >= xres || col+halfsize < 0
            || row-halfsize >= yres || row+halfsize < 0) {
            gwy_field_invalidate(field);
            g_assert_cmpuint(gwy_field_sum(field), ==, 0);
        }
        else {
            GwyField *bigfield = gwy_field_new(right-left, bot-top, right-left, bot-top, TRUE);
            gwy_field_circular_area_fill(bigfield, col-left, row-top, r, 1.0);
            GwyField *extracted = gwy_field_area_extract(bigfield, -left, -top, xres, yres);
            field_assert_equal(G_OBJECT(field), G_OBJECT(extracted));
            g_assert_finalize_object(extracted);
            g_assert_finalize_object(bigfield);
        }

        g_assert_finalize_object(field);
    }
}

// When we trust fill() has been tested thoroughly, we can use it to check that extract() and insert() work with the
// same set of pixels.
void
test_shaped_circular_nield_extract_data(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-25, 25+1), row = g_test_rand_int_range(-25, 25+1);
        gdouble r = 20.0*sqrt(g_test_rand_double());
        GwyNield *nield = gwy_nield_new(xres, yres);

        gwy_nield_circular_area_fill(nield, col, row, r, 12345);
        gsize nmax = gwy_circular_area_size(r);
        gint *data = g_new(gint, 2*nmax + 100);
        gsize nextracted = gwy_nield_circular_area_extract(nield, col, row, r, data, NULL, NULL);
        g_assert_cmpuint(nextracted, <=, nmax);
        for (gsize k = 0; k < nextracted; k++)
            g_assert_cmpint(data[k], ==, 12345);
        gwy_clear(data, nextracted);
        gwy_nield_circular_area_insert(nield, col, row, r, data);
        g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        g_free(data);

        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-25, 25+1), row = g_test_rand_int_range(-25, 25+1);
        gdouble r = 20.0*sqrt(g_test_rand_double());
        GwyNield *nield = gwy_nield_new(xres, yres);

        gwy_nield_circular_area_fill(nield, col, row, r, 12345);
        gsize nmax = gwy_circular_area_size(r);
        gint *jpos = g_new(gint, 2*nmax + 100), *ipos = g_new(gint, 2*nmax + 100);
        gsize nextracted = gwy_nield_circular_area_extract(nield, col, row, r, NULL, jpos, ipos);
        g_assert_cmpuint(nextracted, <=, nmax);
        gint *data = gwy_nield_get_data(nield);
        gint halfsize = (gint)floor(r + 1e-12);
        for (gsize k = 0; k < nextracted; k++) {
            // The positions are relative to the circle centre. So they must come from the interval
            // [-halfsize, halfsize]².
            g_assert_cmpint(jpos[k], >=, -halfsize);
            g_assert_cmpint(ipos[k], >=, -halfsize);
            g_assert_cmpint(jpos[k], <=, halfsize);
            g_assert_cmpint(ipos[k], <=, halfsize);
            // The pixels must also lie inside the field.
            g_assert_cmpint(jpos[k] + col, >=, 0);
            g_assert_cmpint(ipos[k] + row, >=, 0);
            g_assert_cmpint(jpos[k] + col, <, xres);
            g_assert_cmpint(ipos[k] + row, <, yres);

            gint pos = (ipos[k] + row)*xres + jpos[k] + col;
            g_assert_cmpint(data[pos], ==, 12345);
            data[pos] = 0;
        }
        g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        g_free(jpos);
        g_free(ipos);

        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-25, 25+1), row = g_test_rand_int_range(-25, 25+1);
        gdouble r = 20.0*sqrt(g_test_rand_double());
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);

        gwy_field_circular_area_fill(field, col, row, r, G_PI);
        gsize nmax = gwy_circular_area_size(r);
        gdouble *data = g_new(gdouble, 2*nmax + 100);
        gsize nextracted = gwy_field_circular_area_extract(field, col, row, r, data, NULL, NULL);
        g_assert_cmpuint(nextracted, <=, nmax);
        for (gsize k = 0; k < nextracted; k++)
            g_assert_cmpfloat(data[k], ==, G_PI);
        gwy_clear(data, nextracted);
        gwy_field_circular_area_insert(field, col, row, r, data);
        g_assert_cmpuint(gwy_field_sum(field), ==, 0.0);
        g_free(data);

        g_assert_finalize_object(field);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 35), yres = g_test_rand_int_range(1, 35);
        gint col = g_test_rand_int_range(-25, 25+1), row = g_test_rand_int_range(-25, 25+1);
        gdouble r = 20.0*sqrt(g_test_rand_double());
        GwyField *field = gwy_field_new(xres, yres, xres, yres, TRUE);

        gwy_field_circular_area_fill(field, col, row, r, G_PI);
        gsize nmax = gwy_circular_area_size(r);
        gint *jpos = g_new(gint, 2*nmax + 100), *ipos = g_new(gint, 2*nmax + 100);
        gsize nextracted = gwy_field_circular_area_extract(field, col, row, r, NULL, jpos, ipos);
        g_assert_cmpuint(nextracted, <=, nmax);
        gdouble *data = gwy_field_get_data(field);
        gint halfsize = (gint)floor(r + 1e-12);
        for (gsize k = 0; k < nextracted; k++) {
            // The positions are relative to the circle centre. So they must come from the interval
            // [-halfsize, halfsize]².
            g_assert_cmpint(jpos[k], >=, -halfsize);
            g_assert_cmpint(ipos[k], >=, -halfsize);
            g_assert_cmpint(jpos[k], <=, halfsize);
            g_assert_cmpint(ipos[k], <=, halfsize);
            // The pixels must also lie inside the field.
            g_assert_cmpint(jpos[k] + col, >=, 0);
            g_assert_cmpint(ipos[k] + row, >=, 0);
            g_assert_cmpint(jpos[k] + col, <, xres);
            g_assert_cmpint(ipos[k] + row, <, yres);

            gint pos = (ipos[k] + row)*xres + jpos[k] + col;
            g_assert_cmpfloat(data[pos], ==, G_PI);
            data[pos] = 0.0;
        }
        g_assert_cmpfloat(gwy_field_sum(field), ==, 0.0);
        g_free(jpos);
        g_free(ipos);

        g_assert_finalize_object(field);
    }
}

// Return 1 for major diagonal, -1 for minor diagonal, 0 for tiny lines filling the entire rectangle.
static gint
line_diag_type(gboolean topleft, gboolean topright, gboolean bottomleft, gboolean bottomright)
{
    if (topleft && bottomright && !topright && !bottomleft)
        return 1;
    if (!topleft && !bottomright && topright && bottomleft)
        return -1;
    if (topleft && bottomright && topright && bottomleft)
        return 0;
    g_assert_not_reached();
}

static GwyNield*
make_small_line8(void)
{
    static const gchar src_11[] =
        "1";
    static const gchar src_12[] =
        "1\n"
        "1";
    static const gchar src_13[] =
        "1\n"
        "1\n"
        "1";
    static const gchar src_21[] =
        "11";
    static const gchar src_31[] =
        "111";
    static const gchar src_22M[] =
        "1 \n"
        " 1";
    static const gchar src_22m[] =
        " 1\n"
        "1 ";
    static const gchar src_33M[] =
        "1  \n"
        " 1 \n"
        "  1";
    static const gchar src_33m[] =
        "  1\n"
        " 1 \n"
        "1  ";
    static const gchar src_42M[] =
        "11  \n"
        "  11";
    static const gchar src_42m[] =
        "  11\n"
        "11  ";
    static const gchar src_24M[] =
        "1 \n"
        "1 \n"
        " 1\n"
        " 1";
    static const gchar src_24m[] =
        " 1\n"
        " 1\n"
        "1 \n"
        "1 ";
    static const gchar src_43M[] =
        "1   \n"
        " 11 \n"
        "   1";
    static const gchar src_43m[] =
        "   1\n"
        " 11 \n"
        "1   ";
    static const gchar src_34M[] =
        "1  \n"
        " 1 \n"
        " 1 \n"
        "  1";
    static const gchar src_34m[] =
        "  1\n"
        " 1 \n"
        " 1 \n"
        "1  ";
    static const gchar src_53M[] =
        "11   \n"
        "  1  \n"
        "   11";
    static const gchar src_53m[] =
        "   11\n"
        "  1  \n"
        "11   ";
    static const gchar src_35M[] =
        "1  \n"
        "1  \n"
        " 1 \n"
        "  1\n"
        "  1";
    static const gchar src_35m[] =
        "  1\n"
        "  1\n"
        " 1 \n"
        "1  \n"
        "1  ";
    static const gchar src_62M[] =
        "111   \n"
        "   111";
    static const gchar src_62m[] =
        "   111\n"
        "111   ";
    static const gchar src_26M[] =
        "1 \n"
        "1 \n"
        "1 \n"
        " 1\n"
        " 1\n"
        " 1";
    static const gchar src_26m[] =
        " 1\n"
        " 1\n"
        " 1\n"
        "1 \n"
        "1 \n"
        "1 ";
    static const gchar src_65M[] =
        "1     \n"
        " 1    \n"
        "  11  \n"
        "    1 \n"
        "     1";
    static const gchar src_65m[] =
        "     1\n"
        "    1 \n"
        "  11  \n"
        " 1    \n"
        "1     ";
    static const gchar src_56M[] =
        "1    \n"
        " 1   \n"
        "  1  \n"
        "  1  \n"
        "   1 \n"
        "    1";
    static const gchar src_56m[] =
        "    1\n"
        "   1 \n"
        "  1  \n"
        "  1  \n"
        " 1   \n"
        "1    ";
    static const gchar *lines[] = {
        src_11,
        src_12, src_21,
        src_13, src_31,
        src_22M, src_22m,
        src_33M, src_33m,
        src_42M, src_42m, src_24M, src_24m,
        src_43M, src_43m, src_34M, src_34m,
        src_53M, src_53m, src_35M, src_35m,
        src_62M, src_62m, src_26M, src_26m,
        src_65M, src_65m, src_56M, src_56m,
    };

    return parse_nield(lines[g_test_rand_int_range(0, G_N_ELEMENTS(lines))]);
}

static gint
gcd(gint a, gint b)
{
    a = ABS(a);
    b = ABS(b);
    GWY_ORDER(gint, b, a);

    /* This also handles that gcd(x, 0) = x, by definition. */
    while (b) {
        a %= b;
        GWY_SWAP(gint, a, b);
    }

    return a;
}

// Check the ambiguous case (2N + 1) × (2M), where N ≥ M, and integer multiples of (repeating a smaller pattern
// line this multiple times in the line).
//
// XXX: This is somewhat too lenient, accepting 1/3 of all lines as crooked. In fact, even integer multiples can
// always be drawn symmetrically by splitting the line to two halfs and drawing them symmetrically (from the centre
// outwards or from the enpoints inwards). So they are ambiguous (as many other lines are), but not necessarily
// crooked. Inherently crooked are only 1/4 of all lines (with longer side odd). But we do not do this extra work…
//
// We do not consider breaking the symmetry a fail here because the line can be drawn eight different ways and some
// will always be different. It is an aesthetic choice which ones.
static gboolean
line8_is_crooked(gint col1, gint row1, gint col2, gint row2)
{
    gint w = ABS(col2 - col1) + 1, h = ABS(row2 - row1) + 1;
    gint g = gcd(w, h);
    gint M = MAX(w, h)/g, m = MIN(w, h)/g;
    return M % 2 == 1 && m % 2 == 0;
}

static gboolean
line4_is_crooked(gint col1, gint row1, gint col2, gint row2)
{
    gint w = ABS(col2 - col1) + 1, h = ABS(row2 - row1) + 1;
    gint M = MAX(w, h), m = MIN(w, h);
    return m == M || (M % 2 == 0 && m % 2 == 0);
}

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

    for (guint i = 0; i < n; i++) {
        GwyNield *shape = make_small_line8();
        gint width = gwy_nield_get_xres(shape), height = gwy_nield_get_yres(shape);
        gint diagtype = line_diag_type(gwy_nield_get_val(shape, 0, 0),
                                       gwy_nield_get_val(shape, width-1, 0),
                                       gwy_nield_get_val(shape, 0, height-1),
                                       gwy_nield_get_val(shape, width-1, height-1));
        gint xres = g_test_rand_int_range(width, width+20), yres = g_test_rand_int_range(height, height+20);
        gint col1 = g_test_rand_int_range(0, xres-width+1), row1 = g_test_rand_int_range(0, yres-height+1);
        gint col2 = col1 + width-1, row2 = row1 + height-1;
        if (diagtype == -1)
            GWY_SWAP(gint, col1, col2);
        else if (!diagtype) {
            if (g_test_rand_int() % 2)
                GWY_SWAP(gint, col1, col2);
            if (g_test_rand_int() % 2)
                GWY_SWAP(gint, row1, row2);
        }
        if (g_test_rand_int() % 2) {
            GWY_SWAP(gint, col1, col2);
            GWY_SWAP(gint, row1, row2);
        }
        GwyNield *nield = gwy_nield_new(xres, yres);
        // Check that drawing a line with the same endpoints gives the expected shape.
        gsize npix = gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, FALSE, 1);
        g_assert_cmpuint(npix, ==, gwy_nield_count(shape));
        gsize ntotal = gwy_nield_count(nield);
        g_assert_cmpuint(ntotal, ==, npix);
        GwyNield *extracted = gwy_nield_area_extract(nield,
                                                     MIN(col1, col2), MIN(row1, row2),
                                                     ABS(col1 - col2) + 1, ABS(row1 - row2) + 1);
        nield_assert_equal(G_OBJECT(extracted), G_OBJECT(shape));
        g_assert_finalize_object(extracted);
        g_assert_finalize_object(nield);
        g_assert_finalize_object(shape);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 22), yres = g_test_rand_int_range(1, 22);
        gint col1 = 0, row1 = 0, col2 = xres-1, row2 = yres-1;
        if (g_test_rand_int() % 2)
            GWY_SWAP(gint, col1, col2);
        if (g_test_rand_int() % 2)
            GWY_SWAP(gint, row1, row2);
        GwyNield *nield = gwy_nield_new(xres, yres);
        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, FALSE, 1);
        guint ngrains = gwy_nield_number_contiguous(nield);
        g_assert_cmpuint(ngrains, ==, MIN(xres, yres));
        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 30), yres = g_test_rand_int_range(1, 30);
        gint col1 = g_test_rand_int_range(0, xres), row1 = g_test_rand_int_range(0, yres);
        gint col2 = g_test_rand_int_range(0, xres), row2 = g_test_rand_int_range(0, yres);
        if (line8_is_crooked(col1, row1, col2, row2))
            continue;
        GwyNield *nield = gwy_nield_new(xres, yres);
        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, FALSE, 1);
        // Draw the line into a tranposed rectangle and verify it is the same as transposing the line.
        GwyNield *swapped = gwy_nield_new(yres, xres);
        gwy_nield_linear_area_fill(swapped, row1, col1, row2, col2, FALSE, 1);
        GwyNield *transposed = gwy_nield_new(1, 1);
        gwy_nield_transpose(nield, transposed, FALSE);
        nield_assert_equal(G_OBJECT(swapped), G_OBJECT(transposed));
        g_assert_finalize_object(swapped);
        g_assert_finalize_object(transposed);
        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 30), yres = g_test_rand_int_range(1, 30);
        gint col1 = g_test_rand_int_range(0, xres), row1 = g_test_rand_int_range(0, yres);
        gint col2 = g_test_rand_int_range(0, xres), row2 = g_test_rand_int_range(0, yres);
        if (line8_is_crooked(col1, row1, col2, row2))
            continue;
        GwyNield *nield = gwy_nield_new(xres, yres), *backward= gwy_nield_new(xres, yres);
        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, FALSE, 1);
        gwy_nield_linear_area_fill(backward, col2, row2, col1, row1, FALSE, 1);
        nield_assert_equal(G_OBJECT(backward), G_OBJECT(nield));
        g_assert_finalize_object(backward);
        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 30), yres = g_test_rand_int_range(1, 30);
        gint col1 = g_test_rand_int_range(-5, xres+5), row1 = g_test_rand_int_range(-5, yres+5);
        gint col2 = g_test_rand_int_range(-5, xres+5), row2 = g_test_rand_int_range(-5, yres+5);
        GwyNield *nield = gwy_nield_new(xres, yres);
        // Draw whatever wherever. This also checks we do not crash.
        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, FALSE, 1);
        gint top = MIN(0, MIN(row1, row2)), bot = MAX(yres, MAX(row1, row2) + 1);
        gint left = MIN(0, MIN(col1, col2)), right = MAX(xres, MAX(col1, col2) + 1);

        // Check the result is the same as drawing the full shape and cropping. If there is any intersection at all.
        // Otherwise just check we did not draw anything.
        if (MIN(col1, col2) >= xres || MAX(col1, col2) < 0
            || MIN(row1, row2) >= yres || MAX(row1, row2) < 0) {
            gwy_nield_invalidate(nield);
            g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        }
        else {
            GwyNield *bignield = gwy_nield_new(right-left, bot-top);
            gwy_nield_linear_area_fill(bignield, col1-left, row1-top, col2-left, row2-top, FALSE, 1);
            GwyNield *extracted = gwy_nield_area_extract(bignield, -left, -top, xres, yres);
            nield_assert_equal(G_OBJECT(nield), G_OBJECT(extracted));
            g_assert_finalize_object(extracted);
            g_assert_finalize_object(bignield);
        }

        g_assert_finalize_object(nield);
    }
}

// When we trust fill() has been tested thoroughly, we can use it to check that extract() and insert() work with the
// same set of pixels.
void
test_shaped_linear8_nield_extract_data(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 30), yres = g_test_rand_int_range(1, 30);
        gint col1 = g_test_rand_int_range(-5, xres+5), row1 = g_test_rand_int_range(-5, yres+5);
        gint col2 = g_test_rand_int_range(-5, xres+5), row2 = g_test_rand_int_range(-5, yres+5);
        GwyNield *nield = gwy_nield_new(xres, yres);

        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, FALSE, 12345);
        gsize nmax = gwy_linear_area_size(col1, row1, col2, row2, FALSE);
        gint *data = g_new(gint, 2*nmax + 100);
        gsize nextracted = gwy_nield_linear_area_extract(nield, col1, row1, col2, row2, FALSE, data, NULL, NULL);
        g_assert_cmpuint(nextracted, <=, nmax);
        for (gsize k = 0; k < nextracted; k++)
            g_assert_cmpint(data[k], ==, 12345);
        gwy_clear(data, nextracted);
        gwy_nield_linear_area_insert(nield, col1, row1, col2, row2, FALSE, data);
        g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        g_free(data);

        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 30), yres = g_test_rand_int_range(1, 30);
        gint col1 = g_test_rand_int_range(-5, xres+5), row1 = g_test_rand_int_range(-5, yres+5);
        gint col2 = g_test_rand_int_range(-5, xres+5), row2 = g_test_rand_int_range(-5, yres+5);
        GwyNield *nield = gwy_nield_new(xres, yres);

        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, FALSE, 12345);
        gsize nmax = gwy_linear_area_size(col1, row1, col2, row2, FALSE);
        gint *jpos = g_new(gint, 2*nmax + 100), *ipos = g_new(gint, 2*nmax + 100);
        gsize nextracted = gwy_nield_linear_area_extract(nield, col1, row1, col2, row2, FALSE, NULL, jpos, ipos);
        g_assert_cmpuint(nextracted, <=, nmax);
        gint *data = gwy_nield_get_data(nield);
        for (gsize k = 0; k < nextracted; k++) {
            // The positions are relative to the first point. So they must come from the interval
            // [0, col2-col1]×[0, row2-row1], except col2-col1 and row2-row1 can have any signs, so the ranges can be
            // backwards.
            if (col2 < col1) {
                g_assert_cmpint(jpos[k], >=, col2-col1);
                g_assert_cmpint(jpos[k], <=, 0);
            }
            else {
                g_assert_cmpint(jpos[k], <=, col2-col1);
                g_assert_cmpint(jpos[k], >=, 0);
            }
            if (row2 < row1) {
                g_assert_cmpint(ipos[k], >=, row2-row1);
                g_assert_cmpint(ipos[k], <=, 0);
            }
            else {
                g_assert_cmpint(ipos[k], <=, row2-row1);
                g_assert_cmpint(ipos[k], >=, 0);
            }
            // The pixels must also lie inside the field.
            g_assert_cmpint(jpos[k] + col1, >=, 0);
            g_assert_cmpint(ipos[k] + row1, >=, 0);
            g_assert_cmpint(jpos[k] + col1, <, xres);
            g_assert_cmpint(ipos[k] + row1, <, yres);

            gint pos = (ipos[k] + row1)*xres + jpos[k] + col1;
            g_assert_cmpint(data[pos], ==, 12345);
            data[pos] = 0;
        }
        g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        g_free(jpos);
        g_free(ipos);

        g_assert_finalize_object(nield);
    }
}

static GwyNield*
make_small_line4(void)
{
    static const gchar src_11[] =
        "1";
    static const gchar src_12[] =
        "1\n"
        "1";
    static const gchar src_13[] =
        "1\n"
        "1\n"
        "1";
    static const gchar src_21[] =
        "11";
    static const gchar src_31[] =
        "111";
    static const gchar src_32M[] =
        "11 \n"
        " 11";
    static const gchar src_32m[] =
        " 11\n"
        "11 ";
    static const gchar src_23M[] =
        "1 \n"
        "11\n"
        " 1";
    static const gchar src_23m[] =
        " 1\n"
        "11\n"
        "1 ";
    static const gchar src_43M[] =
        "11  \n"
        " 11 \n"
        "  11";
    static const gchar src_43m[] =
        "  11\n"
        " 11 \n"
        "11  ";
    static const gchar src_34M[] =
        "1  \n"
        "11 \n"
        " 11\n"
        "  1";
    static const gchar src_34m[] =
        "  1\n"
        " 11\n"
        "11 \n"
        "1  ";
    static const gchar src_53M[] =
        "11   \n"
        " 111 \n"
        "   11";
    static const gchar src_53m[] =
        "   11\n"
        " 111 \n"
        "11   ";
    static const gchar src_35M[] =
        "1  \n"
        "11 \n"
        " 1 \n"
        " 11\n"
        "  1";
    static const gchar src_35m[] =
        "  1\n"
        " 11\n"
        " 1 \n"
        "11 \n"
        "1  ";
    static const gchar src_54M[] =
        "11   \n"
        " 11  \n"
        "  11 \n"
        "   11";
    static const gchar src_54m[] =
        "   11\n"
        "  11 \n"
        " 11  \n"
        "11   ";
    static const gchar src_45M[] =
        "1   \n"
        "11  \n"
        " 11 \n"
        "  11\n"
        "   1";
    static const gchar src_45m[] =
        "   1\n"
        "  11\n"
        " 11 \n"
        "11  \n"
        "1   ";
    static const gchar src_72M[] =
        "1111   \n"
        "   1111";
    static const gchar src_72m[] =
        "   1111\n"
        "1111   ";
    static const gchar src_27M[] =
        "1 \n"
        "1 \n"
        "1 \n"
        "11\n"
        " 1\n"
        " 1\n"
        " 1";
    static const gchar src_27m[] =
        " 1\n"
        " 1\n"
        " 1\n"
        "11\n"
        "1 \n"
        "1 \n"
        "1 ";
    static const gchar src_65M[] =
        "11    \n"
        " 11   \n"
        "  11  \n"
        "   11 \n"
        "    11";
    static const gchar src_65m[] =
        "    11\n"
        "   11 \n"
        "  11  \n"
        " 11   \n"
        "11    ";
    static const gchar src_56M[] =
        "1    \n"
        "11   \n"
        " 11  \n"
        "  11 \n"
        "   11\n"
        "    1";
    static const gchar src_56m[] =
        "    1\n"
        "   11\n"
        "  11 \n"
        " 11  \n"
        "11   \n"
        "1    ";
    static const gchar *lines[] = {
        src_11,
        src_12, src_13,
        src_21, src_31,
        src_32M, src_32m, src_23M, src_23m,
        src_43M, src_43m, src_34M, src_34m,
        src_53M, src_53m, src_35M, src_35m,
        src_54M, src_54m, src_45M, src_45m,
        src_72M, src_72m, src_27M, src_27m,
        src_65M, src_65m, src_56M, src_56m,
    };

    return parse_nield(lines[g_test_rand_int_range(0, G_N_ELEMENTS(lines))]);
}

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

    for (guint i = 0; i < n; i++) {
        GwyNield *shape = make_small_line4();
        gint width = gwy_nield_get_xres(shape), height = gwy_nield_get_yres(shape);
        gint diagtype = line_diag_type(gwy_nield_get_val(shape, 0, 0),
                                       gwy_nield_get_val(shape, width-1, 0),
                                       gwy_nield_get_val(shape, 0, height-1),
                                       gwy_nield_get_val(shape, width-1, height-1));
        gint xres = g_test_rand_int_range(width, width+20), yres = g_test_rand_int_range(height, height+20);
        gint col1 = g_test_rand_int_range(0, xres-width+1), row1 = g_test_rand_int_range(0, yres-height+1);
        gint col2 = col1 + width-1, row2 = row1 + height-1;
        if (diagtype == -1)
            GWY_SWAP(gint, col1, col2);
        else if (!diagtype) {
            if (g_test_rand_int() % 2)
                GWY_SWAP(gint, col1, col2);
            if (g_test_rand_int() % 2)
                GWY_SWAP(gint, row1, row2);
        }
        if (g_test_rand_int() % 2) {
            GWY_SWAP(gint, col1, col2);
            GWY_SWAP(gint, row1, row2);
        }
        GwyNield *nield = gwy_nield_new(xres, yres);
        // Check that drawing a line with the same endpoints gives the expected shape.
        gsize npix = gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, TRUE, 1);
        g_assert_cmpuint(npix, ==, gwy_nield_count(shape));
        gsize ntotal = gwy_nield_count(nield);
        g_assert_cmpuint(ntotal, ==, npix);
        GwyNield *extracted = gwy_nield_area_extract(nield,
                                                     MIN(col1, col2), MIN(row1, row2),
                                                     ABS(col1 - col2) + 1, ABS(row1 - row2) + 1);
        nield_assert_equal(G_OBJECT(extracted), G_OBJECT(shape));
        g_assert_finalize_object(extracted);
        g_assert_finalize_object(nield);
        g_assert_finalize_object(shape);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 22), yres = g_test_rand_int_range(1, 22);
        gint col1 = 0, row1 = 0, col2 = xres-1, row2 = yres-1;
        if (g_test_rand_int() % 2)
            GWY_SWAP(gint, col1, col2);
        if (g_test_rand_int() % 2)
            GWY_SWAP(gint, row1, row2);
        GwyNield *nield = gwy_nield_new(xres, yres);
        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, TRUE, 1);
        guint ngrains = gwy_nield_number_contiguous(nield);
        g_assert_cmpuint(ngrains, ==, 1);
        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 30), yres = g_test_rand_int_range(1, 30);
        gint col1 = g_test_rand_int_range(-5, xres+5), row1 = g_test_rand_int_range(-5, yres+5);
        gint col2 = g_test_rand_int_range(-5, xres+5), row2 = g_test_rand_int_range(-5, yres+5);
        GwyNield *nield4 = gwy_nield_new(xres, yres);
        GwyNield *nield8 = gwy_nield_new(xres, yres);
        gwy_nield_linear_area_fill(nield8, col1, row1, col2, row2, FALSE, 1);
        gwy_nield_linear_area_fill(nield4, col1, row1, col2, row2, TRUE, 1);
        g_assert_cmpuint(gwy_nield_count(nield4), >=, gwy_nield_count(nield8));

        gwy_nield_area_fill(nield8, nield4, GWY_MASK_INCLUDE, 0, 0, xres, yres, 0);
        g_assert_cmpuint(gwy_nield_count(nield8), ==, 0);

        g_assert_finalize_object(nield4);
        g_assert_finalize_object(nield8);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 30), yres = g_test_rand_int_range(1, 30);
        gint col1 = g_test_rand_int_range(0, xres), row1 = g_test_rand_int_range(0, yres);
        gint col2 = g_test_rand_int_range(0, xres), row2 = g_test_rand_int_range(0, yres);
        if (line4_is_crooked(col1, row1, col2, row2))
            continue;
        GwyNield *nield = gwy_nield_new(xres, yres);
        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, TRUE, 1);
        // Draw the line into a tranposed rectangle and verify it is the same as transposing the line.
        GwyNield *swapped = gwy_nield_new(yres, xres);
        gwy_nield_linear_area_fill(swapped, row1, col1, row2, col2, TRUE, 1);
        GwyNield *transposed = gwy_nield_new(1, 1);
        gwy_nield_transpose(nield, transposed, FALSE);
        nield_assert_equal(G_OBJECT(swapped), G_OBJECT(transposed));
        g_assert_finalize_object(swapped);
        g_assert_finalize_object(transposed);
        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 30), yres = g_test_rand_int_range(1, 30);
        gint col1 = g_test_rand_int_range(0, xres), row1 = g_test_rand_int_range(0, yres);
        gint col2 = g_test_rand_int_range(0, xres), row2 = g_test_rand_int_range(0, yres);
        if (line4_is_crooked(col1, row1, col2, row2))
            continue;
        GwyNield *nield = gwy_nield_new(xres, yres), *backward= gwy_nield_new(xres, yres);
        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, TRUE, 1);
        gwy_nield_linear_area_fill(backward, col2, row2, col1, row1, TRUE, 1);
        nield_assert_equal(G_OBJECT(backward), G_OBJECT(nield));
        g_assert_finalize_object(backward);
        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 30), yres = g_test_rand_int_range(1, 30);
        gint col1 = g_test_rand_int_range(-5, xres+5), row1 = g_test_rand_int_range(-5, yres+5);
        gint col2 = g_test_rand_int_range(-5, xres+5), row2 = g_test_rand_int_range(-5, yres+5);
        GwyNield *nield = gwy_nield_new(xres, yres);
        // Draw whatever wherever. This also checks we do not crash.
        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, TRUE, 1);
        gint top = MIN(0, MIN(row1, row2)), bot = MAX(yres, MAX(row1, row2) + 1);
        gint left = MIN(0, MIN(col1, col2)), right = MAX(xres, MAX(col1, col2) + 1);

        // Check the result is the same as drawing the full shape and cropping. If there is any intersection at all.
        // Otherwise just check we did not draw anything.
        if (MIN(col1, col2) >= xres || MAX(col1, col2) < 0
            || MIN(row1, row2) >= yres || MAX(row1, row2) < 0) {
            gwy_nield_invalidate(nield);
            g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        }
        else {
            GwyNield *bignield = gwy_nield_new(right-left, bot-top);
            gwy_nield_linear_area_fill(bignield, col1-left, row1-top, col2-left, row2-top, TRUE, 1);
            GwyNield *extracted = gwy_nield_area_extract(bignield, -left, -top, xres, yres);
            nield_assert_equal(G_OBJECT(nield), G_OBJECT(extracted));
            g_assert_finalize_object(extracted);
            g_assert_finalize_object(bignield);
        }

        g_assert_finalize_object(nield);
    }
}

// When we trust fill() has been tested thoroughly, we can use it to check that extract() and insert() work with the
// same set of pixels.
void
test_shaped_linear4_nield_extract_data(void)
{
    guint n = g_test_slow() ? 1000 : 100;

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 30), yres = g_test_rand_int_range(1, 30);
        gint col1 = g_test_rand_int_range(-5, xres+5), row1 = g_test_rand_int_range(-5, yres+5);
        gint col2 = g_test_rand_int_range(-5, xres+5), row2 = g_test_rand_int_range(-5, yres+5);
        GwyNield *nield = gwy_nield_new(xres, yres);

        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, TRUE, 12345);
        gsize nmax = gwy_linear_area_size(col1, row1, col2, row2, TRUE);
        gint *data = g_new(gint, 2*nmax + 100);
        gsize nextracted = gwy_nield_linear_area_extract(nield, col1, row1, col2, row2, TRUE, data, NULL, NULL);
        g_assert_cmpuint(nextracted, <=, nmax);
        for (gsize k = 0; k < nextracted; k++)
            g_assert_cmpint(data[k], ==, 12345);
        gwy_clear(data, nextracted);
        gwy_nield_linear_area_insert(nield, col1, row1, col2, row2, TRUE, data);
        g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        g_free(data);

        g_assert_finalize_object(nield);
    }
}

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

    for (guint i = 0; i < n; i++) {
        gint xres = g_test_rand_int_range(1, 30), yres = g_test_rand_int_range(1, 30);
        gint col1 = g_test_rand_int_range(-5, xres+5), row1 = g_test_rand_int_range(-5, yres+5);
        gint col2 = g_test_rand_int_range(-5, xres+5), row2 = g_test_rand_int_range(-5, yres+5);
        GwyNield *nield = gwy_nield_new(xres, yres);

        gwy_nield_linear_area_fill(nield, col1, row1, col2, row2, TRUE, 12345);
        gsize nmax = gwy_linear_area_size(col1, row1, col2, row2, TRUE);
        gint *jpos = g_new(gint, 2*nmax + 100), *ipos = g_new(gint, 2*nmax + 100);
        gsize nextracted = gwy_nield_linear_area_extract(nield, col1, row1, col2, row2, TRUE, NULL, jpos, ipos);
        g_assert_cmpuint(nextracted, <=, nmax);
        gint *data = gwy_nield_get_data(nield);
        for (gsize k = 0; k < nextracted; k++) {
            // The positions are relative to the first point. So they must come from the interval
            // [0, col2-col1]×[0, row2-row1], except col2-col1 and row2-row1 can have any signs, so the ranges can be
            // backwards.
            if (col2 < col1) {
                g_assert_cmpint(jpos[k], >=, col2-col1);
                g_assert_cmpint(jpos[k], <=, 0);
            }
            else {
                g_assert_cmpint(jpos[k], <=, col2-col1);
                g_assert_cmpint(jpos[k], >=, 0);
            }
            if (row2 < row1) {
                g_assert_cmpint(ipos[k], >=, row2-row1);
                g_assert_cmpint(ipos[k], <=, 0);
            }
            else {
                g_assert_cmpint(ipos[k], <=, row2-row1);
                g_assert_cmpint(ipos[k], >=, 0);
            }
            // The pixels must also lie inside the field.
            g_assert_cmpint(jpos[k] + col1, >=, 0);
            g_assert_cmpint(ipos[k] + row1, >=, 0);
            g_assert_cmpint(jpos[k] + col1, <, xres);
            g_assert_cmpint(ipos[k] + row1, <, yres);

            gint pos = (ipos[k] + row1)*xres + jpos[k] + col1;
            g_assert_cmpint(data[pos], ==, 12345);
            data[pos] = 0;
        }
        g_assert_cmpuint(gwy_nield_count(nield), ==, 0);
        g_free(jpos);
        g_free(ipos);

        g_assert_finalize_object(nield);
    }
}

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