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

#include "config.h"
#include <string.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/math.h"
#include "libgwyddion/shaped.h"

#include "libgwyddion/internal.h"

typedef enum {
    GWY_SHAPED_FILL,
    GWY_SHAPED_EXTRACT,
    GWY_SHAPED_INSERT,
} GwyShapedOperation;

static void
fill_row_segment_positions(gint *rcols, gint *rrows, gint array_pos,
                           gint n, gint jshift, gint i)
{
    if (rcols) {
        rcols += array_pos;
        for (gint j = 0; j < n; j++)
            rcols[j] = j + jshift;
    }
    if (rrows) {
        rrows += array_pos;
        for (gint j = 0; j < n; j++)
            rrows[j] = i;
    }
}

static gint
elliptic_row_range(gint i, gint xres, gint col, gint row, gdouble a, gdouble b, gint *pjfrom)
{
    gdouble s = (i - row + 0.5)/b;
    s = s*(2.0 - s);
    if (s <= 0.0)
        return 0;
    s = sqrt(s);
    gint jfrom = (gint)ceil(a*(1.0 - s) - 0.5) + col;
    gint jto = (gint)floor(a*(1.0 + s) - 0.5) + col;
    if (jfrom >= xres || jto < 0)
        return 0;
    jfrom = MAX(jfrom, 0);
    jto = MIN(jto, xres-1);
    gint n = jto - jfrom + 1;
    g_return_val_if_fail(n >= 0, 0);
    *pjfrom = jfrom;
    return n;
}

static gsize
field_elliptic_area_operation(GwyField *field,
                              gint col, gint row,
                              gint width, gint height,
                              GwyShapedOperation op, gdouble value, gdouble *buffer,
                              gint *rcols, gint *rrows)
{
    g_return_val_if_fail(GWY_IS_FIELD(field), 0);
    if (!width || !height)                    /* Compatibility */
        return 0;

    gdouble a = width/2.0, b = height/2.0;
    gssize xres = field->xres, yres = field->yres;
    gsize count = 0;

    gint ifrom = MAX(0, row);
    gint ito = MIN(row + height-1, yres-1);
    for (gint i = ifrom; i <= ito; i++) {
        gint pos, n;
        if (!(n = elliptic_row_range(i, xres, col, row, a, b, &pos)))
            continue;

        gdouble *d = field->priv->data + i*xres + pos;
        if (op == GWY_SHAPED_FILL) {
            for (gint j = 0; j < n; j++)
                d[j] = value;
        }
        else if (op == GWY_SHAPED_EXTRACT) {
            if (buffer)
                gwy_assign(buffer + count, d, n);
            fill_row_segment_positions(rcols, rrows, count, n, pos - col, i - row);
        }
        else if (op == GWY_SHAPED_INSERT) {
            gwy_assign(d, buffer + count, n);
        }
        else {
            g_assert_not_reached();
        }
        count += n;
    }

    return count;
}

static gsize
nield_elliptic_area_operation(GwyNield *nield,
                              gint col, gint row,
                              gint width, gint height,
                              GwyShapedOperation op, gint value, gint *buffer,
                              gint *rcols, gint *rrows)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);
    if (!width || !height)                    /* Compatibility */
        return 0;

    gdouble a = width/2.0, b = height/2.0;
    gssize xres = nield->xres, yres = nield->yres;
    gsize count = 0;

    gint ifrom = MAX(0, row);
    gint ito = MIN(row + height-1, yres-1);
    for (gint i = ifrom; i <= ito; i++) {
        gint pos, n;
        if (!(n = elliptic_row_range(i, xres, col, row, a, b, &pos)))
            continue;

        gint *d = nield->priv->data + i*xres + pos;
        if (op == GWY_SHAPED_FILL) {
            for (gint j = 0; j < n; j++)
                d[j] = value;
        }
        else if (op == GWY_SHAPED_EXTRACT) {
            if (buffer)
                gwy_assign(buffer + count, d, n);
            fill_row_segment_positions(rcols, rrows, count, n, pos - col, i - row);
        }
        else if (op == GWY_SHAPED_INSERT) {
            gwy_assign(d, buffer + count, n);
        }
        else {
            g_assert_not_reached();
        }
        count += n;
    }

    return count;
}

/**
 * gwy_field_elliptic_area_fill:
 * @field: A data field.
 * @col: Upper-left bounding box column coordinate.
 * @row: Upper-left bounding box row coordinate.
 * @width: Bounding box width (number of columns).
 * @height: Bounding box height (number of rows).
 * @value: Value to be entered.
 *
 * Fills an elliptic region of a data field with given value.
 *
 * The elliptic region is defined by its bounding box.  The ellipse can intersect the data field in any manner.
 *
 * Returns: The number of filled values.
 **/
gsize
gwy_field_elliptic_area_fill(GwyField *field,
                             gint col, gint row,
                             gint width, gint height,
                             gdouble value)
{
    gsize count = field_elliptic_area_operation(field, col, row, width, height, GWY_SHAPED_FILL, value,
                                                NULL, NULL, NULL);
    if (count)
        gwy_field_invalidate(field);

    return count;
}

