/**************************************************************************
 *{@C
 *      Copyright:      1988-2020 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         ImageProcessing
 *      Filename:       IP_TkPhoto.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Functions for transfering poSoft images into Tk photo
 *                      images and vice versa.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      IP_Img2Photo
 *                      IP_Photo2Img
 *
 **************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "UT_Compat.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"

/* #define DEBUG_LOCAL */

static UT_Bool gtableFloat (Float32 gamma, Int32 n, Float32 table[])
{
    Int32 i;

    for (i = 0; i < n - 1; ++i) {
        table[i] = pow ((Float32) i / (Float32) (n - 2), 1.0 / gamma);
    }
    table[n - 1] = 1.0;
    return UT_True;
}

static UT_Bool img_photo (IP_ImageId img, const char *photoName,
                          FF_ImgChanType chanMap[4],
                          Float32 gamma, Float32 minVal, Float32 maxVal,
                          Tcl_Interp *interp)
{
    Int32       x, y, numChans;
    Float32     *fSrc0 = NULL, *fSrc1 = NULL, *fSrc2 = NULL, *fSrc3 = NULL;
    Float32     *fpstop;
    Float32     *fDst;
    FF_ImgDesc  *pimg;
    Tk_PhotoHandle photo;
    Tk_PhotoImageBlock photoCont;
    unsigned char *pPtr, *imgBuf;
    UT_MemState memstate;

    #if defined (DEBUG_LOCAL)
        printf ("Using channel mapping: %d %d %d %d\n",
                chanMap[0], chanMap[1], chanMap[2], chanMap[3]);
    #endif

    pimg = &img->pimg;

    if (chanMap[0] == FF_ImgChanTypeNone ||
        chanMap[1] == FF_ImgChanTypeNone || 
        chanMap[2] == FF_ImgChanTypeNone ||
        pimg->channel[chanMap[0]] == FF_ImgFmtTypeNone ||
        pimg->channel[chanMap[1]] == FF_ImgFmtTypeNone || 
        pimg->channel[chanMap[2]] == FF_ImgFmtTypeNone ) {
        printf ("Invalid channel mapping (%d, %d, %d, %d)\n",
                chanMap[0], chanMap[1], chanMap[2], chanMap[3]);
        return UT_False;
    }

    if (!(photo = Tk_FindPhoto (interp, (char *)photoName))) {
        printf ("Can't find photo image %s\n", photoName);
        return UT_False;
    }

    #if (TK_MAJOR_VERSION > 8) || \
       ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION >= 5))
        Tk_PhotoSetSize (interp, photo, pimg->desc.width, pimg->desc.height);
    #else
        Tk_PhotoSetSize (photo, pimg->desc.width, pimg->desc.height);
    #endif
    numChans = (chanMap[3] != FF_ImgChanTypeNone ? 4: 3);

    memstate = UT_MemRemember ();

    /* Transfer a complete image at a time with PhotoPutBlock. */
    if (!(imgBuf = UT_MemTempArray (pimg->desc.width * pimg->desc.height * numChans,
                                     sizeof (unsigned char)))) {
        printf ("Can't alloc photo image buffer\n");
        UT_MemRestore (memstate);
        return UT_False;
    }

    photoCont.pixelSize = numChans;
    photoCont.pitch = pimg->desc.width * numChans;
    photoCont.width = pimg->desc.width;
    photoCont.height = pimg->desc.height;
    photoCont.offset[0] = 0;
    photoCont.offset[1] = 1;
    photoCont.offset[2] = 2;
    photoCont.offset[3] = (numChans == 3? 0: 3);

    photoCont.pixelPtr = imgBuf;

    if (pimg->channel[chanMap[0]] == FF_ImgFmtTypeFloat &&
        pimg->channel[chanMap[1]] == FF_ImgFmtTypeFloat &&
        pimg->channel[chanMap[2]] == FF_ImgFmtTypeFloat ) {
        /* All channels are in FLOAT format. */

        Float32 m = (1.0 - 0.0) / (maxVal - minVal);
        Float32 t = 0.0 - m * minVal;
        Float32 *fBuf;
        Float32 gtable[GTABSIZE];

        if (!(fBuf = UT_MemTempArray (pimg->desc.width * numChans,
                                      sizeof (Float32)))) {
            printf ("Can't alloc fBuf\n");
            UT_MemRestore (memstate);
            return UT_False;
        }
        gtableFloat (gamma, GTABSIZE, gtable);

        pPtr = imgBuf;
        for (y = pimg->desc.height-1; y >= 0; y--) {
            fSrc0 = IP_GetFloatRow (img, chanMap[0], y);
            fSrc1 = IP_GetFloatRow (img, chanMap[1], y);
            fSrc2 = IP_GetFloatRow (img, chanMap[2], y);
            fDst  = fBuf;
            if (numChans > 3) {
                fSrc3 = IP_GetFloatRow (img, chanMap[3], y);
            }
            fpstop = fSrc0 + pimg->desc.width;
            if (numChans == 3) {
                while (fSrc0 < fpstop) {
                    *(fDst++) = *(fSrc0++) * m + t;
                    *(fDst++) = *(fSrc1++) * m + t;
                    *(fDst++) = *(fSrc2++) * m + t;
                }
                IP_GammaFloat2UByte (numChans * pimg->desc.width, fBuf, gtable, pPtr);
                pPtr += numChans * pimg->desc.width;
            } else {
                /* TODO: Add code for alpha channel */
            }
        }
    } else if (pimg->channel[chanMap[0]] == FF_ImgFmtTypeUByte &&
               pimg->channel[chanMap[1]] == FF_ImgFmtTypeUByte &&
               pimg->channel[chanMap[2]] == FF_ImgFmtTypeUByte) {
        /* All channels are in UBYTE format. */
        UInt8 * redSrc, *greenSrc, *blueSrc, *matteSrc;
        pPtr = imgBuf;
        if (numChans == 3) {
            for (y = pimg->desc.height-1; y >= 0; y--) {
                redSrc   = FF_ImgUByteRow (pimg, chanMap[0], y);
                greenSrc = FF_ImgUByteRow (pimg, chanMap[1], y);
                blueSrc  = FF_ImgUByteRow (pimg, chanMap[2], y);
                for (x = 0; x < pimg->desc.width; x++) {
                    *(pPtr++) = *(redSrc++);
                    *(pPtr++) = *(greenSrc++);
                    *(pPtr++) = *(blueSrc++);
                }
            }
        } else {
            for (y = pimg->desc.height-1; y >= 0; y--) {
                redSrc   = FF_ImgUByteRow (pimg, chanMap[0], y);
                greenSrc = FF_ImgUByteRow (pimg, chanMap[1], y);
                blueSrc  = FF_ImgUByteRow (pimg, chanMap[2], y);
                matteSrc = FF_ImgUByteRow (pimg, chanMap[3], y);
                for (x = 0; x < pimg->desc.width; x++) {
                    *(pPtr++) = *(redSrc++);
                    *(pPtr++) = *(greenSrc++);
                    *(pPtr++) = *(blueSrc++);
                    *(pPtr++) = *(matteSrc++);
                }
            }
        }
    } else {
        printf ("Invalid channel types (%d, %d, %d, %d)\n",
                chanMap[0], chanMap[1], chanMap[2], chanMap[3]);
        return UT_False;
    }

    #if (TK_MAJOR_VERSION > 8) || \
        ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION >= 5))
        Tk_PhotoPutBlock (interp, photo, &photoCont, 0, 0,
                          pimg->desc.width, pimg->desc.height, 
                          numChans == 3? TK_PHOTO_COMPOSITE_SET: TK_PHOTO_COMPOSITE_OVERLAY);
    #elif ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION > 3))
        Tk_PhotoPutBlock (photo, &photoCont, 0, 0,
                          pimg->desc.width, pimg->desc.height,
                          numChans == 3? TK_PHOTO_COMPOSITE_SET: TK_PHOTO_COMPOSITE_OVERLAY);
    #else
        Tk_PhotoPutBlock (photo, &photoCont, 0, 0,
                          pimg->desc.width, pimg->desc.height);
    #endif

    UT_MemRestore (memstate);
    return UT_True;
}

