/**************************************************************************
 *{@C
 *      Copyright:      1988-2024 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         ImageProcessing
 *      Filename:       IP_DrawAntiAliased.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Functions to draw antialiased points, lines
 *                      and texts strings.
 *
 *                      Note: Antialiased lines are drawn using an improved
 *                      version of the smooth vector generator algorithm by
 *                      Akira Fujimoto and Kansei Iwata. For a detailed
 *                      description of the algorithm, see
 *                              
 *                          Tosiyasu L. Kunii (Editor),
 *                          "Computer Graphics / Theory and Applications",
 *                          Springer-Verlag, 1983,
 *                          pp. 2 - 15.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      IP_DrawAAPixel
 *                      IP_DrawAALine
 *                      IP_DrawAAText
 *
 **************************************************************************/

#include <stdio.h>

#include "UT_Compat.h"
#include "UT_Const.h"
#include "UT_Macros.h"
#include "UT_Error.h"
#include "UT_Image.h"

#define  USE_IMG_MACROS
#include "FF_Image.h"

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


static FF_ImgDesc *pimg;                /* Image into which we draw */

static Float32  pimg_gamma = 0.0,       /* Gamma of pimg */
                ub_to_lin[256],         /* Gamma tables for drawing */
                f_to_lin[GTABSIZE],
                lin_to_f[GTABSIZE];

static UT_Bool  quickdraw;              /* UT_True if quick drawing is possible */

static Int32    firstchn,               /* First channel affected by drawing */
                lastchn,                /* Last channel affected by drawing */
                first_cchn,             /* First color channel drawn into */
                last_cchn,              /* Last color channel drawn into */
                first_mchn,             /* First matte channel drawn into */
                last_mchn;              /* Last matte channel drawn into */


/* Define the "current image", i.e. the image into which function
   "draw_pixel" draws. Initialize some lookup tables so that "draw_pixel"
   can merge the drawing color and the background color with proper
   gamma-correction. */

static void set_image (IP_ImageId img)
{
    if (img->pimg.desc.gamma != pimg_gamma) {
        if (img->pimg.desc.gamma == 1.0) {
             quickdraw = UT_True;
        } else {
            quickdraw = UT_False;
            IP_UByteGammaTable2 (img->pimg.desc.gamma, ub_to_lin);
            IP_FloatGammaTable (img->pimg.desc.gamma, f_to_lin);
            IP_FloatGammaTable (1.0 / img->pimg.desc.gamma, lin_to_f);
        }
        pimg_gamma = img->pimg.desc.gamma;
    }
    pimg = &img->pimg;
    firstchn = 0;
    while (firstchn < FF_NumImgChanTypes) {
        if (IP_StateDrawMask[firstchn] && pimg->channel[firstchn])
            break;
        ++firstchn;
    }
    lastchn = FF_NumImgChanTypes - 1;
    while (lastchn >= 0) {
        if (IP_StateDrawMask[lastchn] && pimg->channel[lastchn])
            break;
        --lastchn;
    }
    first_cchn = UT_MAX (firstchn, FF_ImgChanTypeGray);
    last_cchn  = UT_MIN (lastchn,  FF_ImgChanTypeBlue);
    first_mchn = UT_MAX (firstchn, FF_ImgChanTypeMatte);
    last_mchn  = UT_MIN (lastchn,  FF_ImgChanTypeBlueMatte);
    return;
}

/* Draw a pixel at position (x - 1, y - 1) in the current image.
   For every color channel, "chn", enabled by the current drawing mask,
   increase channel "chn" of pixel (x - 1, y - 1) by (clr[chn] * weight).
   For matte channels, replace the original value, o[chn], with
   (1 - (1 - o[chn]) * (1 - clr[chn])). */

