/**************************************************************************
 *{@C
 *      Copyright:      1988-2023 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         ImageProcessing
 *      Filename:       IP_Stretch.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    A function to stretch images.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      IP_ScaleRect
 *
 **************************************************************************/

#include <stdio.h>

#include "UT_Compat.h"
#include "UT_Macros.h"
#include "UT_Error.h"
#include "UT_Memory.h"

#define  USE_IMG_MACROS
#include "FF_Image.h"

#include "IP_Image.h"
#include "IP_ImagePrivate.h"


/* A scaling lookup table element */

typedef struct {
    Int32   src,            /* Source pixel coordinate */
            dest;           /* Destination pixel coordinate */
    Float32 weight;         /* Source pixel weighting factor */
} scaletable;

/* Create a lookup table for quicker scaling; pyramid filter version. */

static UT_Bool make_pyr_scaletable
        (Int32 imin, Int32 imax, Int32 jmax,
         Float32 umin, Float32 umax,
         Float32 vmin, Float32 vmax,
         UT_Bool reverse,
         scaletable **stable, Int32 *tabsize)
{
    Float32     m, t;
    Int32       i, j;
    scaletable  *st;

    /* Initialization */

    m = (umax - umin) / (vmax - vmin);
    t = umin - m * vmin;
    if (UT_ABS (m) <= 1.0) {
        /* Source range is smaller than destination range; image is expanded. */

        Float32 u, f, g;

        /* First find out how many elements we need in the lookup table
        and allocate memory for the table. */

        *tabsize = 2 * (imax - imin + 1);
        if (!(*stable = UT_MemTempArray (*tabsize, scaletable)))
            return UT_False;

        /* Fill in the elements of the table. */

        st = *stable;
        for (i = imin; i <= imax; ++i) {
            u = i * m + t;
            j = (Int32)u;
            f = u - j;
            g = 1.0 - f;
            st->dest = reverse? imax - i + imin: i;
            st->src = UT_MAX (0, UT_MIN (j, jmax));
            st->weight = g;
            ++st;
            ++j;
            st->dest = reverse? imax - i + imin: i;
            st->src = UT_MAX (0, UT_MIN (j, jmax));
            st->weight = f;
            ++st;
        }
    } else {
        /* Source range is larger than destination range; image is shrunk. */

        Float32 u, u1, u2, weight, sum;

        /* First find out how many elements we need in the lookup table. */

        *tabsize = 0;
        for (i = imin; i <= imax; ++i) {
            u = i * m + t;
            u1 = (i - 1) * m + t;
            u2 = (i + 1) * m + t;
            for (j = (Int32) u1; j <= (Int32) u2 + 1; ++j) {
                if (j < u)
                    weight = j - u1;
                else
                    weight = u2 - j;
                if (weight <= 0.0)
                    continue;
                ++*tabsize;
            }
        }

        /* Allocate memory for the table. */

        if (!(*stable = UT_MemTempArray (*tabsize, scaletable)))
            return UT_False;

        /* Fill in the elements of the table. */

        st = *stable;
        for (i = imin; i <= imax; ++i) {
            u = i * m + t;
            u1 = (i - 1) * m + t;
            u2 = (i + 1) * m + t;
            sum = 0.0;
            for (j = (Int32) u1; j <= (Int32) u2 + 1; ++j) {
                if (j < u)
                    weight = j - u1;
                else
                    weight = u2 - j;
                if (weight <= 0.0)
                    continue;
                sum += weight;
            }
            for (j = (Int32) u1; j <= (Int32) u2 + 1; ++j) {
                if (j < u)
                    weight = j - u1;
                else
                    weight = u2 - j;
                if (weight <= 0.0)
                    continue;
                st->dest = reverse? imax - i + imin: i;
                st->src = UT_MAX (0, UT_MIN (j, jmax));
                st->weight = weight / sum;
                ++st;
            }
        }
    }
    if (st - *stable != *tabsize)
        UT_ErrFatal ("make_pyr_scaletable", "unexpected table size");
    return UT_True;
}

/* Create a lookup table for quicker scaling; box filter version. */