/**
 * gwy_field_elliptic_area_extract:
 * @field: A data field.
 * @col: Upper-left bounding box column coordinate.
 * @row: Upper-left bounding box row coordinate.
 * @width: Bounding box width (number of columns).
 * @height: Bounding box height (number of rows).
 * @data: Optional array to store the extracted values to.  Its size has to be sufficient to contain all the extracted
 *        values.  As a conservative estimate @width*@height can be used, or the size can be calculated with
 *        gwy_elliptic_area_size().
 * @rcols: Optional array to store relative column indices of values in @data to, the size requirements are the same
 *         as for @data.
 * @rrows: Optional array to store relative row indices of values in @data to, the size requirements are the same as
 *         for @data.
 *
 * Extracts values from an elliptic region of a data field.
 *
 * The elliptic region is defined by its bounding box.  The ellipse can intersect the data field in any manner.
 *
 * The row and column indices stored to @rrows and @rcols are relative to the area top left corner, i.e. to (@col,
 * @row). Note that the convention is different than for circular areas.
 *
 * All three output arrays are optional. However, if you simply want a quick upper bound for their size, use
 * gwy_elliptic_area_size() instead of passing all three as %NULL.
 *
 * Returns: The number of extracted values.
 **/
gsize
gwy_field_elliptic_area_extract(GwyField *field,
                                gint col, gint row,
                                gint width, gint height,
                                gdouble *data,
                                gint *rcols, gint *rrows)
{
    return field_elliptic_area_operation(field, col, row, width, height, GWY_SHAPED_EXTRACT, 0.0, data, rcols, rrows);
}

/**
 * gwy_field_elliptic_area_insert:
 * @field: A data field.
 * @col: Upper-left bounding box column coordinate.
 * @row: Upper-left bounding box row coordinate.
 * @width: Bounding box width (number of columns).
 * @height: Bounding box height (number of rows).
 * @data: The values to put back.  The array must correspond to gwy_field_elliptic_area_extract().
 *
 * Puts values back to an elliptic region of a data field.
 *
 * The elliptic region is defined by its bounding box.  The ellipse can intersect the data field in any manner.
 *
 * This method does the reverse of gwy_field_elliptic_area_extract() allowing to implement pixel-wise filters on
 * elliptic areas.  Values from @data are put back to the same positions gwy_field_elliptic_area_extract() took
 * them from.
 **/
void
gwy_field_elliptic_area_insert(GwyField *field,
                               gint col, gint row,
                               gint width, gint height,
                               const gdouble *data)
{
    g_return_if_fail(data);
    gsize count = field_elliptic_area_operation(field, col, row, width, height, GWY_SHAPED_INSERT, 0.0,
                                                (gdouble*)data, NULL, NULL);
    if (count)
        gwy_field_invalidate(field);
}

/**
 * gwy_elliptic_intersection_size:
 * @xres: Number of columns of the entire image.
 * @yres: Number of rows of the entire image.
 * @col: Upper-left bounding box column coordinate.
 * @row: Upper-left bounding box row coordinate.
 * @width: Bounding box width.
 * @height: Bounding box height.
 *
 * Calculates an upper bound of the number of samples in an elliptic region intersecting a data field.
 *
 * Returns: The number of pixels in an elliptic region with given rectangular bounds (or its upper bound).
 **/
gsize
gwy_elliptic_intersection_size(gint xres, gint yres,
                               gint col, gint row,
                               gint width, gint height)
{
    if (!width || !height)
        return 0;

    gdouble a = width/2.0, b = height/2.0;
    gint ifrom = MAX(0, row);
    gint ito = MIN(row + height-1, yres-1);
    gsize count = 0;
    for (gint i = ifrom; i <= ito; i++) {
        gint pos;
        count += elliptic_row_range(i, xres, col, row, a, b, &pos);
    }

    return count;
}

/**
 * gwy_nield_elliptic_area_fill:
 * @nield: A number field.
 * @col: Upper-left bounding box column coordinate.
 * @row: Upper-left bounding box row coordinate.
 * @width: Bounding box width (number of columns).
 * @height: Bounding box height (number of rows).
 * @value: Value to be entered.
 *
 * Fills an elliptic region of a number field with given value.
 *
 * The elliptic region is defined by its bounding box.  The ellipse can intersect the number field in any manner.
 *
 * Returns: The number of filled values.
 **/
gsize
gwy_nield_elliptic_area_fill(GwyNield *nield,
                             gint col, gint row,
                             gint width, gint height,
                             gint value)
{
    gsize count = nield_elliptic_area_operation(nield, col, row, width, height, GWY_SHAPED_FILL, value,
                                                NULL, NULL, NULL);
    if (count)
        gwy_nield_invalidate(nield);

    return count;
}

/**
 * gwy_nield_elliptic_fill:
 * @nield: A number field.
 *
 * Fills an maximum-sized elliptic region of a number field with one.
 *
 * This is a convenience wrapper for the common use case of gwy_nield_elliptic_area_fill(). Note that the area outside
 * of the ellipse is not cleared. Nevertheless, a newly created #GwyNield is alaways zero-filled.
 *
 * Returns: The number of filled values.
 **/
gsize
gwy_nield_elliptic_fill(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);
    return gwy_nield_elliptic_area_fill(nield, 0, 0, nield->xres, nield->yres, 1);
}

