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

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

#include "libgwyddion/macros.h"
#include "libgwyddion/correct.h"
#include "libgwyddion/extend.h"

#include "libgwyddion/omp.h"
#include "libgwyddion/internal.h"

static inline void
fill_block(gdouble *data, guint len, gdouble value)
{
    while (len--)
        *(data++) = value;
}

static inline void
row_extend_base(const gdouble *in, gdouble *out,
                guint *pos, guint *width, guint res,
                guint *extend_left, guint *extend_right)
{
    guint e2r, e2l;

    // Expand the ROI to the right as far as possible
    e2r = MIN(*extend_right, res - (*pos + *width));
    *width += e2r;
    *extend_right -= e2r;

    // Expand the ROI to the left as far as possible
    e2l = MIN(*extend_left, *pos);
    *width += e2l;
    *extend_left -= e2l;
    *pos -= e2l;

    // Direct copy of the ROI
    gwy_assign(out + *extend_left, in + *pos, *width);
}

static void
row_extend_mirror(const gdouble *in, gdouble *out,
                  guint pos, guint width, guint res,
                  guint extend_left, guint extend_right,
                  G_GNUC_UNUSED gdouble value)
{
    guint res2 = 2*res, k0, j;
    gdouble *out2;
    row_extend_base(in, out, &pos, &width, res, &extend_left, &extend_right);
    // Forward-extend
    out2 = out + extend_left + width;
    for (j = 0; j < extend_right; j++, out2++) {
        guint k = (pos + width + j) % res2;
        *out2 = (k < res) ? in[k] : in[res2-1 - k];
    }
    // Backward-extend
    k0 = (extend_left/res2 + 1)*res2;
    out2 = out + extend_left-1;
    for (j = 1; j <= extend_left; j++, out2--) {
        guint k = (k0 + pos - j) % res2;
        *out2 = (k < res) ? in[k] : in[res2-1 - k];
    }
}

static void
row_extend_periodic(const gdouble *in, gdouble *out,
                    guint pos, guint width, guint res,
                    guint extend_left, guint extend_right,
                    G_GNUC_UNUSED gdouble value)
{
    guint k0, j;
    gdouble *out2;
    row_extend_base(in, out, &pos, &width, res, &extend_left, &extend_right);
    // Forward-extend
    out2 = out + extend_left + width;
    for (j = 0; j < extend_right; j++, out2++) {
        guint k = (pos + width + j) % res;
        *out2 = in[k];
    }
    // Backward-extend
    k0 = (extend_left/res + 1)*res;
    out2 = out + extend_left-1;
    for (j = 1; j <= extend_left; j++, out2--) {
        guint k = (k0 + pos - j) % res;
        *out2 = in[k];
    }
}

static void
row_extend_border(const gdouble *in, gdouble *out,
                  guint pos, guint width, guint res,
                  guint extend_left, guint extend_right,
                  G_GNUC_UNUSED gdouble value)
{
    row_extend_base(in, out, &pos, &width, res, &extend_left, &extend_right);
    // Forward-extend
    fill_block(out + extend_left + width, extend_right, in[res-1]);
    // Backward-extend
    fill_block(out, extend_left, in[0]);
}

static void
row_extend_fill(const gdouble *in, gdouble *out,
                guint pos, guint width, guint res,
                guint extend_left, guint extend_right,
                gdouble value)
{
    row_extend_base(in, out, &pos, &width, res, &extend_left, &extend_right);
    // Forward-extend
    fill_block(out + extend_left + width, extend_right, value);
    // Backward-extend
    fill_block(out, extend_left, value);
}