static UT_Bool img_photo2 (IP_ImageId img, const char *photoName,
                          FF_ImgChanType chanMap[4],
                          Float32 gamma, Float32 minVal, Float32 maxVal,
                          Tcl_Interp *interp)
{
    Int32       y, chan, numChans;
    UInt8       *ub0 = NULL, *ub1 = NULL, *ub2 = NULL, *ub3 = NULL;
    UInt8       *ubstop;
    Float32     *fSrc0 = NULL, *fSrc1 = NULL, *fSrc2 = NULL, *fSrc3 = NULL;
    Float32     *fpstop;
    Float32     *fDst;
    FF_ImgDesc  *pimg;
    Tk_PhotoHandle photo;
    Tk_PhotoImageBlock photoCont;
    unsigned char *pPtr, *scanBuf;
    UInt8 *buf[FF_NumImgChanTypes]  = { NULL },
          *data[FF_NumImgChanTypes] = { NULL };
    UT_MemState memstate;

    #if defined (DEBUG_LOCAL)
        printf ("Using channel mapping: %d %d %d %d\n",
                chanMap[0], chanMap[1], chanMap[2], chanMap[3]);
    #endif

    pimg = &img->pimg;

    if (chanMap[0] == FF_ImgChanTypeNone ||
        chanMap[1] == FF_ImgChanTypeNone || 
        chanMap[2] == FF_ImgChanTypeNone ||
        pimg->channel[chanMap[0]] == FF_ImgFmtTypeNone ||
        pimg->channel[chanMap[1]] == FF_ImgFmtTypeNone || 
        pimg->channel[chanMap[2]] == FF_ImgFmtTypeNone ) {
        printf ("Invalid channel mapping (%d, %d, %d, %d)\n",
                chanMap[0], chanMap[1], chanMap[2], chanMap[3]);
        return UT_False;
    }

    if (!(photo = Tk_FindPhoto (interp, (char *)photoName))) {
        printf ("Can't find photo image %s\n", photoName);
        return UT_False;
    }

    #if (TK_MAJOR_VERSION > 8) || \
       ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION >= 5))
        Tk_PhotoSetSize (interp, photo, pimg->desc.width, pimg->desc.height);
    #else
        Tk_PhotoSetSize (photo, pimg->desc.width, pimg->desc.height);
    #endif
    numChans = (chanMap[3] != FF_ImgChanTypeNone ? 4: 3);

    memstate = UT_MemRemember ();

    if (!(scanBuf = UT_MemTempArray (pimg->desc.width * numChans,
                                     sizeof (unsigned char)))) {
        printf ("Can't alloc scanline buffer\n");
        UT_MemRestore (memstate);
        return UT_False;
    }

    for (chan=0; chan<4; chan++) {
        if (chanMap[chan] >= FF_ImgChanTypeGray) {
            if (!(buf[chanMap[chan]] = 
                        UT_MemTempArray (pimg->desc.width, sizeof (UInt8)))) {
                printf ("Can't alloc scanline buffer\n");
                UT_MemRestore (memstate);
                return UT_False;
            }
        }
    }

    photoCont.pixelSize = numChans;
    photoCont.pitch = pimg->desc.width * numChans;
    photoCont.width = pimg->desc.width;
    photoCont.height = 1;
    photoCont.offset[0] = 0;
    photoCont.offset[1] = 1;
    photoCont.offset[2] = 2;
    photoCont.offset[3] = (numChans == 3? 0: 3);

    photoCont.pixelPtr = scanBuf;

    if (pimg->channel[chanMap[0]] == FF_ImgFmtTypeFloat &&
        pimg->channel[chanMap[1]] == FF_ImgFmtTypeFloat &&
        pimg->channel[chanMap[2]] == FF_ImgFmtTypeFloat ) {
        /* All channels are in FLOAT format. */

        Float32 m = (1.0 - 0.0) / (maxVal - minVal);
        Float32 t = 0.0 - m * minVal;
        Float32 *fBuf;
        Float32 gtable[GTABSIZE];

        if (!(fBuf = UT_MemTempArray (pimg->desc.width * numChans,
                                      sizeof (Float32)))) {
            printf ("Can't alloc fBuf\n");
            UT_MemRestore (memstate);
            return UT_False;
        }
        gtableFloat (gamma, GTABSIZE, gtable);

        for (y = pimg->desc.height-1; y >= 0; y--) {
            fSrc0 = IP_GetFloatRow (img, chanMap[0], y);
            fSrc1 = IP_GetFloatRow (img, chanMap[1], y);
            fSrc2 = IP_GetFloatRow (img, chanMap[2], y);
            fDst  = fBuf;
            if (numChans > 3) {
                fSrc3 = IP_GetFloatRow (img, chanMap[3], y);
            }
            fpstop = fSrc0 + pimg->desc.width;
            pPtr = scanBuf;
            if (numChans == 3) {
                while (fSrc0 < fpstop) {
                    *(fDst++) = *(fSrc0++) * m + t;
                    *(fDst++) = *(fSrc1++) * m + t;
                    *(fDst++) = *(fSrc2++) * m + t;
                }
                IP_GammaFloat2UByte (numChans * pimg->desc.width, fBuf, gtable, pPtr);
            } else {
                /* TODO: Add code for alpha channel */
            }

            #if (TK_MAJOR_VERSION > 8) || \
                ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION >= 5))
                Tk_PhotoPutBlock (interp, photo, &photoCont, 0, pimg->desc.height -1 - y,
                                  pimg->desc.width, 1, TK_PHOTO_COMPOSITE_OVERLAY);
            #elif ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION > 3))
                Tk_PhotoPutBlock (photo, &photoCont, 0, pimg->desc.height -1 - y,
                                  pimg->desc.width, 1, TK_PHOTO_COMPOSITE_OVERLAY);
            #else
                Tk_PhotoPutBlock (photo, &photoCont, 0, pimg->desc.height -1 - y,
                                  pimg->desc.width, 1);
            #endif
        }
    } else if (pimg->channel[chanMap[0]] == FF_ImgFmtTypeUByte &&
        pimg->channel[chanMap[1]] == FF_ImgFmtTypeUByte &&
        pimg->channel[chanMap[2]] == FF_ImgFmtTypeUByte ) {
        /* All channels are in UBYTE format. */
        for (y = pimg->desc.height-1; y >= 0; y--) {
            IP_ConvertUByteRow (img, y, buf, data);
            ub0 = data[chanMap[0]];
            ub1 = data[chanMap[1]];
            ub2 = data[chanMap[2]];
            if (numChans > 3) {
                ub3 = data[chanMap[3]];
            }
            ubstop = ub0 + pimg->desc.width;
            pPtr = scanBuf;
            if (numChans == 3) {
                while (ub0 < ubstop) {
                    *(pPtr++) = *(ub0++);
                    *(pPtr++) = *(ub1++);
                    *(pPtr++) = *(ub2++);
                }
            } else {
                while (ub0 < ubstop) {
                    *(pPtr++) = *(ub0++);
                    *(pPtr++) = *(ub1++);
                    *(pPtr++) = *(ub2++);
                    *(pPtr++) = *(ub3++);
                }
            }
            #if (TK_MAJOR_VERSION > 8) || \
                ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION >= 5))
                Tk_PhotoPutBlock (interp, photo, &photoCont, 0, pimg->desc.height -1 - y,
                                  pimg->desc.width, 1, TK_PHOTO_COMPOSITE_OVERLAY);
            #elif ((TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION > 3))
                Tk_PhotoPutBlock (photo, &photoCont, 0, pimg->desc.height -1 - y,
                                  pimg->desc.width, 1, TK_PHOTO_COMPOSITE_OVERLAY);
            #else
                Tk_PhotoPutBlock (photo, &photoCont, 0, pimg->desc.height -1 - y,
                                  pimg->desc.width, 1);
            #endif
        }
    } else {
        printf ("Invalid channel types (%d, %d, %d, %d)\n",
                chanMap[0], chanMap[1], chanMap[2], chanMap[3]);
        return UT_False;
    }

    UT_MemRestore (memstate);
    return UT_True;
}