/**
 * gwy_nield_elliptic_area_extract:
 * @nield: A number field.
 * @col: Upper-left bounding box column coordinate.
 * @row: Upper-left bounding box row coordinate.
 * @width: Bounding box width (number of columns).
 * @height: Bounding box height (number of rows).
 * @data: Optional array to store the extracted values to.  Its size has to be sufficient to contain all the
 *        extracted values.  As a conservative estimate @width*@height can be used, or the size can be calculated with
 *        gwy_nield_get_elliptic_area_size().
 * @rcols: Optional array to store relative column indices of values in @data to, the size requirements are the same
 *         as for @data.
 * @rrows: Optional array to store relative row indices of values in @data to, the size requirements are the same as
 *         for @data.
 *
 * Extracts values from an elliptic region of a number field.
 *
 * The elliptic region is defined by its bounding box.  The ellipse can intersect the number field in any manner.
 *
 * All three output arrays are optional. However, if you simply want a quick upper bound for their size, use
 * gwy_elliptic_area_size() instead of passing all three as %NULL.
 *
 * Returns: The number of extracted values.
 **/
gsize
gwy_nield_elliptic_area_extract(GwyNield *nield,
                                gint col, gint row,
                                gint width, gint height,
                                gint *data,
                                gint *rcols, gint *rrows)
{
    return nield_elliptic_area_operation(nield, col, row, width, height, GWY_SHAPED_EXTRACT, 0.0, data, rcols, rrows);
}

/**
 * gwy_nield_elliptic_area_insert:
 * @nield: A number field.
 * @col: Upper-left bounding box column coordinate.
 * @row: Upper-left bounding box row coordinate.
 * @width: Bounding box width (number of columns).
 * @height: Bounding box height (number of rows).
 * @data: The values to put back.  The array must correspond to gwy_nield_elliptic_area_extract().
 *
 * Puts values back to an elliptic region of a number field.
 *
 * The elliptic region is defined by its bounding box.  The ellipse can intersect the number field in any manner.
 *
 * This method does the reverse of gwy_nield_elliptic_area_extract() allowing to implement pixel-wise filters on
 * elliptic areas.  Values from @data are put back to the same positions gwy_nield_elliptic_area_extract() took
 * them from.
 **/
void
gwy_nield_elliptic_area_insert(GwyNield *nield,
                               gint col, gint row,
                               gint width, gint height,
                               const gint *data)
{
    g_return_if_fail(data);
    gsize count = nield_elliptic_area_operation(nield, col, row, width, height, GWY_SHAPED_INSERT, 0.0,
                                                (gint*)data, NULL, NULL);
    if (count)
        gwy_nield_invalidate(nield);
}

/**
 * gwy_elliptic_area_size:
 * @width: Bounding box width.
 * @height: Bounding box height.
 *
 * Calculates an upper bound of the number of samples in an elliptic region.
 *
 * This function is useful for elliptic areas more or less contained within the data field.  Otherwise the returned
 * size can be overestimated a lot. Use gwy_elliptic_intersection_size() for elliptic areas intersecting the
 * data field in arbitrary manner.
 *
 * Returns: The number of pixels in an elliptic region with given rectangular bounds (or its upper bound).
 **/
gsize
gwy_elliptic_area_size(gint width, gint height)
{
    if (width <= 0 || height <= 0)
        return 0;

    gdouble a = width/2.0;
    gdouble b = height/2.0;
    gsize count = 0;

    for (gint i = 0; i < height; i++) {
        gdouble s = (i + 0.5)/b;
        s = s*(2.0 - s);
        if (s <= 0)
            continue;
        s = sqrt(s);
        gint from = ceil(a*(1.0 - s) - 0.5);
        gint to = floor(a*(1.0 + s) - 0.5);
        from = MAX(from, 0);
        to = MIN(to, width-1);
        count += MAX(to - from + 1, 0);
    }

    return count;
}

static gboolean
circular_vprepare(gdouble radius, gint yres, gint row,
                  gdouble *pr2, gint *ifrom, gint *ito)
{
    if (radius < 0.0)
        return FALSE;

    gsize r2 = (gsize)floor(radius*radius + 1e-12);
    gint r = (gint)floor(radius + 1e-12);
    *pr2 = r2;

    /* Clip */
    *ifrom = MAX(row - r, 0) - row;
    *ito = MIN(row + r, yres-1) - row;
    return TRUE;
}

static gint
circular_row_range(gint i, gint xres, gint col, gdouble r2, gint *pjfrom)
{
    gdouble s = sqrt(r2 - i*i);
    gint jfrom = ceil(-s);
    gint jto = floor(s);
    if (jfrom + col < 0)
        jfrom = -col;
    if (jto + col >= xres)
        jto = xres-1 - col;
    if (jfrom > jto)
        return 0;
    *pjfrom = jfrom;
    return jto+1 - jfrom;
}

/**
 * gwy_field_circular_area_fill:
 * @field: A data field.
 * @col: Row index of circular area centre.
 * @row: Column index of circular area centre.
 * @radius: Circular area radius (in pixels).  Any value is allowed, although to get areas that do not deviate from
 *          true circles after pixelization too much, half-integer values are recommended, integer values are NOT
 *          recommended.
 * @value: Value to be entered.
 *
 * Fills a circular region of a data field with given value.
 *
 * Returns: The number of filled values.
 **/