static UT_Bool make_box_scaletable
        (Int32 imin, Int32 imax, Int32 jmax,
         Float32 umin, Float32 umax,
         Float32 vmin, Float32 vmax,
         UT_Bool reverse,
         scaletable **stable, Int32 * tabsize)
{
    Float32     m, t, u1, u2;
    Int32               i, j;
    scaletable  *st;

    /* Initialization */

    m = (umax - umin) / (vmax - vmin);
    t = umin - m * vmin + 0.5;

    /* Find out how many elements we need in the lookup table. */

    *tabsize = 0;
    for (i = imin; i <= imax; ++i) {
        u1 = i * m + t;
        u2 = (i + 1) * m + t;
        for (j = (Int32) u1; j <= (Int32) u2; ++j)
            ++* tabsize;
    }

    /* Allocate memory for the lookup table. */

    if (!(*stable = UT_MemTempArray (*tabsize, scaletable)))
        return UT_False;

    /* Fill in the elements of the table. */

    st = *stable;
    for (i = imin; i <= imax; ++i) {
        u1 = i * m + t;
        u2 = (i + 1) * m + t;
        for (j = (Int32) u1; j <= (Int32) u2; ++j) {
            st->dest = reverse? imax - i + imin: i;
            if (j >= 0 && j <= jmax) {
                st->src = j;
                st->weight =
                    (UT_MIN (u2, (Float32) (j + 1)) - UT_MAX (u1, (Float32) j)) / m;
            } else {
                st->src = 0;
                st->weight = 0.0;
            }
            ++st;
        }
    }
    if (st - *stable != *tabsize)
        UT_ErrFatal ("make_box_scaletable", "unexpected table size");
    return UT_True;
}

/* Insert a horizontal span of scaled pixel data, "data", into channel "chn" of
the destination image, "img", observing the current drawing mode. The left and
right end points of the span are inserted at pixels (x1, y) and (x2, y). */

static void insert_span
        (IP_ImageId img,
         const Float32 *data,
         FF_ImgChanType chn,
         Int32 x1, Int32 x2, Int32 y)
{
    Int32         tmp;
    const Float32 *fsrc, *fstop;
    Float32       *fdest;
    UInt8         *ubdest;

    fsrc = data;
    fstop = fsrc + x2 - x1;
    switch (img->pimg.channel[chn]) { 
        case FF_ImgFmtTypeNone: {
            break;
        }
        case FF_ImgFmtTypeUByte: {
            ubdest = FF_ImgUBytePixel (&img->pimg, chn, x1, y);
            while (fsrc <= fstop) {
                switch (IP_StateDrawMode[chn]) {
                    case IP_DrawModeReplace: {
                        tmp = (Int32) (*fsrc * 255.0);
                        *ubdest = UT_MAX (0, UT_MIN (tmp, 255));
                        break;
                    }
                    case IP_DrawModeAdd: {
                        tmp = (Int32) (*fsrc * 255.0);
                        tmp += *ubdest;
                        *ubdest = UT_MAX (0, UT_MIN (tmp, 255));
                        break;
                    }
                    case IP_DrawModeSub: {
                        tmp = (Int32) (*fsrc * 255.0);
                        tmp -= *ubdest;
                        *ubdest = UT_MAX (0, UT_MIN (tmp, 255));
                        break;
                    }
                    case IP_DrawModeXor: {
                        tmp = (Int32) (*fsrc * 255.0);
                        tmp ^= *ubdest;
                        *ubdest = UT_MAX (0, UT_MIN (tmp, 255));
                        break;
                    }
                    default: {
                        UT_ErrFatal ("insert_span", "unknown draw mode");
                    }
                }
                ++fsrc;
                ++ubdest;
            }
            break;
        }
        case FF_ImgFmtTypeFloat: {
            fdest = FF_ImgFloatPixel (&img->pimg, chn, x1, y);
            while (fsrc <= fstop) {
                switch (IP_StateDrawMode[chn]) {
                    case IP_DrawModeReplace: {
                        *fdest = *fsrc;
                        break;
                    }
                    case IP_DrawModeAdd: {
                        *fdest += *fsrc;
                        break;
                    }
                    case IP_DrawModeSub: {
                        *fdest -= *fsrc;
                        break;
                    }
                    case IP_DrawModeXor: {
                        *fdest = (1.0 / 255.0) *
                                 ((Int32) (255.0 * *fdest) ^
                                  (Int32) (255.0 * *fsrc));
                        break;
                    }
                    default: {
                        UT_ErrFatal ("insert_span", "unknown draw mode");
                    }
                }
                ++fsrc;
                ++fdest;
            }
            break;
        }
    }
    return;
}

/* Copy and stretch a rectangular region of an image. See the description
of IP_ScaleRect for an explanation of the parameters. */