static void
row_extend_connect(const gdouble *in, gdouble *out,
                   guint pos, guint width, guint res,
                   guint extend_left, guint extend_right,
                   G_GNUC_UNUSED gdouble value)
{
    row_extend_base(in, out, &pos, &width, res, &extend_left, &extend_right);
    gint ext_total = extend_left + extend_right;
    if (!ext_total)
        return;

    gint start = extend_left;
    gint end = extend_left + width-1;

    /* Handle the stupid cases of extending by a couple of pixels separarely. */
    gdouble vleft = out[start], vright = out[end];
    gdouble mid = (out[start] + out[end])/2;
    gint next = width + ext_total;
    if (ext_total <= 2) {
        if (ext_total == 1)
            out[(next + extend_left-1) % next] = mid;
        else {
            out[(next + extend_left-2) % next] = 0.75*vright + 0.25*vleft;
            out[(next + extend_left-1) % next] = 0.75*vleft + 0.25*vright;
        }
        return;
    }

    gdouble der_left, der_right;
    if (width == 1)
        der_left = der_right = 0.0;
    else if (width == 2) {
        der_left = 0.6*(vleft - out[start+1]);
        der_right = 0.6*(vright - out[end-1]);
    }
    else {
        der_left = (2*vleft - out[start+1] - out[start+2])/3;
        der_right = (2*vright - out[end-1] - out[end-2])/3;
    }

    //g_printerr("der_left = %g, der_right = %g\n", der_left, der_right);

    enum { SMEAR = 6 };
    for (gint j = 0; j < ext_total; j++) {
        gdouble ywsum = 0.0, wsum = 0.0;
        gint m = ext_total-1 - j;

        /* Extend the derivatives. */
        if (j < SMEAR) {
            gdouble w = 2.0*(SMEAR - j)/SMEAR;
            gdouble y = vright + der_right*(j + 1);
            wsum += w;
            ywsum += w*y;
            //g_printerr("SMEAR-RIGHT val=%g, weight=%g\n", y, w);
        }
        if (m < SMEAR) {
            gdouble w = 2.0*(SMEAR - m)/SMEAR;
            gdouble y = vleft + der_left*(m + 1);
            wsum += w;
            ywsum += w*y;
            //g_printerr("SMEAR-LEFT val=%g, weight=%g\n", y, w);
        }

        /* Mirror the data. */
        if (j < width) {
            gdouble x = 1.0 - j/(ext_total - 1.0);
            gdouble w = x*x;
            gdouble y = out[end - j];
            wsum += w;
            ywsum += w*y;
            //g_printerr("MIRROR-RIGHT val=%g, weight=%g\n", y, w);
        }
        if (m < width) {
            gdouble x = 1.0 - m/(ext_total - 1.0);
            gdouble w = x*x;
            gdouble y = out[start + m];
            wsum += w;
            ywsum += w*y;
            //g_printerr("MIRROR-LEFT val=%g, weight=%g\n", y, w);
        }

        /* For long extensions, add a straight connection. Since we divide by width, not ext_total, the weight is
         * unbounded and ultimately takes over. */
        gdouble x = 2.0*MIN(j, m)/width;
        gdouble w = x*x;
        gdouble t = (j + 1.0)/(ext_total + 1.0);
        gdouble y = vleft*t + vright*(1.0 - t);
        //g_printerr("STRAIGHT t=%g, val=%g, weight=%g\n", t, y, w);
        wsum += w;
        ywsum += w*y;

        //g_printerr("extending[%u -> %u] = %g/%g\n\n", j, (j + end+1) % next, ywsum, wsum);
        out[(j + end+1) % next] = ywsum/wsum;
    }
}

static inline void
rect_extend_base(const gdouble *in, guint inrowstride,
                 gdouble *out, guint outrowstride,
                 guint xpos, guint *ypos,
                 guint width, guint *height,
                 guint xres, guint yres,
                 guint extend_left, guint extend_right,
                 guint *extend_up, guint *extend_down,
                 RowExtendFunc extend_row, gdouble fill_value)
{
    guint e2r, e2l, h;

    // Expand the ROI down as far as possible
    e2r = MIN(*extend_down, yres - (*ypos + *height));
    *height += e2r;
    *extend_down -= e2r;

    // Expand the ROI up as far as possible
    e2l = MIN(*extend_up, *ypos);
    *height += e2l;
    *extend_up -= e2l;
    *ypos -= e2l;

    // Row-wise extension within the vertical range of the ROI
    h = *height;
#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            shared(in,out,h,xpos,ypos,xres,width,inrowstride,outrowstride,extend_up,extend_left,extend_right,fill_value,extend_row)
#endif
    for (guint i = 0; i < h; i++) {
        extend_row(in + (*ypos + i)*inrowstride,
                   out + (*extend_up + i)*outrowstride,
                   xpos, width, xres, extend_left, extend_right, fill_value);
    }
}