static IP_ImageId photo_img (const char *photoName, Tcl_Interp *interp)
{
    Int32              x, y, w, h, numChans;
    UInt8              *redDest, *greenDest, *blueDest, *matteDest;
    FF_ImgDesc         *pimg;
    IP_ImageId         img;
    FF_ImgFmtType      pixfmt[FF_NumImgChanTypes] = { FF_ImgFmtTypeNone };
    FF_ImgFmtType      savepixfmt[FF_NumImgChanTypes];
    Tk_PhotoHandle     photo;
    Tk_PhotoImageBlock photoCont;
    unsigned char      *pPtr;

    if (!(photo = Tk_FindPhoto (interp, (char *)photoName))) {
        printf ("Can't find photo image %s\n", photoName);
        return NULL;
    }
    Tk_PhotoGetSize (photo, &w, &h);
    Tk_PhotoGetImage (photo, &photoCont);
    numChans = photoCont.pixelSize;
    w = photoCont.width;
    h = photoCont.height;
    pPtr = photoCont.pixelPtr;

    /* Only red, green, blue and alpha channels are currently supported. */
    IP_GetFormat (savepixfmt);
    pixfmt[(int)FF_ImgChanTypeRed]   = FF_ImgFmtTypeUByte;
    pixfmt[(int)FF_ImgChanTypeGreen] = FF_ImgFmtTypeUByte;
    pixfmt[(int)FF_ImgChanTypeBlue]  = FF_ImgFmtTypeUByte;
    if ( numChans > 3 ) {
        pixfmt[(int)FF_ImgChanTypeMatte] = FF_ImgFmtTypeUByte;
    }
    IP_SetFormat (pixfmt);

    if (!(img = IP_ImageNew (w, h, (float)w / (float)h))) {
        IP_SetFormat (savepixfmt);
        return NULL;
    }

    IP_SetFormat (savepixfmt);
    pimg = &img->pimg;

    if (numChans == 3) {
        for (y = h-1; y >= 0; y--) {
            redDest   = FF_ImgUByteRow (pimg, FF_ImgChanTypeRed,   y);
            greenDest = FF_ImgUByteRow (pimg, FF_ImgChanTypeGreen, y);
            blueDest  = FF_ImgUByteRow (pimg, FF_ImgChanTypeBlue,  y);
            for (x = 0; x < w; x++) {
                *redDest++   = *pPtr++;
                *greenDest++ = *pPtr++;
                *blueDest++  = *pPtr++;
            }
        }
    } else {
        for (y = h-1; y >= 0; y--) {
            redDest   = FF_ImgUByteRow (pimg, FF_ImgChanTypeRed,   y);
            greenDest = FF_ImgUByteRow (pimg, FF_ImgChanTypeGreen, y);
            blueDest  = FF_ImgUByteRow (pimg, FF_ImgChanTypeBlue,  y);
            matteDest = FF_ImgUByteRow (pimg, FF_ImgChanTypeMatte, y);
            for (x = 0; x < w; x++) {
                *redDest++   = *pPtr++;
                *greenDest++ = *pPtr++;
                *blueDest++  = *pPtr++;
                *matteDest++ = *pPtr++;
            }
        }
    }
    return img;
}