static UT_Bool stretch_rectangle
        (IP_ImageId srcimg, IP_ImageId destimg,
         Int32 xs1, Int32 ys1, Int32 xs2, Int32 ys2,
         Int32 xd1, Int32 yd1, Int32 xd2, Int32 yd2,
         UT_Bool pyr, UT_Bool gc)
{
    scaletable  *xstab, *ystab, *xst, *yst;
    Int32       xstabsize, ystabsize, xd1clip, yd1clip, xd2clip, yd2clip,
                xd, yd, chn, dstwidth;
    Float32     *spanbuf, *span, *fsrc, tmp,
                ub_gamma_lin[256],
                f_gamma_lin[GTABSIZE],
                f_lin_gamma[GTABSIZE];
    UInt8       *ubsrc;

    UT_Bool     (*sctfunct) (Int32, Int32, Int32, Float32, Float32, Float32, Float32,
                             UT_Bool, scaletable **, Int32 *);

    /* Clip the destination rectangle at the destination image borders. */

    xd1clip = UT_MAX (UT_MIN (xd1, xd2), 0);
    yd1clip = UT_MAX (UT_MIN (yd1, yd2), 0);
    xd2clip = UT_MIN (UT_MAX (xd1, xd2), destimg->pimg.desc.width - 1);
    yd2clip = UT_MIN (UT_MAX (yd1, yd2), destimg->pimg.desc.height - 1);

    /* Allocate an intermediate buffer for the pixel data. */

    dstwidth = (xd2clip - xd1clip + 1);
    if (!(spanbuf = UT_MemTempArray (dstwidth, Float32))) {
        return UT_False;
    }
    span = spanbuf - xd1clip;

    /* Create lookup tables for quicker scaling. */

    if (pyr) {
        sctfunct = make_pyr_scaletable;
    } else {
        sctfunct = make_box_scaletable;
    }
    if (!(*sctfunct)
            (xd1clip, xd2clip,
             srcimg->pimg.desc.width - 1,
             UT_MIN (xs1, xs2) - 0.5,
             UT_MAX (xs1, xs2) + 0.5,
             UT_MIN (xd1, xd2) - 0.5,
             UT_MAX (xd1, xd2) + 0.5,
             (UT_Bool) ((xs2 < xs1) ^ (xd2 < xd1)),
             &xstab, &xstabsize) ||
        !(*sctfunct)
            (yd1clip, yd2clip,
             srcimg->pimg.desc.height - 1,
             UT_MIN (ys1, ys2) - 0.5,
             UT_MAX (ys1, ys2) + 0.5,
             UT_MIN (yd1, yd2) - 0.5,
             UT_MAX (yd1, yd2) + 0.5,
             (UT_Bool) ((ys2 < ys1) ^ (yd2 < yd1)),
             &ystab, &ystabsize)) {
        return UT_False;
    }

    if (gc) {
        /* Compute the colors for the pixels in the destination region,
           with gamma correction. */

        if (!IP_UByteGammaTable2 (srcimg->pimg.desc.gamma, ub_gamma_lin) ||
            !IP_FloatGammaTable (srcimg->pimg.desc.gamma, f_gamma_lin) ||
            !IP_FloatGammaTable (1 / destimg->pimg.desc.gamma, f_lin_gamma)) {
            return UT_False;
        }
        for (chn = 0; chn < FF_NumImgChanTypes; ++chn) {
            if (!IP_StateDrawMask[chn] ||
                !srcimg->pimg.channel[chn] ||
                !destimg->pimg.channel[chn]) {
                continue;
            }
            for (xd = xd1clip; xd <= xd2clip; ++xd) {
                span[xd] = 0.0;
            }
            yst = ystab;
            yd = yst->dest;
            while (yst - ystab < ystabsize) {
                if (yd != yst->dest) {
                    IP_GammaFloat2Float (dstwidth,
                                         spanbuf, f_lin_gamma, spanbuf);
                    insert_span (destimg, spanbuf, chn, xd1clip, xd2clip, yd);
                    for (xd = xd1clip; xd <= xd2clip; ++xd) {
                        span[xd] = 0.0;
                    }
                    yd = yst->dest;
                }
                switch (srcimg->pimg.channel[chn]) {
                    case FF_ImgFmtTypeUByte: {
                        ubsrc = FF_ImgUByteRow (&srcimg->pimg, chn, yst->src);
                        xst = xstab;
                        while (xst - xstab < xstabsize) {
                            gcorrect_UByte2 (ubsrc[xst->src], ub_gamma_lin, tmp);
                            span[xst->dest] += tmp * xst->weight * yst->weight;
                            ++xst;
                        }
                        break;
                    }
                    case FF_ImgFmtTypeFloat: {
                        fsrc = FF_ImgFloatRow (&srcimg->pimg, chn, yst->src);
                        xst = xstab;
                        while (xst - xstab < xstabsize)
                        {
                            gcorrect_Float (fsrc[xst->src], f_gamma_lin, tmp);
                            span[xst->dest] += tmp * xst->weight * yst->weight;
                            ++xst;
                        }
                        break;
                    }
                    default: {
                        UT_ErrFatal ("stretch_rectangle", "invalid channel type");
                    }
                }
                ++yst;
            }
            IP_GammaFloat2Float (dstwidth, spanbuf, f_lin_gamma, spanbuf);
            insert_span (destimg, spanbuf, chn, xd1clip, xd2clip, yd);
        }
    } else {
        /* Compute the colors for the pixels in the destination region,
           without gamma correction. */

        for (chn = 0; chn < FF_NumImgChanTypes; ++chn) {
            if (!IP_StateDrawMask[chn] ||
                !srcimg->pimg.channel[chn] ||
                !destimg->pimg.channel[chn]) {
                continue;
            }
            for (xd = xd1clip; xd <= xd2clip; ++xd) {
                span[xd] = 0.0;
            }
            yst = ystab;
            yd = yst->dest;
            while (yst - ystab < ystabsize) {
                if (yd != yst->dest) {
                    insert_span (destimg, spanbuf, chn, xd1clip, xd2clip, yd);
                    for (xd = xd1clip; xd <= xd2clip; ++xd)
                        span[xd] = 0.0;
                    yd = yst->dest;
                }
                switch (srcimg->pimg.channel[chn]) {
                    case FF_ImgFmtTypeUByte: {
                        ubsrc = FF_ImgUByteRow (&srcimg->pimg, chn, yst->src);
                        xst = xstab;
                        while (xst - xstab < xstabsize) {
                            span[xst->dest] += (1.0 / 255) *
                                ubsrc[xst->src] * xst->weight * yst->weight;
                            ++xst;
                        }
                        break;
                    }
                    case FF_ImgFmtTypeFloat: {
                        fsrc = FF_ImgFloatRow (&srcimg->pimg, chn, yst->src);
                        xst = xstab;
                        while (xst - xstab < xstabsize) {
                            span[xst->dest] +=
                                fsrc[xst->src] * xst->weight * yst->weight;
                            ++xst;
                        }
                        break;
                    }
                    default: {
                        UT_ErrFatal ("stretch_rectangle", "invalid channel type");
                    }
                }
                ++yst;
            }
            insert_span (destimg, spanbuf, chn, xd1clip, xd2clip, yd);
        }
    }
    return UT_True;
}