static void
rect_extend_mirror(const gdouble *in, guint inrowstride,
                   gdouble *out, guint outrowstride,
                   guint xpos, guint ypos,
                   guint width, guint height,
                   guint xres, guint yres,
                   guint extend_left, guint extend_right,
                   guint extend_up, guint extend_down,
                   G_GNUC_UNUSED gdouble value)
{
    guint yres2, k0;
    gdouble *out2;
    rect_extend_base(in, inrowstride, out, outrowstride,
                     xpos, &ypos, width, &height, xres, yres,
                     extend_left, extend_right, &extend_up, &extend_down,
                     &row_extend_mirror, value);
    // Forward-extend
    yres2 = 2*yres;
    out2 = out + outrowstride*(extend_up + height);
#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            shared(in,out2,xres,yres,xpos,ypos,yres2,width,height,inrowstride,outrowstride,extend_left,extend_right,extend_down,value)
#endif
    for (guint i = 0; i < extend_down; i++) {
        guint k = (ypos + height + i) % yres2;
        if (k >= yres)
            k = yres2-1 - k;
        row_extend_mirror(in + k*inrowstride, out2 + i*outrowstride,
                          xpos, width, xres, extend_left, extend_right, value);
    }
    // Backward-extend
    k0 = (extend_up/yres2 + 1)*yres2;
    out2 = out + outrowstride*extend_up;
#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            shared(in,out2,xres,yres,xpos,ypos,yres2,width,height,inrowstride,outrowstride,extend_left,extend_right,extend_up,extend_down,k0,value)
#endif
    for (guint i = 1; i <= extend_up; i++) {
        guint k = (k0 + ypos - i) % yres2;
        if (k >= yres)
            k = yres2-1 - k;
        row_extend_mirror(in + k*inrowstride, out2 - i*outrowstride,
                          xpos, width, xres, extend_left, extend_right, value);
    }
}

static void
rect_extend_periodic(const gdouble *in, guint inrowstride,
                     gdouble *out, guint outrowstride,
                     guint xpos, guint ypos,
                     guint width, guint height,
                     guint xres, guint yres,
                     guint extend_left, guint extend_right,
                     guint extend_up, guint extend_down,
                     G_GNUC_UNUSED gdouble value)
{
    guint k0;
    gdouble *out2;
    rect_extend_base(in, inrowstride, out, outrowstride,
                     xpos, &ypos, width, &height, xres, yres,
                     extend_left, extend_right, &extend_up, &extend_down,
                     &row_extend_periodic, value);
    // Forward-extend
    out2 = out + outrowstride*(extend_up + height);
#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            shared(in,out2,xres,yres,xpos,ypos,width,height,inrowstride,outrowstride,extend_left,extend_right,extend_down,value)
#endif
    for (guint i = 0; i < extend_down; i++) {
        guint k = (ypos + height + i) % yres;
        row_extend_periodic(in + k*inrowstride, out2 + i*outrowstride,
                            xpos, width, xres, extend_left, extend_right, value);
    }
    // Backward-extend
    k0 = (extend_up/yres + 1)*yres;
    out2 = out + outrowstride*extend_up;
#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            shared(in,out2,xres,yres,xpos,ypos,width,height,inrowstride,outrowstride,extend_left,extend_right,extend_up,extend_down,k0,value)
#endif
    for (guint i = 1; i <= extend_up; i++) {
        guint k = (k0 + ypos - i) % yres;
        row_extend_periodic(in + k*inrowstride, out2 - i*outrowstride,
                            xpos, width, xres, extend_left, extend_right, value);
    }
}