gsize
gwy_field_circular_area_fill(GwyField *field,
                             gint col, gint row,
                             gdouble radius,
                             gdouble value)
{
    g_return_val_if_fail(GWY_IS_FIELD(field), 0);

    gdouble r2;
    gint ifrom, ito;
    if (!circular_vprepare(radius, field->yres, row, &r2, &ifrom, &ito))
        return 0;

    gssize xres = field->xres;
    gdouble *d = field->priv->data;
    gsize count = 0;
    for (gint i = ifrom; i <= ito; i++) {
        gint pos, n;
        if (!(n = circular_row_range(i, xres, col, r2, &pos)))
            continue;

        // NB: Indices like i can be negative.
        gdouble *drow = d + (row + i)*xres + col + pos;
        for (gint j = 0; j < n; j++)
            drow[j] = value;
        count += n;
    }

    if (count)
        gwy_field_invalidate(field);

    return count;
}

/**
 * gwy_field_circular_area_extract:
 * @field: A data field.
 * @col: Row index of circular area centre.
 * @row: Column index of circular area centre.
 * @radius: Circular area radius (in pixels).
 * @data: Optional array to store the extracted values to.  Its size has to be sufficient to contain all the extracted
 *        values.  As a conservative estimate (2*floor(@radius)+1)^2 can be used, or the size can be calculated with
 *        gwy_circular_area_size().
 * @rcols: Optional array to store relative column indices of values in @data to, the size requirements are the same
 *         as for @data.
 * @rrows: Optional array to store relative row indices of values in @data to, the size requirements are the same as
 *         for @data.
 *
 * Extracts values with positions from a circular region of a data field.
 *
 * The row and column indices stored to @rrows and @rcols are relative to the area centre, i.e. to (@col, @row).  The
 * central pixel will therefore have 0 at the corresponding position in both @rrows and @rcols. Note that the
 * convention is different than for elliptic areas.
 *
 * All three output arrays are optional. However, if you simply want a quick upper bound for their size, use
 * gwy_circular_area_size() instead of passing all three as %NULL.
 *
 * Returns: The number of extracted values.  It can be zero when the inside of the circle does not intersect with the
 *          data field.
 **/
gsize
gwy_field_circular_area_extract(GwyField *field,
                                gint col, gint row,
                                gdouble radius,
                                gdouble *data,
                                gint *rcols, gint *rrows)
{
    g_return_val_if_fail(GWY_IS_FIELD(field), 0);

    gdouble r2;
    gint ifrom, ito;
    if (!circular_vprepare(radius, field->yres, row, &r2, &ifrom, &ito))
        return 0;

    gssize xres = field->xres;
    const gdouble *d = field->priv->data;
    gsize count = 0;
    for (gint i = ifrom; i <= ito; i++) {
        gint pos, n;
        if ((n = circular_row_range(i, xres, col, r2, &pos))) {
            if (data)
                gwy_assign(data + count, d + (row + i)*xres + col + pos, n);
            fill_row_segment_positions(rcols, rrows, count, n, pos, i);
            count += n;
        }
    }

    return count;
}

/**
 * gwy_field_circular_area_insert:
 * @field: A data field.
 * @col: Row index of circular area centre.
 * @row: Column index of circular area centre.
 * @radius: Circular area radius (in pixels).
 * @data: The values to put back.  The array must correspond to gwy_field_circular_area_insert().
 *
 * Puts values back to a circular region of a data field.
 *
 * This method does the reverse of gwy_field_circular_area_extract() allowing to implement pixel-wise filters on
 * circular areas.  Values from @data are put back to the same positions gwy_field_circular_area_extract() took
 * them from.
 **/
void
gwy_field_circular_area_insert(GwyField *field,
                               gint col, gint row,
                               gdouble radius,
                               const gdouble *data)
{
    g_return_if_fail(GWY_IS_FIELD(field));
    g_return_if_fail(data);

    gdouble r2;
    gint ifrom, ito;
    if (!circular_vprepare(radius, field->yres, row, &r2, &ifrom, &ito))
        return;

    gssize xres = field->xres;
    gdouble *d = field->priv->data;
    gsize count = 0;
    for (gint i = ifrom; i <= ito; i++) {
        gint pos, n;
        if ((n = circular_row_range(i, xres, col, r2, &pos))) {
            gwy_assign(d + (row + i)*xres + col + pos, data + count, n);
            count += n;
        }
    }

    if (count)
        gwy_field_invalidate(field);
}

/**
 * gwy_nield_circular_area_fill:
 * @nield: A number field.
 * @col: Row index of circular area centre.
 * @row: Column index of circular area centre.
 * @radius: Circular area radius (in pixels).  Any value is allowed, although to get areas that do not deviate from
 *          true circles after pixelization too much, half-integer values are recommended, integer values are NOT
 *          recommended.
 * @value: Value to be entered.
 *
 * Fills an circular region of a number field with given value.
 *
 * Returns: The number of filled values.
 **/
gsize
gwy_nield_circular_area_fill(GwyNield *nield,
                             gint col, gint row,
                             gdouble radius,
                             gint value)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);

    gdouble r2;
    gint ifrom, ito;
    if (!circular_vprepare(radius, nield->yres, row, &r2, &ifrom, &ito))
        return 0;

    gssize xres = nield->xres;
    gint *d = nield->priv->data;
    gsize count = 0;
    for (gint i = ifrom; i <= ito; i++) {
        gint pos, n;
        if ((n = circular_row_range(i, xres, col, r2, &pos))) {
            // NB: Indices like i can be negative.
            gint *drow = d + (row + i)*xres + col + pos;
            for (gint j = 0; j < n; j++)
                drow[j] = value;
            count += n;
        }
    }
    if (count)
        gwy_nield_invalidate(nield);

    return count;
}

