/*
 *  $Id: fractals.c 29416 2026-01-30 16:49:54Z yeti-dn $
 *  Copyright (C) 2004 Jindrich Bilek.
 *  Copyright (C) 2024-2026 David Nečas (Yeti).
 *  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 "libgwyddion/macros.h"
#include "libgwyddion/math.h"
#include "libgwyddion/rand-gen-set.h"
#include "libgwyddion/field.h"
#include "libgwyddion/stats.h"
#include "libgwyddion/fractals.h"

#include "libgwyddion/internal.h"

// XXX: Most of the methods do not really need 2^N-sized images. They could simply normalise the results correctly
// to account for the number of pieces at given refinement level not always being ‘nice’.
// Then we would not have to do stupid things like trying to resample masks.
// Also the fractal interpolation is stupid because it resamples everything.

static void gwy_field_fractal_fit(GwyLine *xresult,
                                  GwyLine *yresult,
                                  gdouble *a,
                                  gdouble *b);

/**
 * gwy_field_fractal_partitioning:
 * @field: A data field.
 * @xresult: Data line to store x-values for log-log plot to.
 * @yresult: Data line to store y-values for log-log plot to.
 * @interpolation: Interpolation type.
 *
 * Computes data for log-log plot by partitioning (variance).
 *
 * Data lines @xresult and @yresult will be resized to the output size and they will contain corresponding values at
 * each position.
 **/
void
gwy_field_fractal_partitioning(GwyField *field,
                               GwyLine *xresult,
                               GwyLine *yresult,
                               GwyInterpolationType interpolation)
{
    GwyField *buffer;
    gint i, j, l, rp;
    gdouble rms;

    g_return_if_fail(GWY_IS_FIELD(field));
    g_return_if_fail(GWY_IS_LINE(xresult));
    g_return_if_fail(GWY_IS_LINE(yresult));

    gint xres = field->xres;
    gint dimexp = (gint)floor(log2(xres) + 0.5);
    gint xnewres = (1 << dimexp) + 1;

    buffer = gwy_field_new_resampled(field, xnewres, xnewres, interpolation);
    gwy_line_resize(xresult, dimexp-1);
    gwy_line_resize(yresult, dimexp-1);
    gwy_line_clear(yresult);

    gdouble *xrdata = xresult->priv->data, *yrdata = yresult->priv->data;
    for (l = 1; l < dimexp; l++) {
        rp = 1 << l;
        for (j = 0; j < (xnewres - 1)/rp - 1; j++) {
            for (i = 0; i < (xnewres - 1)/rp - 1; i++) {
                rms = gwy_field_area_rms(buffer, NULL, GWY_MASK_IGNORE, i*rp, j*rp, rp, rp);
                yrdata[l-1] += rms * rms;
            }
        }
        xrdata[l-1] = log(rp);

        /* Work around floating point arithmetic implementations that yield NaNs when one attempts to calculate
         * exp(log(0)) expecting 0 (greetings to Microsoft). */
        if (!yrdata[l])
            yrdata[l] = G_MINDOUBLE;
        yrdata[l-1] = log(yrdata[l-1]/(((xnewres - 1)/rp - 1) * ((xnewres - 1)/rp - 1)));
    }
    g_object_unref(buffer);
}

/**
 * gwy_field_fractal_cubecounting:
 * @field: A data field.
 * @xresult: Data line to store x-values for log-log plot to.
 * @yresult: Data line to store y-values for log-log plot to.
 * @interpolation: Interpolation type.
 *
 * Computes data for log-log plot by cube counting.
 *
 * Data lines @xresult and @yresult will be resized to the output size and they will contain corresponding values at
 * each position.
 **/
