/**************************************************************************
 *{@C
 *      Copyright:      1988-2023 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         ImageProcessing
 *      Filename:       IP_Info.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Various functions to obtain information about images.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      IP_DiffChannel
 *                      IP_Histogram
 *                      IP_GetImageInfo
 *                      IP_GetFileInfo
 *                      IP_GetChanStats
 *                      IP_GetChanRange
 *                      IP_GetDepthRange
 *                      IP_GetRadianceRange
 *                      IP_GetTemperatureRange
 *
 **************************************************************************/

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

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

/* Generate a histogram of channel "chn" of image "img". 
   The histogram information is returned in "buf". 
*/

static void histo (IP_ImageId img, FF_ImgChanType chn, Int32 buf[256])
{
    UInt8 *ubdata, *ubstop;
    Float32 *fdata,  *fstop;
    Int32  y, i;

    for (i = 0; i < 256; i++)
        buf[i] = 0;

    switch (img->pimg.channel[chn]) { 
        case FF_ImgFmtTypeNone: {
            break;
        }
        case FF_ImgFmtTypeUByte: {
            for (y = 0; y < img->pimg.desc.height; ++y) {
                ubdata = FF_ImgUByteRow (&img->pimg, chn, y);
                ubstop = ubdata + img->pimg.desc.width;
                while (ubdata < ubstop) {
                    buf[*ubdata]++;
                    ++ubdata;
                }
            }
            break;
        }
        case FF_ImgFmtTypeFloat: {
            for (y = 0; y < img->pimg.desc.height; ++y) {
                fdata = FF_ImgFloatRow (&img->pimg, chn, y);
                fstop = fdata + img->pimg.desc.width;
                while (fdata < fstop) {
                    buf[UT_MIN (255, UT_MAX (0, (int)(*fdata * 255)))]++;
                    ++fdata;
                }
            }
            break;
        }
    }
    return;
}

/* Scan images "img1" and "img2" to see how similar they are. */

static void diff_channel (IP_ImageId img1, IP_ImageId img2, FF_ImgChanType chn,
                          Int32 less1[256], Int32 less2[256])
{
    UInt8 *pix1, *pix2, *stop;
    Int32         y, i, diff;

    for (i = 0; i < 256; i++) {
        less1[i] = 0;
        less2[i] = 0;
    }

    for (y=0; y<img1->pimg.desc.height; y++) {
        pix1 = FF_ImgUByteRow (&img1->pimg, chn, y);
        pix2 = FF_ImgUByteRow (&img2->pimg, chn, y);
        stop = pix1 + img1->pimg.desc.width;
        while (pix1 < stop) {
            if ((diff = *pix2 - *pix1) >= 0)
                less1[diff]++;
            else
                less2[-diff]++;
            pix1++;
            pix2++;
        }
    }
    return;
}

/* Scan channel "chan" of image "image" and return the arithmetic mean value
 * and the standard deviation.
 * The function does not check, if channel "chan" and the matte channel
 * are available and in the correct format.
 * This is in the responsibility of the calling procedure. 
 * See IP_GetChanStats for a detailled description.
 */