/**
 * gwy_nield_circular_area_extract:
 * @nield: A number field.
 * @col: Row index of circular area centre.
 * @row: Column index of circular area centre.
 * @radius: Circular area radius (in pixels).
 * @data: Optional array to store the extracted values to.
 * @rcols: Optional array to store relative column indices of values in @data to, the size requirements are the same
 *         as for @data.
 * @rrows: Optional array to store relative row indices of values in @data to, the size requirements are the same as
 *         for @data.
 *
 * Extracts values from a circular region of a number field.
 *
 * The row and column indices stored to @rrows and @rcols are relative to the area centre, i.e. to (@col, @row).  The
 * central pixel will therefore have 0 at the corresponding position in both @rrows and @rcols. Note that the
 * convention is different than for elliptic areas.
 *
 * All three output arrays are optional. However, if you simply want a quick upper bound for their size, use
 * gwy_circular_area_size() instead of passing all three as %NULL.
 *
 * Returns: The number of extracted values.  It can be zero when the inside of the circle does not intersect with the
 *          number field.
 **/
gsize
gwy_nield_circular_area_extract(GwyNield *nield,
                                gint col, gint row,
                                gdouble radius,
                                gint *data,
                                gint *rcols, gint *rrows)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);

    gdouble r2;
    gint ifrom, ito;
    if (!circular_vprepare(radius, nield->yres, row, &r2, &ifrom, &ito))
        return 0;

    gssize xres = nield->xres;
    const gint *d = nield->priv->data;
    gsize count = 0;
    for (gint i = ifrom; i <= ito; i++) {
        gint pos, n;
        if ((n = circular_row_range(i, xres, col, r2, &pos))) {
            if (data)
                gwy_assign(data + count, d + (row + i)*xres + col + pos, n);
            fill_row_segment_positions(rcols, rrows, count, n, pos, i);
            count += n;
        }
    }

    return count;
}

/**
 * gwy_nield_circular_area_insert:
 * @nield: A number field.
 * @col: Row index of circular area centre.
 * @row: Column index of circular area centre.
 * @radius: Circular area radius (in pixels).
 * @data: The values to put back.  The array must correspond to gwy_nield_circular_area_insert().
 *
 * Puts values back to a circular region of a number field.
 *
 * This method does the reverse of gwy_nield_circular_area_extract() allowing to implement pixel-wise filters on
 * circular areas.  Values from @data are put back to the same positions gwy_nield_circular_area_extract() took
 * them from.
 **/
void
gwy_nield_circular_area_insert(GwyNield *nield,
                               gint col, gint row,
                               gdouble radius,
                               const gint *data)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    g_return_if_fail(data);

    gdouble r2;
    gint ifrom, ito;
    if (!circular_vprepare(radius, nield->yres, row, &r2, &ifrom, &ito))
        return;

    gssize xres = nield->xres;
    gint *d = nield->priv->data;
    gsize count = 0;
    for (gint i = ifrom; i <= ito; i++) {
        gint pos, n;
        if ((n = circular_row_range(i, xres, col, r2, &pos))) {
            gwy_assign(d + (row + i)*xres + col + pos, data + count, n);
            count += n;
        }
    }

    if (count)
        gwy_nield_invalidate(nield);
}

/**
 * gwy_circular_area_size:
 * @radius: Circular area radius (in pixels).
 *
 * Calculates an upper bound of the number of samples in a circular region.
 *
 * Returns: The number of pixels in a circular region with given rectangular bounds (or its upper bound).
 **/
gsize
gwy_circular_area_size(gdouble radius)
{
    if (radius < 0.0)
        return 0;

    gsize r2 = floor(radius*radius + 1e-12);
    gint r = floor(radius + 1e-12);
    gsize count = 2*ceil(sqrt(r2)) + 1;

    for (gint i = 1; i <= r; i++) {
        gdouble s = sqrt(r2 - i*i);
        gint jfrom = ceil(-s);
        gint jto = floor(s);
        count += 2*(jto - jfrom + 1);
    }

    return count;
}

/* We store absolute pixel positions here to avoid a zillion function arguments and make them relative later if
 * required. */
static inline void
nield_pixel_op(gint *field, gint xres, gint yres, gint col, gint row,
               gint *values,
               GwyShapedOperation op, gint *count,
               gint *cols, gint *rows)
{
    if (col < 0 || row < 0 || col >= xres || row >= yres)
        return;

    if (op == GWY_SHAPED_EXTRACT) {
        if (values)
            values[*count] = field[row*xres + col];
        if (cols)
            cols[*count] = col;
        if (rows)
            rows[*count] = row;
    }
    else if (op == GWY_SHAPED_INSERT)
        field[row*xres + col] = values[*count];
    else if (op == GWY_SHAPED_FILL)
        field[row*xres + col] = *values;
    else {
        g_assert_not_reached();
    }
    (*count)++;
}

static inline gint
sign(gint n)
{
    if (n > 0)
        return 1;
    if (n < 0)
        return -1;
    return 0;
}