void
gwy_field_fractal_cubecounting(GwyField *field,
                               GwyLine *xresult,
                               GwyLine *yresult,
                               GwyInterpolationType interpolation)
{
    GwyField *buffer;
    gint i, j, l, m, n, rp, rp2;
    gdouble a, max, min, imin, hlp, height;

    g_return_if_fail(GWY_IS_FIELD(field));
    g_return_if_fail(GWY_IS_LINE(xresult));
    g_return_if_fail(GWY_IS_LINE(yresult));

    gint xres = field->xres;
    gint dimexp = (gint)floor(log2(xres) + 0.5);
    gint xnewres = (1 << dimexp) + 1;

    buffer = gwy_field_copy(field);
    gwy_field_resample(buffer, xnewres, xnewres, interpolation);
    gwy_line_resize(xresult, dimexp);
    gwy_line_resize(yresult, dimexp);
    gwy_line_clear(yresult);

    gwy_field_min_max(buffer, &imin, &height);
    height -= imin;

    gdouble *xrdata = xresult->priv->data, *yrdata = yresult->priv->data;
    gdouble *b = buffer->priv->data;
    for (l = 0; l < dimexp; l++) {
        rp = 1 << (l + 1);
        rp2 = (1 << dimexp)/rp;
        a = height/rp;
        for (i = 0; i < rp; i++) {
            for (j = 0; j < rp; j++) {
                max = -G_MAXDOUBLE;
                min = G_MAXDOUBLE;
                for (m = 0; m <= rp2; m++) {
                    for (n = 0; n <= rp2; n++) {
                        hlp = b[(i*rp2 + m) + xres*(j*rp2 + n)] - imin;
                        if (hlp > max)
                            max = hlp;
                        if (hlp < min)
                            min = hlp;
                    }
                }
                yrdata[l] += rp - floor(min/a) - floor((height - max)/a);
            }
        }
        xrdata[l] = (l+1 - dimexp)*G_LN2;
    }
    for (l = 0; l < dimexp; l++)
        yrdata[l] = log(yrdata[l]);

    g_object_unref(buffer);
}

/**
 * gwy_field_fractal_triangulation:
 * @field: A data field.
 * @xresult: Data line to store x-values for log-log plot to.
 * @yresult: Data line to store y-values for log-log plot to.
 * @interpolation: Interpolation type.
 *
 * Computes data for log-log plot by triangulation.
 *
 * Data lines @xresult and @yresult will be resized to the output size and they will contain corresponding values at
 * each position.
 **/
void
gwy_field_fractal_triangulation(GwyField *field,
                                GwyLine *xresult,
                                GwyLine *yresult,
                                GwyInterpolationType interpolation)
{
    GwyField *buffer;
    gint i, j, l, rp, rp2;
    gdouble dil, a, b, c, d, e, s1, s2, s, z1, z2, z3, z4, height;

    g_return_if_fail(GWY_IS_FIELD(field));
    g_return_if_fail(GWY_IS_LINE(xresult));
    g_return_if_fail(GWY_IS_LINE(yresult));

    gint xres = field->xres;
    gint dimexp = (gint)floor(log2(xres) + 0.5);
    gint xnewres = (1 << dimexp) + 1;

    buffer = gwy_field_copy(field);
    gwy_field_resample(buffer, xnewres, xnewres, interpolation);
    gwy_line_resize(xresult, dimexp + 1);
    gwy_line_resize(yresult, dimexp + 1);
    gwy_line_clear(yresult);

    height = gwy_field_max(buffer) - gwy_field_min(buffer);
    dil = (gdouble)(1 << dimexp)/height;
    dil *= dil;

    gdouble *xrdata = xresult->priv->data, *yrdata = yresult->priv->data;
    gdouble *bdata = buffer->priv->data;
    for (l = 0; l <= dimexp; l++) {
        rp = 1 << l;
        rp2 = (1 << dimexp)/rp;
        for (i = 0; i < rp; i++) {
            for (j = 0; j < rp; j++) {
                /* FIXME: We already have square_area1(), square_area2() in stats and entire area functions. */
                z1 = bdata[(i * rp2) + xnewres * (j * rp2)];
                z2 = bdata[((i + 1) * rp2) + xnewres * (j * rp2)];
                z3 = bdata[(i * rp2) + xnewres * ((j + 1) * rp2)];
                z4 = bdata[((i + 1) * rp2) + xnewres * ((j + 1) * rp2)];

                a = sqrt(rp2 * rp2 + dil * (z1 - z2) * (z1 - z2));
                b = sqrt(rp2 * rp2 + dil * (z1 - z3) * (z1 - z3));
                c = sqrt(rp2 * rp2 + dil * (z3 - z4) * (z3 - z4));
                d = sqrt(rp2 * rp2 + dil * (z2 - z4) * (z2 - z4));
                e = sqrt(2 * rp2 * rp2 + dil * (z3 - z2) * (z3 - z2));

                s1 = (a + b + e)/2;
                s2 = (c + d + e)/2;
                s = sqrt(s1 * (s1 - a) * (s1 - b) * (s1 - e)) + sqrt(s2 * (s2 - c) * (s2 - d) * (s2 - e));

                yrdata[l] += s;
            }
        }
        xrdata[l] = (l - dimexp)*G_LN2;
    }
    for (l = 0; l <= dimexp; l++)
        yrdata[l] = log(yrdata[l]);

    g_object_unref(buffer);
}