static UT_Bool getChanStats (IP_ImageId img, FF_ImgChanType chan,
                             Float32 *meanVal, Float32 *stdDev,
                             UInt32 *numPix, UT_Bool useMatte)
{
    Int32 y, width, height;
    Float32 *startChan = NULL, *stopChan = NULL;
    Float32 *startMatte = NULL;
    Float32 *tmpMatte, *tmpChan;
    FF_ImgDesc *pimg;
    UInt32  count = 0;
    Float64 sum = 0.0;

    width  = img->pimg.desc.width;
    height = img->pimg.desc.height;
    pimg   = &img->pimg;

    if (!(tmpChan  = UT_MemTempArray (width, Float32)) ||
        !(tmpMatte = UT_MemTempArray (width, Float32))) {
        return UT_False;
    }

    for (y=0; y<height; y++) {
        if (useMatte) {
            switch (pimg->channel[FF_ImgChanTypeMatte]) {
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (pimg, FF_ImgChanTypeMatte, y),
                         tmpMatte);
                    startMatte = tmpMatte;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    startMatte = FF_ImgFloatRow (pimg, FF_ImgChanTypeMatte, y);
                    break;
                }
                default: {
                    UT_ErrFatal ("getChanStats", "No matte channel");
                    break;
                }
            }
        }
        switch (pimg->channel[chan]) {
            case FF_ImgFmtTypeUByte: {
                IP_CopyUByte2FloatAsIs
                    (width,
                     FF_ImgUByteRow (pimg, chan, y),
                     tmpChan);
                startChan = tmpChan;
                break;
            }
            case FF_ImgFmtTypeFloat: {
                startChan = FF_ImgFloatRow (pimg, chan, y);
                break;
            }
            default: {
                UT_ErrFatal ("getChanStats", "No valid channel");
                break;
            }
        }
        stopChan = startChan + width;
        if (useMatte) {
            while (startChan < stopChan) {
                if (*startMatte != 0.0) {
                    sum += (Float64)(*startChan * *startMatte);
                    count++;
                }
                startChan++;
                startMatte++;
            }
        } else {
            while (startChan < stopChan) {
                sum += (Float64)*startChan;
                startChan++;
            }
            count += width;
        }
    }
    *meanVal = (Float32) (sum / count);
    *numPix = count;

    sum = 0.0;
    for (y=0; y<height; y++) {
        if (useMatte) {
            switch (pimg->channel[FF_ImgChanTypeMatte]) {
                case FF_ImgFmtTypeUByte: {
                    IP_CopyUByte2Float
                        (width,
                         FF_ImgUByteRow (pimg, FF_ImgChanTypeMatte, y),
                         tmpMatte);
                    startMatte = tmpMatte;
                    break;
                }
                case FF_ImgFmtTypeFloat: {
                    startMatte = FF_ImgFloatRow (pimg, FF_ImgChanTypeMatte, y);
                    break;
                }
                default: {
                    UT_ErrFatal ("getChanStats", "No matte channel");
                    break;
                }
            }
        }
        switch (pimg->channel[chan]) {
            case FF_ImgFmtTypeUByte: {
                IP_CopyUByte2FloatAsIs
                    (width,
                     FF_ImgUByteRow (pimg, chan, y),
                     tmpChan);
                startChan = tmpChan;
                break;
            }
            case FF_ImgFmtTypeFloat: {
                startChan = FF_ImgFloatRow (pimg, chan, y);
                break;
            }
            default: {
                UT_ErrFatal ("getChanStats", "No valid channel");
                break;
            }
        }
        stopChan = startChan + width;
        if (useMatte) {
            while (startChan < stopChan) {
                if (*startMatte != 0.0) {
                    sum += UT_SQR (*meanVal - (Float64)(*startChan * *startMatte));
                }
                startChan++;
                startMatte++;
            }
        } else {
            while (startChan < stopChan) {
                sum += UT_SQR (*meanVal - (Float64)*startChan);
                startChan++;
            }
        }
    }
    if (count == 1) {
        *stdDev = (Float32) sum;
    } else {
        *stdDev = (Float32) sqrt (sum / (count-1));
    }

    UT_MemFree (tmpChan);
    UT_MemFree (tmpMatte);
    return UT_True;
}

/* Scan channel "chan" of image "image" and return the minimum and maximum 
 * values in "minVal", resp. "maxVal". 
 * The channel has to be in UInt8 format.
 * The function does not check, if channel "chan" is of type FF_ImgFmtTypeUByte.
 * This is in the responsibility of the calling procedure. 
 */

static void getUByteRange (IP_ImageId img, FF_ImgChanType chan,
                           UInt8 *minVal, UInt8 *maxVal)
{
    Int32 y, width, height;
    UInt8 low, high;
    UInt8 *start, *stop;
    FF_ImgDesc *pimg;

    width  = img->pimg.desc.width;
    height = img->pimg.desc.height;
    low  = MaxUInt8;
    high = 0;
    pimg = &img->pimg;

    for (y=0; y<height; y++) {
        start = FF_ImgUByteRow (pimg, chan, y); 
        stop  = start + width;
        while (start < stop) {
            if (*start > high) {
                high = *start;
            } else if (*start < low) {
                low = *start;
            }
            start++;
        }
    }
    *minVal = low;
    *maxVal = high;
    return;
}

/* Scan channel "chan" of image "image" and return the minimum and maximum 
 * values in "minVal", resp. "maxVal". 
 * The channel has to be in Float32 format.
 * The function does not check, if channel "chan" is of type FF_ImgFmtTypeFloat.
 * This is in the responsibility of the calling procedure. 
 */