static void
rect_extend_border(const gdouble *in, guint inrowstride,
                   gdouble *out, guint outrowstride,
                   guint xpos, guint ypos,
                   guint width, guint height,
                   guint xres, guint yres,
                   guint extend_left, guint extend_right,
                   guint extend_up, guint extend_down,
                   G_GNUC_UNUSED gdouble value)
{
    gdouble *out2;
    rect_extend_base(in, inrowstride, out, outrowstride,
                     xpos, &ypos, width, &height, xres, yres,
                     extend_left, extend_right, &extend_up, &extend_down,
                     &row_extend_border, value);
    // Forward-extend
    out2 = out + outrowstride*(extend_up + height);
#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            shared(in,out2,xres,yres,xpos,ypos,width,height,inrowstride,outrowstride,extend_left,extend_right,extend_down,value)
#endif
    for (guint i = 0; i < extend_down; i++)
        row_extend_border(in + (yres-1)*inrowstride, out2 + i*outrowstride,
                          xpos, width, xres, extend_left, extend_right, value);
    // Backward-extend
    out2 = out + outrowstride*extend_up;
#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            shared(in,out2,xres,yres,xpos,ypos,width,height,inrowstride,outrowstride,extend_left,extend_right,extend_up,extend_down,value)
#endif
    for (guint i = 1; i <= extend_up; i++)
        row_extend_border(in, out2 - i*outrowstride,
                          xpos, width, xres, extend_left, extend_right, value);
}

static void
rect_extend_fill(const gdouble *in, guint inrowstride,
                 gdouble *out, guint outrowstride,
                 guint xpos, guint ypos,
                 guint width, guint height,
                 guint xres, guint yres,
                 guint extend_left, guint extend_right,
                 guint extend_up, guint extend_down,
                 gdouble value)
{
    gdouble *out2;
    rect_extend_base(in, inrowstride, out, outrowstride,
                     xpos, &ypos, width, &height, xres, yres,
                     extend_left, extend_right, &extend_up, &extend_down,
                     &row_extend_fill, value);
    // Forward-extend
    out2 = out + outrowstride*(extend_up + height);
    for (guint i = 0; i < extend_down; i++, out2 += outrowstride)
        fill_block(out2, extend_left + width + extend_right, value);
    // Backward-extend
    out2 = out + outrowstride*(extend_up - 1);
    for (guint i = 1; i <= extend_up; i++, out2 -= outrowstride)
        fill_block(out2, extend_left + width + extend_right, value);
}

/* Returns TRUE if extension is handled without extending anything. */
static inline gboolean
rect_extend_fullfield_common(const gdouble *in, guint inrowstride,
                             gdouble *out, guint outrowstride,
                             guint *xpos, guint *ypos,
                             guint *width, guint *height,
                             guint xres, guint yres,
                             guint *extend_left, guint *extend_right,
                             guint *extend_up, guint *extend_down)
{
    /* Expand the ROI down as far as possible */
    guint e2d = MIN(*extend_down, yres - (*ypos + *height));
    *height += e2d;
    *extend_down -= e2d;

    /* Expand the ROI up as far as possible */
    guint e2u = MIN(*extend_up, *ypos);
    *height += e2u;
    *extend_up -= e2u;
    *ypos -= e2u;

    /* Expand the ROI to the right as far as possible */
    guint e2r = MIN(*extend_right, xres - (*xpos + *width));
    *width += e2r;
    *extend_right -= e2r;

    /* Expand the ROI to the left as far as possible */
    guint e2l = MIN(*extend_left, *xpos);
    *width += e2l;
    *extend_left -= e2l;
    *xpos -= e2l;

    if (*extend_down + *extend_up + *extend_right + *extend_left)
        return FALSE;

    gint w = *width, h = *height;
    /* Direct copy of the ROI */
    for (guint i = 0; i < h; i++)
        gwy_assign(out + (*extend_up + i)*outrowstride + *extend_left, in + (*ypos + i)*inrowstride + *xpos, w);
    return TRUE;
}