/**
 * gwy_field_fractal_psdf:
 * @field: A data field.
 * @xresult: Data line to store x-values for log-log plot to.
 * @yresult: Data line to store y-values for log-log plot to.
 * @interpolation: Interpolation type. The argument has no effect. It exists to keep the same signature as the other
 *                 functions.
 *
 * Computes data for log-log plot by spectral density method.
 *
 * Data lines @xresult and @yresult will be resized to the output size and they will contain corresponding values at
 * each position.
 **/
void
gwy_field_fractal_psdf(GwyField *field,
                       GwyLine *xresult,
                       GwyLine *yresult,
                       G_GNUC_UNUSED GwyInterpolationType interpolation)
{
    gint i, res;

    g_return_if_fail(GWY_IS_FIELD(field));
    g_return_if_fail(GWY_IS_LINE(xresult));
    g_return_if_fail(GWY_IS_LINE(yresult));

    gwy_field_psdf(field, yresult, GWY_ORIENTATION_HORIZONTAL, GWY_WINDOWING_HANN);
    res = yresult->res;
    gwy_line_resize(xresult, res-1);

    gdouble *xrdata = xresult->priv->data, *yrdata = yresult->priv->data;
    for (i = 1; i < res; i++) {
        xrdata[i-1] = log(i);
        yrdata[i-1] = log(yrdata[i]);
    }
    gwy_line_crop(yresult, 0, res-1);
}

/**
 * gwy_field_fractal_hhcf:
 * @field: A data field.
 * @xresult: Data line to store x-values for log-log plot to.
 * @yresult: Data line to store y-values for log-log plot to.
 *
 * Computes data for log-log plot by structure function (HHCF) method.
 *
 * Data lines @xresult and @yresult will be resized to the output size and they will contain corresponding values at
 * each position.
 **/
void
gwy_field_fractal_hhcf(GwyField *field,
                       GwyLine *xresult,
                       GwyLine *yresult)
{
    gint i, res, outres;

    g_return_if_fail(GWY_IS_FIELD(field));
    g_return_if_fail(GWY_IS_LINE(xresult));
    g_return_if_fail(GWY_IS_LINE(yresult));

    gwy_field_hhcf(field, yresult, GWY_ORIENTATION_HORIZONTAL);
    res = yresult->res;
    outres = MIN(res-1, (res + 5)/10 + GWY_ROUND(sqrt(res)));
    gwy_line_resize(xresult, outres);

    gdouble *xrdata = xresult->priv->data, *yrdata = yresult->priv->data;
    for (i = 1; i <= outres; i++) {
        xrdata[i-1] = log(i);
        yrdata[i-1] = log(yrdata[i]);
    }
    gwy_line_crop(yresult, 0, outres);
}

/**
 * gwy_field_fractal_cubecounting_dim:
 * @xresult: Log-log fractal data (x values).
 * @yresult: Log-log fractal data (y values).
 * @a: (out): Location to store linear fit constant to.
 * @b: (out): Location to store linear fit slope to.
 *
 * Computes fractal dimension by cube counting method from log-log plot data.
 *
 * The @xresult and @yresult data lines are usually calculated by gwy_field_fractal_cubecounting().
 *
 * Returns: The fractal dimension.
 **/