/***************************************************************************
 *[@e
 *      Name:           IP_Img2Photo
 *
 *      Usage:          Copy an image into a TK photo image.
 *
 *      Synopsis:       UT_Bool IP_Img2Photo 
 *                              (IP_ImageId img,
 *                               const char *photoName,
 *                               FF_ImgChanType chanMap[4],
 *                               Float32 gamma, 
 *                               Float32 minVal, Float32 maxVal,
 *                               Tcl_Interp *interp)
 *
 *      Description:    Copy an image into a TK photo image.
 *
 *      Return value:   UT_True if succesful, else UT_False.
 *
 *      See also:       IP_Photo2Img
 *
 ***************************************************************************/

UT_Bool IP_Img2Photo (IP_ImageId img, const char *photoName,
                      FF_ImgChanType chanMap[4],
                      Float32 gamma, Float32 minVal, Float32 maxVal,
                      Tcl_Interp *interp)
{
    UT_Bool success;

    success = img_photo (img, photoName, chanMap,
                         gamma, minVal, maxVal, interp);
    return success;
}

/***************************************************************************
 *[@e
 *      Name:           IP_Photo2Img
 *
 *      Usage:          Copy a TK photo image into a poImage.
 *
 *      Synopsis:       IP_ImageId IP_Photo2Img (const char *photoName,
 *                                               Tcl_Interp *interp)
 *
 *      Description:    Copy a TK photo image into a poImage.
 *
 *      Return value:   An identifier for the new image if successful,
 *                      else NULL.
 *
 *      See also:       IP_Img2Photo
 *
 ***************************************************************************/

IP_ImageId IP_Photo2Img (const char *photoName, Tcl_Interp *interp)
{
    IP_ImageId img;

    img = photo_img (photoName, interp);
    return img;
}