static inline void
rect_extend_laplace(const gdouble *in, guint inrowstride,
                    gdouble *out, guint outrowstride,
                    guint xpos, guint ypos,
                    guint width, guint height,
                    guint xres, guint yres,
                    guint extend_left, guint extend_right,
                    guint extend_up, guint extend_down,
                    G_GNUC_UNUSED gdouble value)
{
    if (rect_extend_fullfield_common(in, inrowstride, out, outrowstride,
                                     &xpos, &ypos, &width, &height, xres, yres,
                                     &extend_left, &extend_right, &extend_up, &extend_down))
        return;

    guint extxres = width + extend_left + extend_right;
    guint extyres = height + extend_up + extend_down;
    GwyNield *mask = gwy_nield_new(extxres, extyres);
    gwy_nield_fill(mask, 1);
    gwy_nield_area_clear(mask, extend_left, extend_up, width, height);

    /* NB: We cannot recycle out in any manner because it has different rowstride than extxres! */
    GwyField *workspace = gwy_field_new(extxres, extyres, 1.0, 1.0, FALSE);
    gdouble *wdata = workspace->priv->data;

    for (guint i = 0; i < height; i++)
        gwy_assign(wdata + (extend_up + i)*extxres + extend_left, in + (ypos + i)*inrowstride + xpos, width);
    gwy_field_laplace_solve(workspace, mask, GWY_LAPLACE_MASKED, 0.5);
    for (guint i = 0; i < extyres; i++)
        gwy_assign(out + i*outrowstride, wdata + i*extxres, extxres);

    g_object_unref(workspace);
    g_object_unref(mask);
}

static inline void
extend_connect_corner(const gdouble *erow, gdouble *outrow,
                      guint xpos, guint width, guint xres,
                      guint extend_left, guint extend_right,
                      G_GNUC_UNUSED gdouble value)
{
    row_extend_connect(erow + extend_left, outrow,
                       xpos, width, xres,
                       extend_left, extend_right,
                       value);

    guint extxres = width + extend_left + extend_right;
    for (guint j = 0; j < extend_left; j++)
        outrow[j] = 0.5*(outrow[j] + erow[j]);
    for (guint j = extend_left + width; j < extxres; j++)
        outrow[j] = 0.5*(outrow[j] + erow[j]);
}

static inline void
rect_extend_connect(const gdouble *in, guint inrowstride,
                    gdouble *out, guint outrowstride,
                    guint xpos, guint ypos,
                    guint width, guint height,
                    guint xres, guint yres,
                    guint extend_left, guint extend_right,
                    guint extend_up, guint extend_down,
                    G_GNUC_UNUSED gdouble value)
{
    if (rect_extend_fullfield_common(in, inrowstride, out, outrowstride,
                                     &xpos, &ypos, &width, &height, xres, yres,
                                     &extend_left, &extend_right, &extend_up, &extend_down))
        return;

    guint extxres = width + extend_left + extend_right;
    guint extyres = height + extend_up + extend_down;
    GwyField *extfield = gwy_field_new(extxres, extyres, extxres, extyres, FALSE);
    gdouble *edata = extfield->priv->data;
    /* Extend rows horizontally, but only rows with original data in them. The zeroth row will end up at extend_up
     * in the extended field (which has exactly the extended dimensions – out might not have). */
    for (guint i = 0; i < height; i++) {
        row_extend_connect(in + i*inrowstride, edata + (i + extend_up)*extxres,
                           xpos, width, xres,
                           extend_left, extend_right,
                           value);
    }

    /* Flip and extend columns, including cross-extending the newly added column data. */
    GwyField *flipped = gwy_field_new(extyres, extxres, extyres, extxres, FALSE);
    gwy_field_transpose(extfield, flipped, FALSE);
    gdouble *fdata = flipped->priv->data;
    gdouble *buf = g_new(gdouble, extyres);
    for (guint i = 0; i < extxres; i++) {
        row_extend_connect(fdata + i*extyres + extend_up, buf,
                           ypos, height, yres,
                           extend_up, extend_down,
                           value);
        gwy_assign(fdata + i*extyres, buf, extyres);
    }

    /* Flip back. */
    gwy_field_transpose(flipped, extfield, FALSE);
    g_object_unref(flipped);

    /* Now the corner areas have only been extended in one direction (vertical). So also cross-extend the up and down
     * rows horizontally and use the average of the two methods to fill the corner regions. Use rows of out directly
     * as the targets. */
    for (guint i = 0; i < extend_up; i++) {
        extend_connect_corner(edata + i*extxres, out + i*outrowstride,
                              xpos, width, xres,
                              extend_left, extend_right,
                              value);
    }

    for (guint i = extend_up; i < extend_up + height; i++)
        gwy_assign(out + i*outrowstride, edata + i*extxres, extxres);

    for (guint i = extend_up + height; i < extyres; i++) {
        extend_connect_corner(edata + i*extxres, out + i*outrowstride,
                              xpos, width, xres,
                              extend_left, extend_right,
                              value);
    }

    g_object_unref(extfield);
}