gdouble
gwy_field_fractal_cubecounting_dim(GwyLine *xresult,
                                   GwyLine *yresult,
                                   gdouble *a,
                                   gdouble *b)
{
    gwy_field_fractal_fit(xresult, yresult, a, b);

    return *a;
}

/**
 * gwy_field_fractal_triangulation_dim:
 * @xresult: Log-log fractal data (x values).
 * @yresult: Log-log fractal data (y values).
 * @a: (out): Location to store linear fit constant to.
 * @b: (out): Location to store linear fit slope to.
 *
 * Computes fractal dimension by triangulation method from log-log plot data.
 *
 * The @xresult and @yresult data lines are usually calculated by gwy_field_fractal_triangulation().
 *
 * Returns: The fractal dimension.
 **/
gdouble
gwy_field_fractal_triangulation_dim(GwyLine *xresult,
                                    GwyLine *yresult,
                                    gdouble *a,
                                    gdouble *b)
{
    gwy_field_fractal_fit(xresult, yresult, a, b);

    return 2.0 + (*a);
}

/**
 * gwy_field_fractal_partitioning_dim:
 * @xresult: Log-log fractal data (x values).
 * @yresult: Log-log fractal data (y values).
 * @a: (out): Location to store linear fit constant to.
 * @b: (out): Location to store linear fit slope to.
 *
 * Computes fractal dimension by partitioning (variance) method from log-log plot data.
 *
 * The @xresult and @yresult data lines are usually calculated by gwy_field_fractal_partitioning().
 *
 * Returns: The fractal dimension.
 **/
gdouble
gwy_field_fractal_partitioning_dim(GwyLine *xresult,
                                   GwyLine *yresult,
                                   gdouble *a,
                                   gdouble *b)
{
    gwy_field_fractal_fit(xresult, yresult, a, b);

    return 3.0 - (*a)/2.0;
}

/**
 * gwy_field_fractal_psdf_dim:
 * @xresult: Log-log fractal data (x values).
 * @yresult: Log-log fractal data (y values).
 * @a: (out): Location to store linear fit constant to.
 * @b: (out): Location to store linear fit slope to.
 *
 * Computes fractal dimension by spectral density function method from log-log plot data.
 *
 * The @xresult and @yresult data lines are usually calculated by gwy_field_fractal_psdf().
 *
 * Returns: The fractal dimension.
 **/
gdouble
gwy_field_fractal_psdf_dim(GwyLine *xresult, GwyLine *yresult,
                           gdouble *a, gdouble *b)
{
    gwy_field_fractal_fit(xresult, yresult, a, b);

    return 3.5 + (*a)/2.0;
}

/**
 * gwy_field_fractal_hhcf_dim:
 * @xresult: Log-log fractal data (x values).
 * @yresult: Log-log fractal data (y values).
 * @a: (out): Location to store linear fit constant to.
 * @b: (out): Location to store linear fit slope to.
 *
 * Computes fractal dimension by structure function (HHCF) method from log-log plot data.
 *
 * The @xresult and @yresult data lines are usually calculated by gwy_field_fractal_hhcf().
 *
 * Returns: The fractal dimension.
 **/
gdouble
gwy_field_fractal_hhcf_dim(GwyLine *xresult, GwyLine *yresult,
                           gdouble *a, gdouble *b)
{
    gwy_field_fractal_fit(xresult, yresult, a, b);

    return 3.0 - (*a)/2.0;
}

/**
 * gwy_field_fractal_fit:
 * @xresult: Log-log fractal data (x values).
 * @yresult: Log-log fractal data (y values).
 * @a: (out): Resulting shift.
 * @b: (out): Resulting direction.
 *
 * Fits fractal dimension from paritioning data.
 *
 * Currently simply fits results from the gwy_field_fractal_partitioning and similar functions by straight line.
 **/
