/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2004-07-29
 * Description : image levels manipulation methods.
 *
 * Copyright (C) 2004-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * Some code parts are inspired from gimp 2.0
 * app/base/levels.c, gimplut.c, and app/base/gimpleveltool.c 
 * source files.
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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, 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.
 *
 * ============================================================ */

// TQt includes.

#include <tqfile.h>

// C++ includes.

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cerrno>

// Local includes.

#include "ddebug.h"
#include "imagehistogram.h"
#include "imagelevels.h"

namespace Digikam
{

class ImageLevelsPriv
{

public:

    enum PixelType
    {
        RedPixel = 0,
        GreenPixel,
        BluePixel,
        AlphaPixel
    };

    struct _Levels
    {
        double  gamma[5];

        int     low_input[5];
        int     high_input[5];

        int     low_output[5];
        int     high_output[5];
    };

    struct _Lut
    {
        unsigned short **luts;
        int              nchannels;
    };

public:

    ImageLevelsPriv()
    {
        levels = 0;
        lut    = 0;
        dirty  = false;
    }

    // Levels data.
    struct _Levels *levels;

    // Lut data.
    struct _Lut    *lut;

    bool            sixteenBit;
    bool            dirty;
};

ImageLevels::ImageLevels(bool sixteenBit)
{
    d = new ImageLevelsPriv;
    d->lut        = new ImageLevelsPriv::_Lut;
    d->levels     = new ImageLevelsPriv::_Levels;
    d->sixteenBit = sixteenBit;

    memset(d->levels, 0, sizeof(struct ImageLevelsPriv::_Levels));
    d->lut->luts      = NULL;
    d->lut->nchannels = 0;

    reset();
}

ImageLevels::~ImageLevels()
{ 
    if (d->lut)
    {
       if (d->lut->luts)
       {
          for (int i = 0 ; i < d->lut->nchannels ; i++)
              delete [] d->lut->luts[i];

          delete [] d->lut->luts;
       }

       delete d->lut;
    }

    if (d->levels)
       delete d->levels;

    delete d;
}

bool ImageLevels::isDirty()
{
    return d->dirty;
}

bool ImageLevels::isSixteenBits()
{
    return d->sixteenBit;
}

void ImageLevels::reset()
{
    for (int channel = 0 ; channel < 5 ; channel++)
       levelsChannelReset(channel);
}

void ImageLevels::levelsChannelReset(int channel)
{
    if (!d->levels) return;

    d->levels->gamma[channel]       = 1.0;
    d->levels->low_input[channel]   = 0;
    d->levels->high_input[channel]  = d->sixteenBit ? 65535 : 255;
    d->levels->low_output[channel]  = 0;
    d->levels->high_output[channel] = d->sixteenBit ? 65535 : 255;
    d->dirty = false;
}

void ImageLevels::levelsAuto(ImageHistogram *hist)
{
    if (!d->levels || !hist) return;

    levelsChannelReset(ImageHistogram::ValueChannel);

    for (int channel = ImageHistogram::RedChannel ;
         channel <= ImageHistogram::BlueChannel ;
         channel++)
    {
       levelsChannelAuto(hist, channel);
    }
    d->dirty = true;
}

void ImageLevels::levelsChannelAuto(ImageHistogram *hist, int channel)
{
    int    i;
    double count, new_count, percentage, next_percentage;

    if (!d->levels || !hist) return;

    d->levels->gamma[channel]       = 1.0;
    d->levels->low_output[channel]  = 0;
    d->levels->high_output[channel] = d->sixteenBit ? 65535 : 255;

    count = hist->getCount(channel, 0, d->sixteenBit ? 65535 : 255);

    if (count == 0.0)
    {
       d->levels->low_input[channel]  = 0;
       d->levels->high_input[channel] = 0;
    }
    else
    {
       //  Set the low input

       new_count = 0.0;

       for (i = 0 ; i < (d->sixteenBit ? 65535 : 255) ; i++)
       {
          new_count       += hist->getValue(channel, i);
          percentage      = new_count / count;
          next_percentage = (new_count + hist->getValue(channel, i + 1)) / count;

          if (fabs (percentage - 0.006) < fabs (next_percentage - 0.006))
          {
             d->levels->low_input[channel] = i + 1;
             break;
          }
       }

       //  Set the high input

       new_count = 0.0;

       for (i = (d->sixteenBit ? 65535 : 255) ; i > 0 ; i--)
       {
          new_count       += hist->getValue(channel, i);
          percentage      = new_count / count;
          next_percentage = (new_count + hist->getValue(channel, i - 1)) / count;

          if (fabs (percentage - 0.006) < fabs (next_percentage - 0.006))
          {
             d->levels->high_input[channel] = i - 1;
             break;
          }
       }
    }
    d->dirty = true;
}

int ImageLevels::levelsInputFromColor(int channel, const DColor& color)
{
    switch (channel)
    {
       case ImageHistogram::ValueChannel:
          return TQMAX (TQMAX (color.red(), color.green()), color.blue());

       case ImageHistogram::RedChannel:
          return color.red();

       case ImageHistogram::GreenChannel:
          return color.green();

       case ImageHistogram::BlueChannel:
          return color.blue();
    }

    return 0;  // just to please the compiler.
}

void ImageLevels::levelsBlackToneAdjustByColors(int channel, const DColor& color)
{
    if (!d->levels) return;

    d->levels->low_input[channel] = levelsInputFromColor(channel, color);
    d->dirty = true;
}

void ImageLevels::levelsWhiteToneAdjustByColors(int channel, const DColor& color)
{
    if (!d->levels) return;

    d->levels->high_input[channel] = levelsInputFromColor(channel, color);
    d->dirty = true;
}

void ImageLevels::levelsGrayToneAdjustByColors(int channel, const DColor& color)
{
    if (!d->levels) return;

    int            input;
    int            range;
    double         inten;
    double         out_light;
    unsigned short lightness;

    // Calculate lightness value.

    lightness = (unsigned short)LEVELS_RGB_INTENSITY (color.red(), color.green(), color.blue());

    input     = levelsInputFromColor(channel, color);

    range     = d->levels->high_input[channel] - d->levels->low_input[channel];

    if (range <= 0)
       return;

    input -= d->levels->low_input[channel];

    if (input < 0)
       return;

    // Normalize input and lightness.

    inten     = (double) input / (double) range;
    out_light = (double) lightness/ (double) range;

    if (out_light <= 0)
       return;

    // Map selected color to corresponding lightness.

    d->levels->gamma[channel] = log (inten) / log (out_light);
    d->dirty = true;
}

void ImageLevels::levelsCalculateTransfers()
{
    double inten;
    int    i, j;

    if (!d->levels) return;

    // Recalculate the levels arrays.

    for (j = 0 ; j < 5 ; j++)
    {
      for (i = 0; i <= (d->sixteenBit ? 65535 : 255); i++)
      {
          //  determine input intensity.

          if (d->levels->high_input[j] != d->levels->low_input[j])
          {
             inten = ((double) (i - d->levels->low_input[j]) /
                      (double) (d->levels->high_input[j] - d->levels->low_input[j]));
          }
          else
          {
             inten = (double) (i - d->levels->low_input[j]);
          }

          inten = CLAMP (inten, 0.0, 1.0);

          if (d->levels->gamma[j] != 0.0)
             inten = pow (inten, (1.0 / d->levels->gamma[j]));
      }
   }
}

float ImageLevels::levelsLutFunc(int n_channels, int channel, float value)
{
    double inten;
    int    j;

    if (!d->levels) return 0.0;

    if (n_channels == 1)
       j = 0;
    else
       j = channel + 1;

    inten = value;

    // For color  images this runs through the loop with j = channel +1
    // the first time and j = 0 the second time.
    //
    // For bw images this runs through the loop with j = 0 the first and
    // only time.

    for ( ; j >= 0 ; j -= (channel + 1) )
    {
       // Don't apply the overall curve to the alpha channel.

       if (j == 0 && (n_channels == 2 || n_channels == 4)
          && channel == n_channels -1)
          return inten;

       //  Determine input intensity.

       if (d->levels->high_input[j] != d->levels->low_input[j])
          inten = ((double) ((float)(d->sixteenBit ? 65535 : 255) * inten - d->levels->low_input[j]) /
                  (double) (d->levels->high_input[j] - d->levels->low_input[j]));
       else
          inten = (double) ((float)(d->sixteenBit ? 65535 : 255) * inten - d->levels->low_input[j]);

       if (d->levels->gamma[j] != 0.0)
       {
          if (inten >= 0.0)
             inten =  pow ( inten, (1.0 / d->levels->gamma[j]));
          else
             inten = -pow (-inten, (1.0 / d->levels->gamma[j]));
       }

       //  determine the output intensity.

       if (d->levels->high_output[j] >= d->levels->low_output[j])
          inten = (double) (inten * (d->levels->high_output[j] -
                  d->levels->low_output[j]) + d->levels->low_output[j]);

       else if (d->levels->high_output[j] < d->levels->low_output[j])
          inten = (double) (d->levels->low_output[j] - inten *
                  (d->levels->low_output[j] - d->levels->high_output[j]));

       inten /= (float)(d->sixteenBit ? 65535 : 255);
    }

    return inten;
}

void ImageLevels::levelsLutSetup(int nchannels)
{
    int    i; 
    uint   v;
    double val;

    if (d->lut->luts)
    {
       for (i = 0 ; i < d->lut->nchannels ; i++)
           delete [] d->lut->luts[i];

       delete [] d->lut->luts;
    }

    d->lut->nchannels = nchannels;
    d->lut->luts      = new unsigned short*[d->lut->nchannels];

    for (i = 0 ; i < d->lut->nchannels ; i++)
    {
       d->lut->luts[i] = new unsigned short[(d->sixteenBit ? 65535 : 255) + 1];

       for (v = 0 ; v <= (d->sixteenBit ? 65535 : 255) ; v++)
       {
          // to add gamma correction use func(v ^ g) ^ 1/g instead.

          val = (float)(d->sixteenBit ? 65535 : 255) *
                levelsLutFunc( d->lut->nchannels, i, v/(float)(d->sixteenBit ? 65535 : 255)) + 0.5;

          d->lut->luts[i][v] = (unsigned short)CLAMP (val, 0, (d->sixteenBit ? 65535 : 255));
       }
    }
}

void ImageLevels::levelsLutProcess(uchar *srcPR, uchar *destPR, int w, int h)
{
    unsigned short *lut0 = NULL, *lut1 = NULL, *lut2 = NULL, *lut3 = NULL;

    int   i;

    if (d->lut->nchannels > 0)
       lut0 = d->lut->luts[0];
    if (d->lut->nchannels > 1)
       lut1 = d->lut->luts[1];
    if (d->lut->nchannels > 2)
       lut2 = d->lut->luts[2];
    if (d->lut->nchannels > 3)
       lut3 = d->lut->luts[3];

    if (!d->sixteenBit)        // 8 bits image.
    {
        uchar red, green, blue, alpha;
        uchar *ptr = srcPR;
        uchar *dst = destPR;

        for (i = 0 ; i < w*h ; i++)
        {
            blue  = ptr[0];
            green = ptr[1];
            red   = ptr[2];
            alpha = ptr[3];

            if ( d->lut->nchannels > 0 )
               red = lut0[red];

            if ( d->lut->nchannels > 1 )
               green = lut1[green];

            if ( d->lut->nchannels > 2 )
               blue = lut2[blue];

            if ( d->lut->nchannels > 3 )
               alpha = lut3[alpha];

            dst[0] = blue;
            dst[1] = green;
            dst[2] = red;
            dst[3] = alpha;

            ptr += 4;
            dst += 4;
        }
    }
    else               // 16 bits image.
    {
        unsigned short red, green, blue, alpha;
        unsigned short *ptr = (unsigned short *)srcPR;
        unsigned short *dst = (unsigned short *)destPR;

        for (i = 0 ; i < w*h ; i++)
        {
            blue  = ptr[0];
            green = ptr[1];
            red   = ptr[2];
            alpha = ptr[3];

            if ( d->lut->nchannels > 0 )
               red = lut0[red];

            if ( d->lut->nchannels > 1 )
               green = lut1[green];

            if ( d->lut->nchannels > 2 )
               blue = lut2[blue];

            if ( d->lut->nchannels > 3 )
               alpha = lut3[alpha];

            dst[0] = blue;
            dst[1] = green;
            dst[2] = red;
            dst[3] = alpha;

            ptr += 4;
            dst += 4;
        }
    }
}

void ImageLevels::setLevelGammaValue(int Channel, double val)
{
    if ( d->levels && Channel>=0 && Channel<5 )
    {
        d->levels->gamma[Channel] = val;
        d->dirty = true;
    }
}

void ImageLevels::setLevelLowInputValue(int Channel, int val)
{
    if ( d->levels && Channel>=0 && Channel<5 )
    {
        d->levels->low_input[Channel] = val;
        d->dirty = true;
    }
}

void ImageLevels::setLevelHighInputValue(int Channel, int val)
{
    if ( d->levels && Channel>=0 && Channel<5 )
    {
        d->levels->high_input[Channel] = val;
        d->dirty = true;
    }
}

void ImageLevels::setLevelLowOutputValue(int Channel, int val)
{
    if ( d->levels && Channel>=0 && Channel<5 )
    {
        d->levels->low_output[Channel] = val;
        d->dirty = true;
    }
}

void ImageLevels::setLevelHighOutputValue(int Channel, int val)
{
    if ( d->levels && Channel>=0 && Channel<5 )
    {
        d->levels->high_output[Channel] = val;
        d->dirty = true;
    }
}

double ImageLevels::getLevelGammaValue(int Channel)
{
    if ( d->levels && Channel>=0 && Channel<5 )
       return (d->levels->gamma[Channel]);

    return 0.0;
}

int ImageLevels::getLevelLowInputValue(int Channel)
{
    if ( d->levels && Channel>=0 && Channel<5 )
       return (d->levels->low_input[Channel]);

    return 0;
}

int ImageLevels::getLevelHighInputValue(int Channel)
{
    if ( d->levels && Channel>=0 && Channel<5 )
       return (d->levels->high_input[Channel]);

    return 0;
}

int ImageLevels::getLevelLowOutputValue(int Channel)
{
    if ( d->levels && Channel>=0 && Channel<5 )
       return (d->levels->low_output[Channel]);

    return 0;
}

int ImageLevels::getLevelHighOutputValue(int Channel)
{
    if ( d->levels && Channel>=0 && Channel<5 )
       return (d->levels->high_output[Channel]);

    return 0;
}

bool ImageLevels::loadLevelsFromGimpLevelsFile(const KURL& fileUrl)
{
    // TODO : support KURL !

    FILE   *file;
    int     low_input[5];
    int     high_input[5];
    int     low_output[5];
    int     high_output[5];
    double  gamma[5];
    int     i, fields;
    char    buf[50];
    char   *nptr;

    file = fopen(TQFile::encodeName(fileUrl.path()), "r");

    if (!file)
       return false;

    if (! fgets (buf, sizeof (buf), file))
    {
       fclose(file);
       return false;
    }

    if (strcmp (buf, "# GIMP Levels File\n") != 0)
    {
       fclose(file);
       return false;
    }

    for (i = 0 ; i < 5 ; i++)
    {
       fields = fscanf (file, "%d %d %d %d ",
                        &low_input[i],
                        &high_input[i],
                        &low_output[i],
                        &high_output[i]);

       if (fields != 4)
       {
          DWarning() <<  "Invalid Gimp levels file!" << endl;
          fclose(file);
          return false;
       }

       if (!fgets (buf, 50, file))
       {
          DWarning() <<  "Invalid Gimp levels file!" << endl;
          fclose(file);
          return false;
       }

       gamma[i] = strtod (buf, &nptr);

       if (buf == nptr || errno == ERANGE)
       {
          DWarning() <<  "Invalid Gimp levels file!" << endl;
          fclose(file);
          return false;
       }
    }

    for (i = 0 ; i < 5 ; i++)
    {
       setLevelGammaValue(i, gamma[i]);
       setLevelLowInputValue(i, d->sixteenBit ? low_input[i]*255 : low_input[i]);
       setLevelHighInputValue(i, d->sixteenBit ? high_input[i]*255 : high_input[i]);
       setLevelLowOutputValue(i, d->sixteenBit ? low_output[i]*255 : low_output[i]);
       setLevelHighOutputValue(i, d->sixteenBit ? high_output[i]*255 : high_output[i]);
    }

    fclose(file);
    return true;
}

bool ImageLevels::saveLevelsToGimpLevelsFile(const KURL& fileUrl)
{
    // TODO : support KURL !

    FILE *file;
    int   i;

    file = fopen(TQFile::encodeName(fileUrl.path()), "w");

    if (!file)
       return false;

    fprintf (file, "# GIMP Levels File\n");

    for (i = 0 ; i < 5 ; i++)
    {
       char buf[256];
       sprintf (buf, "%f", getLevelGammaValue(i));

       fprintf (file, "%d %d %d %d %s\n",
                d->sixteenBit ? getLevelLowInputValue(i)/255 : getLevelLowInputValue(i),
                d->sixteenBit ? getLevelHighInputValue(i)/255 : getLevelHighInputValue(i),
                d->sixteenBit ? getLevelLowOutputValue(i)/255 : getLevelLowOutputValue(i),
                d->sixteenBit ? getLevelHighInputValue(i)/255 : getLevelHighInputValue(i),
                buf);
    }

    fflush(file);
    fclose(file);

    return true;
}

}  // NameSpace Digikam