static gsize
nield_linear_area_operation(GwyNield *nield,
                            gint col1, gint row1,
                            gint col2, gint row2,
                            const gboolean is_4_connected,
                            const GwyShapedOperation op,
                            gint *data,
                            gint *rcols, gint *rrows)
{
    gint *m = nield->priv->data;
    gint xres = nield->xres, yres = nield->yres;
    gboolean reversed = (col2 < col1);

    gint count = 0;
    if (col1 == col2 && row1 == row2) {
        // Handling 0×0 dimensions is messy later. Do it separately.
        nield_pixel_op(m, xres, yres, col1, row1, data, op, &count, rcols, rrows);
        goto finalise;
    }

    /* Do not try to shorten the line, just ignore any pixels which fall outside the image. This allows passing
     * endpoints outside the image with consistent results. But make width always positive to reduce the number of
     * branches (the height can take any sign). */
    gint col, row, width, height;
    if (reversed) {
        col = col2;
        row = row2;
        width = col1 - col2;
        height = row1 - row2;
    }
    else {
        col = col1;
        row = row1;
        width = col2 - col1;
        height = row2 - row1;
    }
    width += sign(width);
    height += sign(height);

    // The follow relies heavily on signed integer division rounding towards zero in C (which is usually rather
    // annoying).
    if (is_4_connected) {
        if (ABS(height) >= width) {
            gint q = width/2;
            if (height > 0) {
                for (gint i = 0; i < height; i++) {
                    nield_pixel_op(m, xres, yres, col + q/height, row + i, data, op, &count, rcols, rrows);
                    if (q/height != (q - width)/height) {
                        if (2*q >= 2*height*(q/height) + width)
                            nield_pixel_op(m, xres, yres, col + q/height, row + i-1, data, op, &count, rcols, rrows);
                        else
                            nield_pixel_op(m, xres, yres, col + q/height-1, row + i, data, op, &count, rcols, rrows);
                    }
                    q += width;
                }
            }
            else {
                height = -height;
                for (gint i = 0; i < height; i++) {
                    nield_pixel_op(m, xres, yres, col + q/height, row - i, data, op, &count, rcols, rrows);
                    if (q/height != (q - width)/height) {
                        if (2*q >= 2*height*(q/height) + width)
                            nield_pixel_op(m, xres, yres, col + q/height, row - i+1, data, op, &count, rcols, rrows);
                        else
                            nield_pixel_op(m, xres, yres, col + q/height-1, row - i, data, op, &count, rcols, rrows);
                    }
                    q += width;
                }
            }
        }
        else {
            gint q = ABS(height)/2;
            if (height >= 0) {
                for (gint j = 0; j < width; j++) {
                    nield_pixel_op(m, xres, yres, col + j, row + q/width, data, op, &count, rcols, rrows);
                    if (q/width != (q - height)/width) {
                        if (2*q >= 2*width*(q/width) + height)
                            nield_pixel_op(m, xres, yres, col + j-1, row + q/width, data, op, &count, rcols, rrows);
                        else
                            nield_pixel_op(m, xres, yres, col + j, row + q/width-1, data, op, &count, rcols, rrows);
                    }
                    q += height;
                }
            }
            else {
                height = -height;
                col += width-1;
                row -= height-1;
                for (gint j = 0; j < width; j++) {
                    nield_pixel_op(m, xres, yres, col - j, row + q/width, data, op, &count, rcols, rrows);
                    if (q/width != (q - height)/width) {
                        if (2*q >= 2*width*(q/width) + height)
                            nield_pixel_op(m, xres, yres, col - j+1, row + q/width, data, op, &count, rcols, rrows);
                        else
                            nield_pixel_op(m, xres, yres, col - j, row + q/width-1, data, op, &count, rcols, rrows);
                    }
                    q += height;
                }
            }
        }
    }
    else {
        if (ABS(height) >= width) {
            gint q = width/2;
            if (height > 0) {
                for (gint i = 0; i < height; i++) {
                    nield_pixel_op(m, xres, yres, col + q/height, row + i, data, op, &count, rcols, rrows);
                    q += width;
                }
            }
            else {
                height = -height;
                for (gint i = 0; i < height; i++) {
                    nield_pixel_op(m, xres, yres, col + q/height, row - i, data, op, &count, rcols, rrows);
                    q += width;
                }
            }
        }
        else {
            gint q = height/2;
            for (gint j = 0; j < width; j++) {
                nield_pixel_op(m, xres, yres, col + j, row + q/width, data, op, &count, rcols, rrows);
                q += height;
            }
        }
    }

finalise:
    /* Fix the positions to relative. */
    if (rcols) {
        for (gint k = 0; k < count; k++)
            rcols[k] -= col1;
    }
    if (rrows) {
        for (gint k = 0; k < count; k++)
            rrows[k] -= row1;
    }

    if (count && op != GWY_SHAPED_EXTRACT)
        gwy_nield_invalidate(nield);

    return count;
}

/**
 * gwy_nield_linear_area_fill:
 * @nield: A number field.
 * @col1: Column index of the first endpoint.
 * @row1: Row index of the first endpoint.
 * @col2: Column index of the second endpoint.
 * @row2: Row index of the second endpoint.
 * @connect4: %TRUE for a 4-connected line (it would be flood-fillable), %FALSE for an 8-connected line (it can connect
 *            only diagonally).
 * @value: Value to be entered.
 *
 * Fills a thin linear region of a number field with given value.
 *
 * The thin linear region is defined by its end points.  The line can intersect the number field in any manner.
 *
 * Returns: The number of filled values.
 **/