static void draw_pixel (Int32 x, Int32 y, const Float32 clr[], Float32 weight)
{
    Float32     *fpixel, ftmp;
    UInt8       *ubpixel;
    Int32       chn, itmp;

    if (x < 1 || x > pimg->desc.width ||
        y < 1 || y > pimg->desc.height) {
        return;
    }
    --x;
    --y;
    if (quickdraw) {
        for (chn = first_cchn; chn <= last_cchn; ++chn) {
            if (!IP_StateDrawMask[chn]) {
                continue;
            }
            switch (pimg->channel[chn]) {
                case FF_ImgFmtTypeNone: {
                    break;
                }
                case FF_ImgFmtTypeUByte: {
                    ubpixel = FF_ImgUBytePixel (pimg, chn, x, y);
                    itmp = *ubpixel + (Int32) (255.0 * weight * clr[chn] + 0.5);
                    if (itmp > 255) {
                        *ubpixel = 255;
                    } else if (itmp < 0) {
                        *ubpixel = 0;
                    } else {
                        *ubpixel = itmp;
                    }
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    *FF_ImgFloatPixel (pimg, chn, x, y) += weight * clr[chn];
                    break;
                }
            }
        }
    } else {
        for (chn = first_cchn; chn <= last_cchn; ++chn) {
            if (!IP_StateDrawMask[chn]) {
                continue;
            }
            switch (pimg->channel[chn]) {
                case FF_ImgFmtTypeNone: {
                    break;
                }
                case FF_ImgFmtTypeUByte: {
                    ubpixel = FF_ImgUBytePixel (pimg, chn, x, y);
                    gcorrect_UByte2 (*ubpixel, ub_to_lin, ftmp);
                    ftmp += weight * clr[chn];
                    if (ftmp < 0.0) {
                        ftmp = 0.0;
                    } else if (ftmp > 1.0) {
                        ftmp = 1.0;
                    }
                    gcorrect_Float (ftmp, lin_to_f, ftmp);
                    *ubpixel = (Int32) (ftmp * 255.0 + 0.5);
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    fpixel = FF_ImgFloatPixel (pimg, chn, x, y);
                    gcorrect_Float (*fpixel, f_to_lin, ftmp);
                    ftmp += weight * clr[chn];
                    if (ftmp < 0.0) {
                        ftmp = 0.0;
                    } else if (ftmp > 1.0) {
                        ftmp = 1.0;
                    }
                    gcorrect_Float (ftmp, lin_to_f, *fpixel);
                    break;
                }
            }
        }
    }
    for (chn = first_mchn; chn <= last_mchn; ++chn) {
        if (!IP_StateDrawMask[chn]) {
            continue;
        }
        switch (pimg->channel[chn]) {
            case FF_ImgFmtTypeNone: {
                break;
            }
            case FF_ImgFmtTypeUByte: {
                ubpixel = FF_ImgUBytePixel (pimg, chn, x, y);
                itmp = (Int32) (0.5 + 255.0 * (1.0 -
                                      (1.0 - *ubpixel * (1.0 / 255.0)) *
                                      (1.0 - clr[chn] * weight)));
                if (itmp > 255) {
                    *ubpixel = 255;
                } else if (itmp < 0) {
                    *ubpixel = 0;
                } else {
                    *ubpixel = itmp;
                }
                break;
            }
            case FF_ImgFmtTypeFloat: {
                fpixel = FF_ImgFloatPixel (pimg, chn, x, y);
                *fpixel = 1.0 - (1.0 - *fpixel) * (1.0 - clr[chn] * weight);
                break;
            }
        }
    }
    return;
}

/* Draw an antialiased point with color "clr" at position (x - 1, y - 1). */

static void aa_point (Float32 x, Float32 y, const Float32 clr[])
{
    Float32 sx, tx, sy, ty;

    sx = x - (Int32) x;
    tx = 1.0 - sx;
    sy = y - (Int32) y;
    ty = 1.0 - sy;
    draw_pixel ((Int32) x,     (Int32) y,     clr, tx * ty);
    draw_pixel ((Int32) x + 1, (Int32) y,     clr, sx * ty);
    draw_pixel ((Int32) x,     (Int32) y + 1, clr, tx * sy);
    draw_pixel ((Int32) x + 1, (Int32) y + 1, clr, sx * sy);
    return;
}

/* Draw an antialiased line from position (xs - 1, ys - 1) to position
   (xe - 1, ye - 1), using Fujimoto's and Iwata's algorithm. Interpolate
   the line's color linearly from "clr1" at (xs - 1, ys - 1) to "clr2"
   at (xe - 1, ye - 1). */