static void getFloatRange (IP_ImageId img, FF_ImgChanType chan,
                           Float32 *minVal, Float32 *maxVal)
{
    Int32 y, width, height;
    Float32 low, high;
    Float32 *start, *stop;
    FF_ImgDesc *pimg;

    width  = img->pimg.desc.width;
    height = img->pimg.desc.height;
    low  = MaxFloat32;
    high = -MaxFloat32;
    pimg = &img->pimg;

    for (y=0; y<height; y++) {
        start = FF_ImgFloatRow (pimg, chan, y); 
        stop  = start + width;
        while (start < stop) {
            if (*start > high) {
                high = *start;
            } else if (*start < low) {
                low = *start;
            }
            start++;
        }
    }
    *minVal = low;
    *maxVal = high;
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_Histogram
 *
 *      Usage:          Obtain a histogram of one channel of an image.  
 *
 *      Synopsis:       UT_Bool IP_Histogram
 *                              (IP_ImageId img, FF_ImgChanType chn, Int32 buf[256])
 *
 *      Description:    A histogram of channel "chn" of image "img" is
 *                      generated. The result is returned in "buf".
 *
 *                      A histogram shows the distribution of pixel values.
 *                      The range from 0.0 to 1.0 is scaled into the range
 *                      from 0 to 255; pixel values, which are less than 0
 *                      or greater than 255 after scaling, are clamped to
 *                      0 or 255. For every pixel, whose scaled value is "i",
 *                      buf[i] is incremented by 1.
 *                      For example, (buf[5] == 45) means that there are 45
 *                      pixels in the image, whose scaled pixel value is 5.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool IP_Histogram (IP_ImageId img, FF_ImgChanType chn, Int32 buf[256])
{
    UT_Bool success;

    if (chn < 0 || chn >= FF_NumImgChanTypes) {
        UT_ErrSetNum (UT_ErrParamInvalid, str_imgchan, 
                      FF_ImgGetChanTypeName (chn));
        return UT_False;
    }
    histo (img, chn, buf);
    success = UT_True;
    return success;
}

/***************************************************************************
 *[@e
 *      Name:           IP_GetChanStats
 *
 *      Usage:          Obtain the medium value of an image channel.
 *
 *      Synopsis:       UT_Bool IP_GetChanStats
 *                              (IP_ImageId img, FF_ImgChanType chanType,
 *                               Float32 *meanVal, Float32 *stdVec,
 *                               UInt32 *numPix, UT_Bool useMatte)
 *
 *      Description:    Channel "chanType" of image "img" is scanned.
 *                      The arithmetic mean value of all pixels in that channel
 *                      is returned in parameter "meanVal".
 *                      The standard deviation is returned in "stdDev".
 *                      If "useMatte" is set to UT_True, only the pixels
 *                      with a matte value unequal to zero are taken into
 *                      consideration.
 *                      The number of pixels used to calculate the mean
 *                      value is returned in parameter "numPix". In the
 *                      simple case, when "useMatte" is UT_False, this value
 *                      equals to the product of the image width and height.
 *
 *                      Note: The mean value and standard deviation are
 *                            always returned as floating point value, even
 *                            if the channel is of type FF_ImgFmtUByte.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:       IP_GetChanRange
 *
 ***************************************************************************/

UT_Bool IP_GetChanStats (IP_ImageId img, FF_ImgChanType chanType, 
                         Float32 *meanVal, Float32 *stdDev,
                         UInt32 *numPix, UT_Bool useMatte)
{
    UT_Bool success = UT_False;

    /* First check the format of the matte channel, if useMatte is set. */
    if (useMatte) {
        switch (img->pimg.channel[FF_ImgChanTypeMatte]) {
            case FF_ImgFmtTypeNone: {
                UT_ErrSetNum (UT_ErrParamInvalid, str_imgchan, 
                              FF_ImgGetChanTypeName (FF_ImgChanTypeMatte));
                success = UT_False;
                break;
            }
            case FF_ImgFmtTypeUByte:
            case FF_ImgFmtTypeFloat: {
                success = UT_True;
                break;
            }
            default: {
                UT_ErrFatal ("IP_GetChanStats", "invalid matte pixel format");
                success = UT_False;
                break;
            }
        }
        if (!success) {
            return success;
        }
    }

    switch (img->pimg.channel[chanType]) {
        case FF_ImgFmtTypeNone: {
            UT_ErrSetNum (UT_ErrParamInvalid, str_imgchan, 
                          FF_ImgGetChanTypeName (chanType));
            success = UT_False;
            break;
        }
        case FF_ImgFmtTypeUByte:
        case FF_ImgFmtTypeFloat: {
            success = getChanStats (img, chanType, meanVal, stdDev, numPix, useMatte);
            break;
        }
        default: {
            UT_ErrFatal ("IP_GetChanStats", "invalid pixel format");
            success = UT_False;
            break;
        }
    }
    return success;
}

/***************************************************************************
 *[@e
 *      Name:           IP_GetChanRange
 *
 *      Usage:          Obtain the range of an image channel.
 *
 *      Synopsis:       UT_Bool IP_GetChanRange
 *                              (IP_ImageId img, FF_ImgChanType chanType,
 *                               Float32 *minVal, Float32 *maxVal)
 *
 *      Description:    Channel "chanType" of image "img" is scanned.
 *                      The minimum resp. maximum channel values are returned
 *                      in "minVal" resp. "maxVal".
 *
 *                      Note: The values are always returned in floating point
 *                      format.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool IP_GetChanRange (IP_ImageId img, FF_ImgChanType chanType, 
                         Float32 *minVal, Float32 *maxVal)
{
    UT_Bool success = UT_False;

    switch (img->pimg.channel[chanType]) {
        case FF_ImgFmtTypeNone: {
            UT_ErrSetNum (UT_ErrParamInvalid, str_imgchan, 
                          FF_ImgGetChanTypeName (chanType));
            success = UT_False;
            break;
        }
        case FF_ImgFmtTypeUByte: {
            UInt8 imin, imax;
            getUByteRange (img, chanType, &imin, &imax);
            *minVal = imin;
            *maxVal = imax;
            success = UT_True;
            break;
        }
        case FF_ImgFmtTypeFloat: {
            getFloatRange (img, chanType, minVal, maxVal);
            success = UT_True;
            break;
        }
        default: {
            UT_ErrFatal ("IP_GetChanRange", "invalid pixel format");
            success = UT_False;
            break;
        }
    }
    return success;
}

/***************************************************************************
 *[@e
 *      Name:           IP_GetDepthRange
 *
 *      Usage:          Obtain the depth range of an image.
 *
 *      Synopsis:       UT_Bool IP_GetDepthRange
 *                              (IP_ImageId img, 
 *                               Float32 *minVal, Float32 *maxVal)
 *
 *      Description:    The depth channel of image "img" is scanned.
 *                      The minimum resp. maximum depth values are returned
 *                      in "minVal" resp. "maxVal".
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool IP_GetDepthRange (IP_ImageId img, Float32 *minVal, Float32 *maxVal)
{
    return IP_GetChanRange (img, FF_ImgChanTypeDepth, minVal, maxVal);
}

/***************************************************************************
 *[@e
 *      Name:           IP_GetTemperatureRange
 *
 *      Usage:          Obtain the temperature range of an image.
 *
 *      Synopsis:       UT_Bool IP_GetTemperatureRange
 *                              (IP_ImageId img, 
 *                               Float32 *minVal, Float32 *maxVal)
 *
 *      Description:    The temperature channel of image "img" is scanned.
 *                      The minimum resp. maximum temperature values are 
 *                      returned in "minVal" resp. "maxVal".
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool IP_GetTemperatureRange (IP_ImageId img,
                                Float32 *minVal, Float32 *maxVal)
{
    return IP_GetChanRange (img, FF_ImgChanTypeTemperature, minVal, maxVal);
}

/***************************************************************************
 *[@e
 *      Name:           IP_GetRadianceRange
 *
 *      Usage:          Obtain the radiance range of an image.
 *
 *      Synopsis:       UT_Bool IP_GetRadianceRange
 *                              (IP_ImageId img, 
 *                               Float32 *minVal, Float32 *maxVal)
 *
 *      Description:    The radiance channel of image "img" is scanned.
 *                      The minimum resp. maximum radiance values are 
 *                      returned in "minVal" resp. "maxVal".
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool IP_GetRadianceRange (IP_ImageId img, Float32 *minVal, Float32 *maxVal)
{
    return IP_GetChanRange (img, FF_ImgChanTypeRadiance, minVal, maxVal);
}

/***************************************************************************
 *[@e
 *      Name:           IP_DiffChannel
 *
 *      Usage:          Obtain the differences of two images.
 *
 *      Synopsis:       UT_Bool IP_DiffChannel
 *                              (IP_ImageId img1, IP_ImageId img2, 
 *                               FF_ImgChanType chn, 
 *                               Int32 less1[256], Int32 less2[256])
 *
 *      Description:    Channel "chn" of images "img1" and "img2" 
 *                      is read to see how much they differ.
 *
 *                      The following notation is used:
 *
 *                      0 < val < 256:
 *                          less1[val] == sum    "sum" pixels in image "img1"
 *                                               have a channel value which
 *                                               is "val" less than the value
 *                                               of image "img2".
 *
 *                          less2[val] == sum    "sum" pixels in image "img2"
 *                                               have a channel value which
 *                                               is "val" less than the value
 *                                               of image "img1".
 *
 *                      val == 0:
 *                          less1[val] == sum    "sum" pixels don't differ.
 *
 *                          less2[val]           always zero.
 *
 *                      Note: 
 *                          - Only channels in FF_ImgFmtTypeUByte are allowed.
 *                          - The two images must be equal in width and height.
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool IP_DiffChannel (IP_ImageId img1, IP_ImageId img2, FF_ImgChanType chn,
                       Int32 less1[256], Int32 less2[256])
{
    if (img1->pimg.channel[chn] != FF_ImgFmtTypeUByte ||
        img2->pimg.channel[chn] != FF_ImgFmtTypeUByte) {
        UT_ErrSetNum (UT_ErrParamInvalid, str_chn_ubyte);
        return UT_False;
    }

    if (!((img1->pimg.desc.height == img2->pimg.desc.height) &&
          (img1->pimg.desc.width  == img2->pimg.desc.width))) {
        UT_ErrSetNum (UT_ErrParamInvalid, str_equal);
        return UT_False;
    }

    diff_channel (img1, img2, chn, less1, less2);
    return UT_True;
}

/***************************************************************************
 *[@e
 *      Name:           IP_GetImageInfo
 *
 *      Usage:          Read the image descriptor of an IP_ImageId.
 *
 *      Synopsis:       void IP_GetImageInfo
 *                              (IP_ImageId img,
 *                              FF_ImgHdr *desc,
 *                              FF_ImgFmtType fmt[])
 *
 *      Description:    The image descriptor (width, height, aspect ratio, gamma
 *                      factor and color correction parameters) of IP_ImageId
 *                      "img" are written to "desc".
 *                      The pixel data formats of the channels in "img"
 *                      are written to "fmt".
 *
 *                      Note: If you are not interested in the image descriptor
 *                      or in the channel formats, call poImgDescr with a
 *                      NULL pointer instead of "desc" or "fmt".
 *
 *      Return value:   None.
 *
 *      See also:       IP_GetFileInfo
 *
 ***************************************************************************/

void IP_GetImageInfo (IP_ImageId img, FF_ImgHdr *desc, FF_ImgFmtType fmt[])
{
    Int32 i;

    if (desc) {
        *desc = img->pimg.desc;
    }
    if (fmt) {
        for (i = 0; i < FF_NumImgChanTypes; ++i) {
            fmt[i] = img->pimg.channel[i];
        }
    }
    return;
}

/***************************************************************************
 *[@e
 *      Name:           IP_GetFileInfo
 *
 *      Usage:          Read the image descriptor from a file.
 *
 *      Synopsis:       UT_Bool IP_GetFileInfo
 *                              (const char fname[],
 *                              FF_ImgHdr *desc,
 *                              FF_ImgFmtType fmt[])
 *
 *      Description:    The header of pixel file "fname" is read. Width,
 *                      height, aspect ratio gamma factor and color correction
 *                      parameters of the image stored in the file are written
 *                      to "desc".
 *                      The pixel data formats of the channels stored in the
 *                      file are written to "fmt".
 *
 *                      Note: If you are not interested in the file header, the
 *                      pixel data formats or in the change information, call
 *                      IP_GetFileInfo with a NULL pointer instead of "desc" or
 *                      "fmt".
 *
 *      Return value:   UT_True if successful, else UT_False.
 *
 *      See also:       IP_GetImageInfo
 *
 ***************************************************************************/

UT_Bool IP_GetFileInfo (const char fname[], FF_ImgHdr *desc, FF_ImgFmtType fmt[])
{
    FF_ImgFileDesc pxf;
    Int32          i;

    memset (&pxf, 0, sizeof (FF_ImgFileDesc));

    if (!FF_ImgOpenRead (fname, &pxf)) {
        return UT_False;
    }
    if (desc) {
        *desc = pxf.desc;
    }
    if (fmt) {
        for (i = 0; i < FF_NumImgChanTypes; ++i) {
            fmt[i] = pxf.channel[i];
        }
    }
    FF_ImgClose (&pxf);
    return UT_True;
}