gsize
gwy_nield_linear_area_fill(GwyNield *nield,
                           gint col1,
                           gint row1,
                           gint col2,
                           gint row2,
                           gboolean connect4,
                           gint value)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);
    return nield_linear_area_operation(nield, col1, row1, col2, row2, connect4, GWY_SHAPED_FILL, &value, NULL, NULL);
}

/**
 * gwy_nield_linear_area_extract:
 * @nield: A number field.
 * @col1: Column index of the first endpoint.
 * @row1: Row index of the first endpoint.
 * @col2: Column index of the second endpoint.
 * @row2: Row index of the second endpoint.
 * @connect4: %TRUE for a 4-connected line (it would be flood-fillable), %FALSE for an 8-connected line (it can connect
 *            only diagonally).
 * @data: Optional array to store the extracted values to.  Its size has to be sufficient to contain all the extracted
 *        values.  As a conservative estimate width+height+1 can be used, or the size can be calculated with
 *        gwy_linear_area_size().
 * @rcols: Optional array to store relative column indices of values in @data to, the size requirements are the same
 *         as for @data.
 * @rrows: Optional array to store relative row indices of values in @data to, the size requirements are the same as
 *         for @data.
 *
 * Extracts values from a thin linear region of a number field.
 *
 * The thin linear region is defined by its end points.  The line can intersect the number field in any manner.
 *
 * The row and column indices stored to @rcols and @rrows are relative to the first endpoint, i.e. to (@col1, @row1),
 * i.e. the first endpoint corresponds to (0,0). The order in which line pixels appear in @rcols and @rrows is not
 * defined. They can appear starting from the second end point for example. It is only guaranteed that
 * gwy_nield_linear_area_insert() uses the same order.
 *
 * All three output arrays are optional. However, if you simply want a quick upper bound for their size, use
 * gwy_linear_area_size() instead of passing all three as %NULL.
 *
 * Returns: The number of extracted values.
 **/
gsize
gwy_nield_linear_area_extract(GwyNield *nield,
                              gint col1,
                              gint row1,
                              gint col2,
                              gint row2,
                              gboolean connect4,
                              gint *data,
                              gint *rcols,
                              gint *rrows)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);

    return nield_linear_area_operation(nield, col1, row1, col2, row2, connect4, GWY_SHAPED_EXTRACT,
                                       data, rcols, rrows);
}

/**
 * gwy_nield_linear_area_insert:
 * @nield: A number field.
 * @col1: Column index of the first endpoint.
 * @row1: Row index of the first endpoint.
 * @col2: Column index of the second endpoint.
 * @row2: Row index of the second endpoint.
 * @connect4: %TRUE for a 4-connected line (it would be flood-fillable), %FALSE for an 8-connected line (it can connect
 *            only diagonally).
 * @data: The values to put back.  The array must correspond to gwy_nield_linear_area_extract().
 *
 * Puts values back to an thin linear region of a number field.
 *
 * The thin linear region is defined by its end points.  The line can intersect the number field in any manner.
 *
 * This method does the reverse of gwy_nield_linear_area_extract() allowing to implement pixel-wise filters on
 * linear areas.  Values from @data are put back to the same positions gwy_nield_linear_area_extract() took
 * them from.
 **/
void
gwy_nield_linear_area_insert(GwyNield *nield,
                             gint col1,
                             gint row1,
                             gint col2,
                             gint row2,
                             gboolean connect4,
                             const gint *data)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    g_return_if_fail(data);

    nield_linear_area_operation(nield, col1, row1, col2, row2, connect4, GWY_SHAPED_INSERT, (gint*)data, NULL, NULL);
}

/**
 * gwy_linear_area_size:
 * @col1: Column index of the first endpoint.
 * @row1: Row index of the first endpoint.
 * @col2: Column index of the second endpoint.
 * @row2: Row index of the second endpoint.
 * @connect4: %TRUE for a 4-connected line (it would be flood-fillable), %FALSE for an 8-connected line (it can connect
 *            only diagonally).
 *
 * Calculates an upper bound of the number of samples in a thin line region.
 *
 * Returns: The number of pixels in a thin line region with given rectangular bounds (or its upper bound).
 **/
gsize
gwy_linear_area_size(gint col1,
                     gint row1,
                     gint col2,
                     gint row2,
                     gboolean connect4)
{
    gint width = col2 - col1, height = row2 - row1;
    if (connect4)
        return ABS(width) + ABS(height) + 1;
    return MAX(ABS(width), ABS(height)) + 1;
}

/* FIXME: Where does this really belong? */
/**
 * gwy_field_local_maximum:
 * @field: A two-dimensional data field.
 * @x: Approximate maximum @x-location to be improved (in pixels).
 * @y: Approximate maximum @y-location to be improved (in pixels).
 * @ax: Horizontal search radius.
 * @ay: Vertical search radius.
 *
 * Searches an elliptical area in a data field for local maximum.
 *
 * The area may stick outside the data field.
 *
 * The function first finds the maximum within the ellipse, intersected with the data field and then tries subpixel
 * refinement.  The maximum is considered successfully located if it is inside the data field, i.e. not on edge, there
 * is no higher value in its 8-neighbourhood, and the subpixel refinement of its position succeeds (which usually
 * happens when the first two conditions are met, but not always).
 *
 * Even if the function returns %FALSE the values of @x and @y are reasonable, but they may not correspond to an
 * actual maximum.
 *
 * The radii can be zero. A single pixel is then examined, but if it is indeed a local maximum, its position is
 * refined.
 *
 * The radii can also be negative. In such case a full-width or full-height strip is searched in the corresponding
 * direction, or the entire field if both are negative.
 *
 * Returns: %TRUE if the maximum was successfully located.  %FALSE when the location is problematic and should not be
 *          used.
 **/