static void
gwy_field_fractal_fit(GwyLine *xresult, GwyLine *yresult, gdouble *a, gdouble *b)
{
    g_return_if_fail(GWY_IS_LINE(xresult));
    g_return_if_fail(GWY_IS_LINE(yresult));
    g_return_if_fail(yresult->res == xresult->res);

    gdouble p[2];
    gwy_math_fit_poly(xresult->priv->data, yresult->priv->data, xresult->res, 1, p);
    *a = p[1];
    *b = p[0];
}

static inline gdouble
rg0(GwyRandGenSet *rngset, gdouble sigma)
{
    return gwy_rand_gen_set_gaussian(rngset, 0, sigma);
}

/*
 * data repair tool using succesive random additional midpoint displacement method.
 * Uses fields of size 2^k+1, *vars - y result field of gwy_field_fractal_partitioning(), *mask to specify which
 * points to correct and *z data
 */
static gboolean
fractal_correct(GwyField *field, GwyNield *mask, GwyLine *vars, gint k)
{
    GwyRandGenSet *rngset;
    gint i, j, l, p, ii, jj, pp, n, xres, kk;
    gdouble sg, avh;
    const gint *m = mask->priv->data;
    gdouble *z = field->priv->data;
    gdouble *vdata = vars->priv->data;

    rngset = gwy_rand_gen_set_new(1);
    avh = gwy_field_mean(field);

    xres = field->xres;

    for (l = 0; l < k; l++) {
        pp = 1 << l;
        p = 1 << (k-1 - l);
        n = (xres + 1)/pp;
        sg = sqrt((4.0*n*n - 6.0*n + 2.0)/((2.0 + G_SQRT2)*n*n - (4.0 + G_SQRT2)*n + 2.0) * exp(vdata[k-1 - l]));

        for (i = 0; i < pp; i++) {
            for (j = 0; j < pp; j++) {
                ii = (2*i + 1) * p;
                jj = (2*j + 1) * p;
                kk = ii*xres + jj;
                if (m[kk] > 0) {
                    if (l == 0)
                        z[kk] = avh;
                    else {
                        z[kk] = (z[kk - p*(xres+1)] + z[kk - p*(xres-1)]
                                 + z[kk + p*(xres-1)] + z[kk + p*(xres+1)])/4 + rg0(rngset, sg);
                    }
                }
            }
        }

        for (i = 0; i < pp; i++) {
            for (j = 0; j < pp; j++) {
                ii = (2*i + 1) * p;
                jj = (2*j + 1) * p;
                kk = ii*xres + jj;
                if (jj + p == n-1) {
                    if (ii + p == n-1) {
                        if (m[kk + p*(xres+1)] > 0)
                            z[kk + p*(xres+1)] = z[kk + p*(xres+1)] + rg0(rngset, sg);
                        if (m[kk + p*(xres-1)] > 0)
                            z[kk + p*(xres-1)] = z[kk + p*(xres-1)] + rg0(rngset, sg);
                        if (m[kk - p*(xres-1)] > 0)
                            z[kk - p*(xres-1)] = z[kk - p*(xres-1)] + rg0(rngset, sg);
                        if (m[kk - p*(xres+1)] > 0)
                            z[kk - p*(xres+1)] = z[kk - p*(xres+1)] + rg0(rngset, sg);
                    }
                    else {
                        if (m[kk - p*(xres-1)] > 0)
                            z[kk - p*(xres-1)] = z[kk - p*(xres-1)] + rg0(rngset, sg);
                        if (m[kk - p*(xres+1)] > 0)
                            z[kk - p*(xres+1)] = z[kk - p*(xres+1)] + rg0(rngset, sg);
                    }
                }
                else {
                    if ((ii + p) == n-1) {
                        if (m[kk + p*(xres-1)] > 0)
                            z[kk + p*(xres-1)] = z[kk + p*(xres-1)] + rg0(rngset, sg);
                        if (m[kk - p*(xres+1)] > 0)
                            z[kk - p*(xres+1)] = z[kk - p*(xres+1)] + rg0(rngset, sg);
                    }
                    else {
                        if (m[kk - p*(xres+1)] > 0)
                            z[kk - p*(xres+1)] = z[kk - p*(xres+1)] + rg0(rngset, sg);
                    }
                }
            }
        }

        sg /= G_SQRT2;
        for (i = 0; i < pp; i++) {
            for (j = 0; j < pp; j++) {
                ii = (2*i + 1) * p;
                jj = (2*j + 1) * p;
                kk = ii*xres + jj;
                if (l == 0) {
                    if (m[kk - p] > 0)
                        z[kk - p] = (z[kk - p*(xres+1)] + z[kk + p*(xres-1)] + z[kk])/3 + rg0(rngset, sg);
                    if (m[kk + p] > 0)
                        z[kk + p] = (z[kk - p*(xres-1)] + z[kk + p*(xres+1)] + z[kk])/3 + rg0(rngset, sg);
                    if (m[kk - p*xres] > 0)
                        z[kk - p*xres] = (z[kk - p*(xres+1)] + z[kk - p*(xres-1)] + z[kk])/3 + rg0(rngset, sg);
                    if (m[kk + p*xres] > 0)
                        z[kk + p*xres] = (z[kk + p*(xres-1)] + z[kk + p*(xres+1)] + z[kk])/3 + rg0(rngset, sg);
                }
                else {
                    if (jj + p == n-1) {
                        if (ii + p == n-1) {
                            if (m[kk + p*xres] > 0)
                                z[kk + p*xres] = (z[kk] + z[kk + p*(xres+1)] + z[kk + p*(xres-1)])/3 + rg0(rngset, sg);
                            if (m[kk + p] > 0)
                                z[kk + p] = (z[kk] + z[kk + p*(xres+1)] + z[kk - p*(xres-1)])/3 + rg0(rngset, sg);
                            if (m[kk - p*xres] > 0) {
                                z[kk - p*xres] = (z[kk] + z[kk - p*(xres-1)]
                                                  + z[kk - 2*p*xres] + z[kk - p*(xres+1)])/4 + rg0(rngset, sg);
                            }
                        }
                        if (ii + p != n && ii - p != 1) {
                            if (m[kk + p] > 0)
                                z[kk + p] = (z[kk] + z[kk + p*(xres+1)] + z[kk - p*(xres-1)])/3 + rg0(rngset, sg);
                            if (m[kk - p*xres] > 0) {
                                z[kk - p*xres] = (z[kk] + z[kk - p*(xres-1)]
                                                  + z[kk - 2*p*xres] + z[kk - p*(xres+1)])/4 + rg0(rngset, sg);
                            }
                        }
                        if ((ii - p) == 0) {
                            if (m[kk + p] > 0)
                                z[kk + p] = (z[kk] + z[kk - p*(xres-1)] + z[kk + p*(xres+1)])/3 + rg0(rngset, sg);
                            if (m[kk - p*xres] > 0)
                                z[kk - p*xres] = (z[kk] + z[kk - p*(xres+1)] + z[kk - p*(xres-1)])/3 + rg0(rngset, sg);
                        }
                    }
                    if (jj + p != n-1 && jj - p > 0) {
                        if (ii + p == n-1) {
                            if (m[kk + p*xres] > 0)
                                z[kk + p*xres] = (z[kk] + z[kk + p*(xres-1)] + z[kk + p*(xres+1)])/3 + rg0(rngset, sg);
                            if (m[kk + p] > 0) {
                                z[kk + p] = (z[kk] + z[kk + 2*p] + z[kk - p*(xres-1)]
                                             + z[kk + p*(xres+1)])/4 + rg0(rngset, sg);
                            }
                            if (m[kk - p*xres] > 0) {
                                z[kk - p*xres] = (z[kk] + z[kk - 2*p*xres]
                                                  + z[kk - p*(xres-1)] + z[kk - p*(xres+1)])/4 + rg0(rngset, sg);
                            }
                        }
                        if (ii + p != n-1 && ii - p > 0) {
                            if (m[kk + p] > 0) {
                                z[kk + p] = (z[kk] + z[kk + 2*p] + z[kk - p*(xres-1)]
                                             + z[kk + p*(xres+1)])/4 + rg0(rngset, sg);
                            }
                            if (m[kk - p*xres] > 0) {
                                z[kk - p*xres] = (z[kk] + z[kk - 2*p*xres]
                                                  + z[kk - p*(xres-1)] + z[kk - p*(xres+1)])/4 + rg0(rngset, sg);
                            }
                        }
                        if (ii - p == 0) {
                            if (m[kk + p] > 0)
                                z[kk + p] = (z[kk] + z[kk + 2*p]
                                             + z[kk - p*(xres-1)] + z[kk + p*(xres+1)])/4 + rg0(rngset, sg);
                            if (m[kk - p*xres] > 0)
                                z[kk - p*xres] = (z[kk] + z[kk - p*(xres-1)] + z[kk - p*(xres+1)])/3 + rg0(rngset, sg);
                        }
                    }
                    if (jj - p == 0) {
                        if (ii + p == n-1) {
                            if (m[kk + p*xres] > 0)
                                z[kk + p*xres] = (z[kk] + z[kk + p*(xres-1)] + z[kk + p*(xres+1)])/3 + rg0(rngset, sg);
                            if (m[kk + p] > 0) {
                                z[kk + p] = (z[kk] + z[kk + 2*p]
                                             + z[kk - p*(xres-1)] + z[kk + p*(xres+1)])/4 + rg0(rngset, sg);
                            }
                            if (m[kk - p*xres] > 0) {
                                z[kk - p*xres] = (z[kk] + z[kk - 2*p*xres]
                                                  + z[kk - p*(xres-1)] + z[kk - p*(xres+1)])/4 + rg0(rngset, sg);
                            }
                            if (m[kk - p] > 0)
                                z[kk - p] = (z[kk] + z[kk - p*(xres+1)] + z[kk + p*(xres-1)])/3 + rg0(rngset, sg);
                        }
                        if (ii + p != n-1 && ii - p > 0) {
                            if (m[kk + p] > 0) {
                                z[kk + p] = (z[kk] + z[kk + 2*p]
                                             + z[kk - p*(xres-1)] + z[kk + p*(xres+1)])/4 + rg0(rngset, sg);
                            }
                            if (m[kk - p*xres] > 0) {
                                z[kk - p*xres] = (z[kk] + z[kk - 2*p*xres]
                                                  + z[kk - p*(xres-1)] + z[kk - p*(xres+1)])/4 + rg0(rngset, sg);
                            }
                            if (m[kk - p] > 0)
                                z[kk - p] = (z[kk] + z[kk - p*(xres+1)] + z[kk + p*(xres-1)])/3 + rg0(rngset, sg);
                        }
                        if (ii - p == 0) {
                            if (m[kk + p] > 0) {
                                z[kk + p] = (z[kk] + z[kk + 2*p]
                                             + z[kk - p*(xres-1)] + z[kk + p*(xres+1)])/4 + rg0(rngset, sg);
                            }
                            if (m[kk - p*xres] > 0)
                                z[kk - p*xres] = (z[kk] + z[kk - p*(xres-1)] + z[kk - p*(xres+1)])/3 + rg0(rngset, sg);
                            if (m[kk - p] > 0)
                                z[kk - p] = (z[kk] + z[kk - p*(xres+1)] + z[kk + p*(xres-1)])/3 + rg0(rngset, sg);
                        }
                    }
                }
            }
        }

        for (i = 0; i < pp; i++) {
            for (j = 0; j < pp; j++) {
                ii = (2*i + 1) * p;
                jj = (2*j + 1) * p;
                kk = ii*xres + jj;
                if (jj + p == n-1) {
                    if (ii + p == n-1) {
                        if (m[kk + p*(xres+1)] > 0)
                            z[kk + p*(xres+1)] = z[kk + p*(xres+1)] + rg0(rngset, sg);
                        if (m[kk + p*(xres-1)] > 0)
                            z[kk + p*(xres-1)] = z[kk + p*(xres-1)] + rg0(rngset, sg);
                        if (m[kk - p*(xres-1)] > 0)
                            z[kk - p*(xres-1)] = z[kk - p*(xres-1)] + rg0(rngset, sg);
                        if (m[kk - p*(xres+1)] > 0)
                            z[kk - p*(xres+1)] = z[kk - p*(xres+1)] + rg0(rngset, sg);
                        if (m[kk] > 0)
                            z[kk] = z[kk] + rg0(rngset, sg);
                    }
                    else {
                        if (m[kk - p*(xres-1)] > 0)
                            z[kk - p*(xres-1)] = z[kk - p*(xres-1)] + rg0(rngset, sg);
                        if (m[kk - p*(xres+1)] > 0)
                            z[kk - p*(xres+1)] = z[kk - p*(xres+1)] + rg0(rngset, sg);
                        if (m[kk] > 0)
                            z[kk] = z[kk] + rg0(rngset, sg);
                    }
                }
                else {
                    if (ii + p == n-1) {
                        if (m[kk + p*(xres-1)] > 0)
                            z[kk + p*(xres-1)] = z[kk + p*(xres-1)] + rg0(rngset, sg);
                        if (m[kk - p*(xres+1)] > 0)
                            z[kk - p*(xres+1)] = z[kk - p*(xres+1)] + rg0(rngset, sg);
                        if (m[kk] > 0)
                            z[kk] = z[kk] + rg0(rngset, sg);
                    }
                    else {
                        if (m[kk - p*(xres+1)] > 0)
                            z[kk - p*(xres+1)] = z[kk - p*(xres+1)] + rg0(rngset, sg);
                        if (m[kk] > 0)
                            z[kk] = z[kk] + rg0(rngset, sg);
                    }
                }
            }
        }
    }

    gwy_rand_gen_set_free(rngset);
    gwy_field_invalidate(field);

    return TRUE;
}