RowExtendFunc
_gwy_get_row_extend_func(GwyExteriorType exterior)
{
    if (exterior == GWY_EXTERIOR_FIXED_VALUE)
        return &row_extend_fill;
    /* In a true 1D context, open-ended Laplace interpolation just copies the boundary values. It is not particularly
     * useful, but it is a valid thing to do. */
    if (exterior == GWY_EXTERIOR_BORDER || exterior == GWY_EXTERIOR_LAPLACE)
        return &row_extend_border;
    if (exterior == GWY_EXTERIOR_MIRROR)
        return &row_extend_mirror;
    if (exterior == GWY_EXTERIOR_PERIODIC)
        return &row_extend_periodic;
    if (exterior == GWY_EXTERIOR_CONNECT)
        return &row_extend_connect;
    g_return_val_if_reached(NULL);
}

RectExtendFunc
_gwy_get_rect_extend_func(GwyExteriorType exterior)
{
    if (exterior == GWY_EXTERIOR_FIXED_VALUE)
        return &rect_extend_fill;
    if (exterior == GWY_EXTERIOR_BORDER)
        return &rect_extend_border;
    if (exterior == GWY_EXTERIOR_MIRROR)
        return &rect_extend_mirror;
    if (exterior == GWY_EXTERIOR_PERIODIC)
        return &rect_extend_periodic;
    if (exterior == GWY_EXTERIOR_LAPLACE)
        return &rect_extend_laplace;
    if (exterior == GWY_EXTERIOR_CONNECT)
        return &rect_extend_connect;
    g_return_val_if_reached(NULL);
}

/**
 * gwy_field_extend:
 * @field: A two-dimensional data field.
 * @left: Number of pixels to extend to the left (towards lower column indices).
 * @right: Number of pixels to extend to the right (towards higher column indices).
 * @up: Number of pixels to extend up (towards lower row indices).
 * @down: Number of pixels to extend down (towards higher row indices).
 * @exterior: Exterior pixels handling.
 * @fill_value: The value to use with %GWY_EXTERIOR_FIXED_VALUE exterior.
 * @keep_offsets: %TRUE to set the X and Y offsets of the new field using @field offsets.  %FALSE to set offsets of
 *                the new field to zeroes.
 *
 * Creates a new data field by extending another data field using the specified method of exterior handling.
 *
 * If @exterior is not %GWY_EXTERIOR_UNDEFINED, the added pixels are filled with values, according to the exterior
 * type. If it is undefined, the added pixels are simply left uninitialised.
 *
 * Returns: (transfer full): A newly created data field.
 **/