gboolean
gwy_field_local_maximum(GwyField *field,
                        gdouble *x, gdouble *y,
                        gint ax, gint ay)
{
    g_return_val_if_fail(GWY_IS_FIELD(field), FALSE);
    g_return_val_if_fail(x, FALSE);
    g_return_val_if_fail(y, FALSE);

    gdouble z[9];
    gssize xres = field->xres, yres = field->yres;
    gint xj = (gint)(*x);
    gint yi = (gint)(*y);

    gwy_debug("searching around: %g, %g (%d+-%d, %d+-%d)", *x, *y, xj, ax, yi, ay);
    gint mi = 0, mj = 0;
    gdouble max = -G_MAXDOUBLE;

    gint yfrom = 0, yto = yres-1;
    if (ay >= 0) {
        yfrom = MAX(yi - ay, 0);
        yto = MIN(yi + ay, yres-1);
    }
    for (gint i = yfrom; i <= yto; i++) {
        gint xfrom = 0, xto = xres-1;
        if (ay >= 0 && ax >= 0) {
            gdouble v = (i - yi)/(ay + 0.5);
            gint k = (gint)floor((ax + 0.5)*sqrt(1.0 - v*v));
            xfrom = MAX(xj - k, 0);
            xto = MIN(xj + k, xres-1);
        }
        else if (ax >= 0) {
            xfrom = MAX(xj - ax, 0);
            xto = MIN(xj + ax, xres-1);
        }
        const gdouble *d = field->priv->data + i*xres;
        for (gint j = xfrom; j <= xto; j++) {
            if (d[j] > max) {
                max = d[j];
                mi = i;
                mj = j;
            }
        }
    }
    gwy_debug("pixel maximum at: %d, %d", mj, mi);

    /* No pixels found at all. */
    if (max == -G_MAXDOUBLE)
        return FALSE;

    /* Data field edge. */
    *x = mj;
    *y = mi;
    if (mi == 0 || mi == yres-1 || mj == 0 || mj == xres-1)
        return FALSE;

    const gdouble *d = field->priv->data;
    gint k = mi*xres + mj;
    for (gint i = -1; i <= 1; i++) {
        for (gint j = -1; j <= 1; j++) {
            gdouble v = d[k + i*xres + j];
            /* Not an actual maximum. */
            if ((i || j) && v > max)
                return FALSE;
            z[3*(i + 1) + (j + 1)] = v;
        }
    }

    gdouble xx, yy;
    gboolean ok = gwy_math_refine_maximum_2d(z, &xx, &yy);
    gwy_debug("refinement by (%g, %g)", xx, yy);
    if (!ok)
        return FALSE;

    *x += xx;
    *y += yy;
    return TRUE;
}

/**
 * SECTION: shaped
 * @title: Shaped areas
 * @short_description: Functions for non-rectangular regions of different shapes
 *
 * Methods for filling, extraction and insertion of data from/to shaped areas can be used as simple drawing primitives
 * or for preparation of round kernels. The can also help with implementation of sample-wise operations, that is
 * operations that depend only on sample value not on its position, on these areas:
 *
 * |[gdouble *data;
 * gint n, i;
 *
 * data = g_new(gdouble, width*height);
 * n = gwy_field_elliptic_area_extract(field, col, row, width, height, data, NULL, NULL);
 * for (i = 0; i < n; i++) {
 *    ... do something with data[i] ...
 * }
 * gwy_field_elliptic_area_insert(field, col, row, width, height, data);]|
 *
 * Another possibility is to use #GwyLine methods on the extracted data (in practice one would use the same data
 * line repeatedly, of course):
 *
 * |[GwyLine *line;
 * gdouble *data;
 * gint n;
 *
 * n = gwy_elliptic_area_size(width, height);
 * line = gwy_line_new(n, 1.0, FALSE);
 * data = gwy_line_get_data(line);
 * gwy_field_elliptic_area_extract(field, col, row, width, height, data, NULL, NULL);
 * gwy_line_pixelwise_filter(line, ...);
 * gwy_field_elliptic_area_insert(field, col, row, width, height, data);
 * g_object_unref(line);]|
 *
 * Although the circular area functions allow any real positive ratius, to get areas that do not deviate from true
 * circles after pixelization too much, half-integer values are recommended. Integer radii are NOT recommended.
 *
 * Be mindful that the relative row and column indices of extracted values, which can be returned by the data
 * extraction functions, have different meanings for different shapes since they are specified differently.
 * Furthemore, the order of values in data is not specified – it is always consistend between the extraction and
 * insertion though. You need always to use the extraction function first, even if you only care about the
 * coordinates, not the data, because it is the only way to guarantee the insertion function will put your data where
 * intended.
 **/

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