/***************************************************************************
 *[@e
 *      Name:           IP_ScaleRect
 *
 *      Usage:          Copy the pixels inside a rectangular source region in
 *                      an image, to a destination region of possibly different
 *                      size in another image.
 *
 *      Synopsis:       UT_Bool IP_ScaleRect
 *                              (IP_ImageId srcimg, IP_ImageId destimg,
 *                              Int32 xs1, Int32 ys1, Int32 xs2, Int32 ys2,
 *                              Int32 xd1, Int32 yd1, Int32 xd2, Int32 yd2,
 *                              UT_Bool pyr, UT_Bool gc)
 *
 *      Description:    One corner of the source region is at position
 *                      (xs1, ys1) in image "srcimg". The diagonally
 *                      opposite corner of the source region is at
 *                      position (xs2, ys2).
 *                      The corner of the destination region, which
 *                      corresponds to (xs1, ys1) in the source region,
 *                      is at position (xd1, yd1) in image "destimg".
 *                      The diagonally opposite corner of the destination
 *                      region is at position (xd2, yd2).
 *
 *                      The "pyr" flag selects the shape of the filter
 *                      mask applied to the stretched pixel data. "UT_True"
 *                      selects a pyramid-shaped mask; "UT_False" selects a
 *                      box-shaped mask.
 *                      If the "gc" flag is set, the stretched pixel data
 *                      are gamma-corrected according to the source and
 *                      destination images' gamma factors.
 *
 *                      The current drawing color, mode and mask select which
 *                      channels in the destination region are affected, and
 *                      how the data from the source region are combined with
 *                      the original data in the destination region.
 *
 *                      Note: The source and destination regions should not
 *                      be overlapping regions in the same image.
 *
 *      Return value:   UT_True if successful, otherwise UT_False.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool IP_ScaleRect
        (IP_ImageId srcimg, IP_ImageId destimg,
         Int32 xs1, Int32 ys1, Int32 xs2, Int32 ys2,
         Int32 xd1, Int32 yd1, Int32 xd2, Int32 yd2,
         UT_Bool pyr, UT_Bool gc)
{
    UT_MemState memstate;
    UT_Bool     success;

    memstate = UT_MemRemember ();
    success = stretch_rectangle
            (srcimg, destimg,
             xs1, ys1, xs2, ys2,
             xd1, yd1, xd2, yd2,
             pyr, gc);
    UT_MemRestore (memstate);
    return success;
}