GwyField*
gwy_field_extend(GwyField *field,
                 guint left, guint right,
                 guint up, guint down,
                 GwyExteriorType exterior,
                 gdouble fill_value,
                 gboolean keep_offsets)
{
    g_return_val_if_fail(GWY_IS_FIELD(field), NULL);

    RectExtendFunc extend_rect = NULL;
    if (exterior != GWY_EXTERIOR_UNDEFINED)
        extend_rect = _gwy_get_rect_extend_func(exterior);

    guint width = field->xres, height = field->yres, col = 0, row = 0;
    GwyField *target = gwy_field_new(width + left + right, height + up + down, 1.0, 1.0, FALSE);
    if (!extend_rect)
        gwy_field_area_copy(field, target, 0, 0, width, height, left, up);
    else {
        extend_rect(field->priv->data, field->xres, target->priv->data, target->xres,
                    col, row, width, height, field->xres, field->yres,
                    left, right, up, down, fill_value);
    }

    gdouble dx = field->xreal/field->xres;
    gdouble dy = field->yreal/field->yres;
    gwy_field_set_xreal(target, (width + left + right)*dx);
    gwy_field_set_yreal(target, (height + up + down)*dy);
    if (keep_offsets) {
        gwy_field_set_xoffset(target, field->xoff + col*dx - left*dx);
        gwy_field_set_yoffset(target, field->yoff + row*dy - up*dy);
    }
    else {
        gwy_field_set_xoffset(target, 0.0);
        gwy_field_set_yoffset(target, 0.0);
    }
    gwy_field_copy_units(field, target);

    return target;
}

/**
 * gwy_nield_extend:
 * @nield: A two-dimensional number field.
 * @left: Number of pixels to extend to the left (towards lower column indices).
 * @right: Number of pixels to extend to the right (towards higher column indices).
 * @up: Number of pixels to extend up (towards lower row indices).
 * @down: Number of pixels to extend down (towards higher row indices).
 * @exterior: Exterior pixels handling.
 * @fill_value: The value to use with %GWY_EXTERIOR_FIXED_VALUE exterior.
 *
 * Creates a new natural number field by extending another field using the specified method of exterior handling.
 *
 * Only simple exteriors not requiring interpolation can be used with this function. This excludes for instance
 * %GWY_EXTERIOR_LAPLACE and %GWY_EXTERIOR_CONNECT.
 *
 * If @exterior is not %GWY_EXTERIOR_UNDEFINED, the added pixels are filled with values, according to the exterior
 * type. If it is undefined, the added pixels are simply left uninitialised.
 *
 * Returns: (transfer full): A newly created number field.
 **/
GwyNield*
gwy_nield_extend(GwyNield *nield,
                 guint left, guint right,
                 guint up, guint down,
                 GwyExteriorType exterior,
                 gint fill_value)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);

    gint sup = up, sleft = left;
    gint width = nield->xres, height = nield->yres;
    gint xres = width + left + right, yres = height + up + down;
    GwyNield *target = gwy_nield_new(xres, yres);
    const gint *m = nield->priv->data;
    gint *t = target->priv->data;

    if (exterior == GWY_EXTERIOR_PERIODIC) {
        gint minus_up = -sup % height + height;
        gint minus_left = -sleft % width + width;
        for (gint i = 0; i < yres; i++) {
            gint isrc = (i + minus_up) % height;
            for (gint j = 0; j < xres; j++) {
                gint jsrc = (j + minus_left) % width;
                t[i*xres + j] = m[isrc*width + jsrc];
            }
        }
    }
    else if (exterior == GWY_EXTERIOR_MIRROR) {
        gint minus_up = -sup % (2*height) + 2*height;
        gint minus_left = -sleft % (2*width) + 2*width;
        for (gint i = 0; i < yres; i++) {
            gint isrc = (i + minus_up) % (2*height);
            if (isrc >= height)
                isrc = 2*height-1 - isrc;
            for (gint j = 0; j < xres; j++) {
                gint jsrc = (j + minus_left) % (2*width);
                if (jsrc >= width)
                    jsrc = 2*width-1 - jsrc;
                t[i*xres + j] = m[isrc*width + jsrc];
            }
        }
    }
    else if (exterior == GWY_EXTERIOR_BORDER) {
        for (gint i = 0; i < yres; i++) {
            gint isrc = CLAMP(i - sup, 0, height-1);
            for (gint j = 0; j < xres; j++) {
                gint jsrc = CLAMP(j - sleft, 0, width-1);
                t[i*xres + j] = m[isrc*width + jsrc];
            }
        }
    }
    else {
        g_warn_if_fail(exterior == GWY_EXTERIOR_FIXED_VALUE || exterior == GWY_EXTERIOR_UNDEFINED);
        if (exterior != GWY_EXTERIOR_UNDEFINED && fill_value && left + right + up + down > 0)
            gwy_nield_fill(target, fill_value);
        gwy_nield_area_copy(nield, target, 0, 0, width, height, left, up);
    }

    return target;
}