/**
 * gwy_field_fractal_correction:
 * @field: A data field.
 * @mask: Mask of places to be corrected.
 * @interpolation: Interpolation type.
 *
 * Replaces data under mask with interpolated values using fractal interpolation.
 *
 * The data should preferrably be square and with pixel size a power of 2. The function does not require it, but data
 * not satisfying this condition are resampled back and forth using given interpolation.
 **/
void
gwy_field_fractal_correction(GwyField *field,
                             GwyNield *mask,
                             GwyInterpolationType interpolation)
{
    if (!_gwy_field_check_mask(field, &mask, NULL))
        return;

    gint xres = field->xres, yres = field->yres;
    gint dimexp = (gint)floor(log2(xres) + 0.5);
    gint xnewres = (1 << dimexp) + 1;

    GwyField *buffer = gwy_field_new_resampled(field, xnewres, xnewres, interpolation);
    GwyNield *maskbuffer = gwy_nield_new_resampled(mask, xnewres, xnewres);
    GwyLine *xresult = gwy_line_new(1, 1, FALSE);
    GwyLine *yresult = gwy_line_new(1, 1, FALSE);

    // FIXME: There used to be function fractal_partitioning_nomask(). The name suggests it was expected to exclude
    // the masked data. But it never did that. It just resampled the mask and did normal partitioning, completely
    // ignoring the mask object.
    gwy_field_fractal_partitioning(field, xresult, yresult, interpolation);

    if (fractal_correct(buffer, maskbuffer, yresult, dimexp)) {
        gwy_field_resample(buffer, xres, yres, interpolation);
        gsize n = (gsize)xres * (gsize)yres;
        const gdouble *b = buffer->priv->data;
        const gint *m = mask->priv->data;
        gdouble *d = field->priv->data;
        for (gsize i = 0; i < n; i++) {
            if (m[i] > 0)
                d[i] = b[i];
        }
    }

    g_object_unref(buffer);
    g_object_unref(maskbuffer);
    g_object_unref(xresult);
    g_object_unref(yresult);
}

/**
 * SECTION: fractals
 * @title: Fractals
 * @short_description: Fractal dimension calculation, fractal interpolation
 **/

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