static void aa_line
        (Float32 xs, Float32 ys, Float32 xe, Float32 ye,
         const Float32 clr1[], const Float32 clr2[])
{
    Int32               xsi, ysi, xei, yei, sdx, sdy;
    Float32             q, a, u, v, dx, dy, adx, ady,
                        clr[FF_NumImgChanTypes], dclr[FF_NumImgChanTypes],
                        *dst1, *dst2, *stop;
    const Float32       *src1, *src2;

    xsi = (Int32) (xs + 0.5);
    ysi = (Int32) (ys + 0.5);
    xei = (Int32) (xe + 0.5);
    yei = (Int32) (ye + 0.5);
    dx = xe - xs;
    dy = ye - ys;
    sdx = UT_SGN (dx);
    sdy = UT_SGN (dy);
    adx = UT_ABS (dx);
    ady = UT_ABS (dy);
    if (adx > ady) {
        src1 = clr1 + firstchn;
        src2 = clr2 + firstchn;
        dst1 = clr + firstchn;
        stop = clr + lastchn;
        if (adx < 1.0) {
            q = adx * 0.5;
            while (dst1 <= stop)
                *dst1++ = (*src1++ + *src2++) * q;
            aa_point ((xs + xe) * 0.5, (ys + ye) * 0.5, clr);
            return;
        }
        q = 1.0 / adx;
        dst2 = dclr + firstchn;
        while (dst1 <= stop) {
            *dst1++ = *src1;
            *dst2++ = (*src2++ - *src1++) * q;
        }
        q = dy / dx;
        a = q * (xsi - xs) + ys;
        if (a < ysi)
            --ysi;
        u = a - ysi;
        if (sdx > 0) {
            v = xsi + 0.5 - xs;
        } else {
            v = xs - xsi + 0.5;
            q = -q;
        }
        draw_pixel (xsi, ysi + 1, clr, u * v);
        draw_pixel (xsi, ysi, clr, (1.0 - u) * v);
        src1 = dclr + firstchn;
        dst1 = clr + firstchn;
        stop = clr + lastchn;
        while (dst1 <= stop)
            *dst1++ += *src1++;
        u += q;
        if (u >= 1.0) {
            ysi += sdy;
            u -= 1.0;
        } else if (u < 0.0) {
            ysi += sdy;
            u += 1.0;
        }
        xsi += sdx;
        while (xsi != xei) {
            draw_pixel (xsi, ysi + 1, clr, u);
            draw_pixel (xsi, ysi, clr, 1.0 - u);
            src1 = dclr + firstchn;
            dst1 = clr + firstchn;
            stop = clr + lastchn;
            while (dst1 <= stop)
                *dst1++ += *src1++;
            u += q;
            if (u >= 1.0) {
                ysi += sdy;
                u -= 1.0;
            } else if (u < 0.0) {
                ysi += sdy;
                u += 1.0;
            }
            xsi += sdx;
        }
        if (sdx > 0)
            v = xe - xei + 0.5;
        else
            v = xei + 0.5 - xe;
        draw_pixel (xsi, ysi + 1, clr, u * v);
        draw_pixel (xsi, ysi, clr, (1.0 - u) * v);
    } else {
        src1 = clr1 + firstchn;
        src2 = clr2 + firstchn;
        dst1 = clr + firstchn;
        stop = clr + lastchn;
        if (ady < 1.0) {
            q = ady * 0.5;
            while (dst1 <= stop)
                *dst1++ = (*src1++ + *src2++) * q;
            aa_point ((xs + xe) * 0.5, (ys + ye) * 0.5, clr);
            return;
        }
        q = 1.0 / ady;
        dst2 = dclr + firstchn;
        while (dst1 <= stop) {
            *dst1++ = *src1;
            *dst2++ = (*src2++ - *src1++) * q;
        }
        q = dx / dy;
        a = q * (ysi - ys) + xs;
        if (a < xsi)
            --xsi;
        u = a - xsi;
        if (sdy > 0) {
            v = ysi + 0.5 - ys;
        } else {
            v = ys - ysi + 0.5;
            q = -q;
        }
        draw_pixel (xsi + 1, ysi, clr, u * v);
        draw_pixel (xsi, ysi, clr, (1.0 - u) * v);
        src1 = dclr + firstchn;
        dst1 = clr + firstchn;
        stop = clr + lastchn;
        while (dst1 <= stop)
            *dst1++ += *src1++;
        u += q;
        if (u >= 1.0) {
            xsi += sdx;
            u -= 1.0;
        } else if (u < 0.0) {
            xsi += sdx;
            u += 1.0;
        }
        ysi += sdy;
        while (ysi != yei) {
            draw_pixel (xsi + 1, ysi, clr, u);
            draw_pixel (xsi, ysi, clr, 1.0 - u);
            src1 = dclr + firstchn;
            dst1 = clr + firstchn;
            stop = clr + lastchn;
            while (dst1 <= stop)
                *dst1++ += *src1++;
            u += q;
            if (u >= 1.0) {
                xsi += sdx;
                u -= 1.0;
            } else if (u < 0.0) {
                xsi += sdx;
                u += 1.0;
            }
            ysi += sdy;
        }
        if (sdy > 0)
            v = ye - yei + 0.5;
        else
            v = yei + 0.5 - ye;
        draw_pixel (xsi + 1, ysi, clr, u * v);
        draw_pixel (xsi, ysi, clr, (1.0 - u) * v);
    }
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_DrawAAPixel
 *
 *      Usage:          Draw an antialiased point.
 *
 *      Synopsis:       void IP_DrawAAPixel
 *                              (IP_ImageId img,
 *                              Float32 x, Float32 y,
 *                              const Float32 clr[])
 *
 *      Description:    A point is drawn at position (x, y) in image "img". The
 *                      units for "x" and "y" are width and height of a pixel
 *                      in "img".
 *                      The point's color is taken from "clr". The current
 *                      drawing mask selects which channels in "img" will be
 *                      affected by IP_DrawAAPixel. The current drawing mode
 *                      is ignored; IP_DrawModeAdd mode is used instead.
 *
 *      Return value:   None.
 *
 *      See also:       IP_DrawPixel
 *                      IP_DrawAALine
 *
 ***************************************************************************/

void IP_DrawAAPixel (IP_ImageId img, Float32 x, Float32 y, const Float32 clr[])
{
    set_image (img);
    if (x < -1.0 || x > img->pimg.desc.width ||
        y < -1.0 || y > img->pimg.desc.height)
        return;
    aa_point (x + 1, y + 1, clr);
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_DrawAALine
 *
 *      Usage:          Draw an antialiased line.
 *
 *      Synopsis:       void IP_DrawAALine
 *                              (IP_ImageId img,
 *                              Float32 x1, Float32 y1,
 *                              Float32 x2, Float32 y2,
 *                              const Float32 clr1[],
 *                              const Float32 clr2[])
 *
 *      Description:    A line is drawn from position (x1, y1) to position
 *                      (x2, y2) in image "img". The unit for "x1" and "x2"
 *                      is the width of a pixel in "img"; the unit for "y1"
 *                      and "y2" is the height of a pixel.
 *                      The line's colors at (x1, y1) and (x2, y2) are taken
 *                      from "clr1" and "clr2"; the color at in-between
 *                      positions is interpolated linearly.
 *                      The current drawing mask selects which channels in
 *                      "img" will be affected by IP_DrawAALine. The current
 *                      drawing mode is ignored; IP_DrawModeAdd mode is used
 *                      instead.
 *
 *      Return value:   None.
 *
 *      See also:       IP_DrawLine
 *                      IP_DrawAAPixel
 *
 ***************************************************************************/

void IP_DrawAALine
        (IP_ImageId img,
         Float32 x1, Float32 y1,
         Float32 x2, Float32 y2,
         const Float32 clr1[],
         const Float32 clr2[])
{
    Float32     xclipmin, xclipmax, yclipmin, yclipmax,
                s, t, dx, dy, *c1, *c2,
                clipclr1[FF_NumImgChanTypes], clipclr2[FF_NumImgChanTypes];
    Int32       chn;
    UT_Bool     swapped;

    /* Copy "clr1" and "clr2" into a temporary buffer for clipping. */

    for (chn = 0; chn < FF_NumImgChanTypes; ++chn) {
        clipclr1[chn] = clr1[chn];
        clipclr2[chn] = clr2[chn];
    }
    c1 = clipclr1;
    c2 = clipclr2;

    /* Clip the line along the bottom and top, and then at the left and
    right borders of the image; clip the colors for the end points of the
    line accordingly. */

    xclipmin = -1.0;
    xclipmax = (Float32)img->pimg.desc.width;
    yclipmin = -1.0;
    yclipmax = (Float32)img->pimg.desc.height;
    if (y1 > y2) {
        swapped = UT_True;
        UT_SWAP (c1, c2, Float32 *);
        UT_SWAP (y1, y2, Float32);
        UT_SWAP (x1, x2, Float32);
    } else {
        swapped = UT_False;
    }
    if (y1 > yclipmax) {
        return;
    } else if (y2 > yclipmax) {
        s = y2 - yclipmax;
        t = yclipmax - y1;
        dy = y2 - y1;
        x2 = (x1 * s + x2 * t) / dy;
        y2 = yclipmax;
        for (chn = 0; chn < FF_NumImgChanTypes; ++chn)
            c2[chn] = (c1[chn] * s + c2[chn] * t) / dy;
    }
    if (y2 < yclipmin) {
        return;
    } else if (y1 < yclipmin) {
        s = y2 - yclipmin;
        t = yclipmin - y1;
        dy = y2 - y1;
        x1 = (x1 * s + x2 * t) / dy;
        y1 = yclipmin;
        for (chn = 0; chn < FF_NumImgChanTypes; ++chn)
            c1[chn] = (c1[chn] * s + c2[chn] * t) / dy;
    }
    if (x1 > x2) {
        swapped = !swapped;
        UT_SWAP (c1, c2, Float32 *);
        UT_SWAP (y1, y2, Float32);
        UT_SWAP (x1, x2, Float32);
    }
    if (x1 > xclipmax) {
        return;
    } else if (x2 > xclipmax) {
        s = x2 - xclipmax;
        t = xclipmax - x1;
        dx = x2 - x1;
        y2 = (y1 * s + y2 * t) / dx;
        x2 = xclipmax;
        for (chn = 0; chn < FF_NumImgChanTypes; ++chn)
            c2[chn] = (c1[chn] * s + c2[chn] * t) / dx;
    }
    if (x2 < xclipmin) {
        return;
    } else if (x1 < xclipmin) {
        s = x2 - xclipmin;
        t = xclipmin - x1;
        dx = x2 - x1;
        y1 = (y1 * s + y2 * t) / dx;
        x1 = xclipmin;
        for (chn = 0; chn < FF_NumImgChanTypes; ++chn)
            c1[chn] = (c1[chn] * s + c2[chn] * t) / dx;
    }

    /* Draw the line if at least part of it remained after clipping. */

    if (swapped) {
        UT_SWAP (c1, c2, Float32 *);
        UT_SWAP (y1, y2, Float32);
        UT_SWAP (x1, x2, Float32);
    }
    set_image (img);
    aa_line (x1 + 1, y1 + 1, x2 + 1, y2 + 1, c1, c2);
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_DrawAAText
 *
 *      Usage:          Draw an antialiased text string.
 *
 *      Synopsis:       void IP_DrawAAText
 *                              (IP_ImageId img,
 *                              Float32 x, Float32 y,
 *                              const char str[],
 *                              const Float32 clr[])
 *
 *      Description:    A text string, "str", is drawn into image "img". The
 *                      lower left corner of the first character in "str" is
 *                      drawn at position (x, y).
 *                      The text string's color is taken from "clr". The
 *                      current drawing mask selects which channels in "img"
 *                      will be affected by IP_DrawAAText. The current
 *                      drawing mode is ignored; IP_DrawModeAdd mode is used
 *                      instead.
 *
 *      Return value:   None.
 *
 *      See also:       IP_DrawText
 *
 ***************************************************************************/

void IP_DrawAAText
        (IP_ImageId img,
         Float32 x, Float32 y,
         const char str[],
         const Float32 clr[])
{
    Float32     xletter, yletter, xstart=0, ystart=0, xend, yend;
    const char  *letter;
    const UInt8 *shape;

    xletter = x;
    yletter = y + (Float32) (13 << IP_TextScale);
    letter = str;
    while (*letter) {
        shape = vecf_string (*letter);
        while (*shape) {
            xend = xletter + (Float32) (vecf_x (shape) << IP_TextScale);
            yend = yletter - (Float32) (vecf_y (shape) << IP_TextScale);
            if (vecf_draw (shape))
                IP_DrawAALine (img, xstart, ystart, xend, yend, clr, clr);
            xstart = xend;
            ystart = yend;
            ++shape;
        }
        xletter += (Float32) (8 << IP_TextScale);
        ++letter;
    }
    return;
}