/**
 * gwy_line_extend:
 * @line: A data line.
 * @left: Number of values to extend to the left (towards lower indices).
 * @right: Number of values to extend to the right (towards higher indices).
 * @exterior: Exterior values handling.
 * @fill_value: The value to use with %GWY_EXTERIOR_FIXED_VALUE exterior.
 * @keep_offsets: %TRUE to set the offset of the new line using @line offset.  %FALSE to set offset of the new line
 *                to zero.
 *
 * Creates a new data line by extending another data line using the specified method of exterior handling.
 *
 * If @exterior is not %GWY_EXTERIOR_UNDEFINED, the added values are filled with values, according to the exterior
 * type. If it is undefined, the added values are simply left uninitialised.
 *
 * Returns: (transfer full): A newly created data line.
 **/
GwyLine*
gwy_line_extend(GwyLine *line,
                guint left,
                guint right,
                GwyExteriorType exterior,
                gdouble fill_value,
                gboolean keep_offsets)
{
    g_return_val_if_fail(GWY_IS_LINE(line), NULL);

    RowExtendFunc extend_row = NULL;
    if (exterior != GWY_EXTERIOR_UNDEFINED)
        extend_row = _gwy_get_row_extend_func(exterior);

    gint width = line->res, col = 0;
    GwyLine *target = gwy_line_new(width + left + right, 1.0, FALSE);
    if (!extend_row)
        gwy_line_part_copy(line, target, 0, width, left);
    else {
        extend_row(line->priv->data, target->priv->data,
                   col, width, line->res,
                   left, right, fill_value);
    }

    gdouble dx = line->real/line->res;
    gwy_line_set_real(target, (width + left + right)*dx);
    if (keep_offsets)
        gwy_line_set_offset(target, line->off + col*dx - left*dx);
    else
        gwy_line_set_offset(target, 0.0);
    gwy_line_copy_units(line, target);

    return target;
}

/**
 * SECTION: extend
 * @title: Extend
 * @short_description: Extend and extrapolate data
 **/

/**
 * GwyExteriorType:
 * @GWY_EXTERIOR_UNDEFINED: The values corresponding to or calculated from exterior data values are undefined, they
 *                          may be left unset or set to bogus values.  The caller must handle them itself afterwards,
 *                          for instance by resizing the result to consist of valid data only.
 * @GWY_EXTERIOR_BORDER: Values of exterior pixels are considered to be equal to the values of the nearest
 *                              interior pixels.
 * @GWY_EXTERIOR_MIRROR: The data is considered to be periodically repeated, with odd instances reflected (the
 *                              total period is thus twice the size of the data).
 * @GWY_EXTERIOR_PERIODIC: The data is considered to be periodically repeated.
 * @GWY_EXTERIOR_FIXED_VALUE: Values of exterior pixels are considered to be all equal to a user-specified value.
 * @GWY_EXTERIOR_LAPLACE: Values of exterior pixels are extended using Laplace interpolation (actually extrapolation)
 *                        like gwy_field_laplace_solve().  Only some functions implement this method.
 * @GWY_EXTERIOR_CONNECT: Values of exterior pixels are extended by connecting the opposite end in a somewhat smooth
 *                        manner, implying periodicity. It is mainly a 1D method. The 2D variant simply applies it
 *                        to rows and column separately, which is less useful.
 *
 * Methods to handle pixels outside data.
 *
 * Many methods currently use a fixed metod of handling of exterior pixels, for example area calculation uses
 * extension (border and mirror coincide), convolution uses mirror extension, rotation fills exterior with a fixed
 * value.
 **/

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

