/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2005-05-25
 * Description : Charcoal threaded image filter.
 *
 * Copyright (C) 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
 * 
 * Original Charcoal algorithm copyright 2002
 * by Daniel M. Duley <mosfet@kde.org> from KImageEffect API.
 *
 * 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.
 * 
 * ============================================================ */

#define SQ2PI   2.50662827463100024161235523934010416269302368164062
#define Epsilon 1.0e-12

// C++ includes.

#include <cmath>

// Local includes.

#include "ddebug.h"
#include "dimg.h"
#include "dimggaussianblur.h"
#include "dimgimagefilters.h"
#include "charcoal.h"

namespace DigikamCharcoalImagesPlugin
{

Charcoal::Charcoal(Digikam::DImg *orgImage, TQObject *parent, double pencil, double smooth)
        : Digikam::DImgThreadedFilter(orgImage, parent, "Charcoal")
{ 
    m_pencil = pencil;
    m_smooth = smooth;
    
    initFilter();
}

void Charcoal::filterImage(void)
{
    if (m_orgImage.isNull())
    {
       DWarning() << k_funcinfo << "No image data available!"
                   << endl;
       return;
    }

    if (m_pencil <= 0.0)
    {
       m_destImage = m_orgImage;
       return;
    }

    // -- Applying Edge effect -----------------------------------------------

    long i=0;
    int kernelWidth = getOptimalKernelWidth(m_pencil, m_smooth);
    
    if((int)m_orgImage.width() < kernelWidth)
    {
        DWarning() << k_funcinfo << "Image is smaller than radius!"
                    << endl;
        return;
    }
    
    double *kernel = new double[kernelWidth*kernelWidth];
    
    if(!kernel)
    {
        DWarning() << k_funcinfo << "Unable to allocate memory!"
                    << endl;
        return;
    }

    for(i = 0 ; i < (kernelWidth*kernelWidth) ; i++)
        kernel[i]=(-1.0);

    kernel[i/2]=kernelWidth*kernelWidth-1.0;
    convolveImage(kernelWidth, kernel);    
    delete [] kernel;

    // -- Applying Gaussian blur effect ---------------------------------------

    Digikam::DImgGaussianBlur(this, m_destImage, m_destImage, 50, 60, (int)(m_smooth/10.0));

    if (m_cancel)
        return;

    // -- Applying strech contrast color effect -------------------------------

    Digikam::DImgImageFilters().stretchContrastImage(m_destImage.bits(), m_destImage.width(), 
                                m_destImage.height(), m_destImage.sixteenBit());
    postProgress( 70 );
    if (m_cancel)
        return;

    // -- Inverting image color -----------------------------------------------

    Digikam::DImgImageFilters().invertImage(m_destImage.bits(), m_destImage.width(), 
                                m_destImage.height(), m_destImage.sixteenBit());
    postProgress( 80 );
    if (m_cancel)
        return;

    // -- Convert to neutral black & white ------------------------------------

    Digikam::DImgImageFilters().channelMixerImage(
                   m_destImage.bits(), m_destImage.width(), 
                   m_destImage.height(), m_destImage.sixteenBit(),  // Image data.
                   true,                                            // Preserve luminosity.    
                   true,                                            // Monochrome.
                   0.3, 0.59 , 0.11,                                // Red channel gains.
                   0.0, 1.0,   0.0,                                 // Green channel gains (not used).
                   0.0, 0.0,   1.0);                                // Blue channel gains (not used).
    postProgress( 90 );
    if (m_cancel)
        return;
}

bool Charcoal::convolveImage(const unsigned int order, const double *kernel)
{
    uint    x, y;
    int     mx, my, sx, sy, mcx, mcy, progress;
    long    kernelWidth, i;
    double  red, green, blue, alpha, normalize=0.0;
    double *k=0;
    Digikam::DColor  color;
    
    kernelWidth = order;
    
    if((kernelWidth % 2) == 0)
    {
        DWarning() << k_funcinfo << "Kernel width must be an odd number!"
                    << endl;
        return(false);
    }
    
    double *normal_kernel = new double[kernelWidth*kernelWidth];
    
    if(!normal_kernel)
    {
        DWarning() << k_funcinfo << "Unable to allocate memory!"
                    << endl;
        return(false);
    }
    
    for(i=0 ; i < (kernelWidth*kernelWidth) ; i++)
        normalize += kernel[i];
        
    if(fabs(normalize) <= Epsilon)
        normalize=1.0;
        
    normalize = 1.0/normalize;
    
    for(i=0 ; i < (kernelWidth*kernelWidth) ; i++)
        normal_kernel[i] = normalize*kernel[i];

    double maxClamp = m_destImage.sixteenBit() ? 16777215.0 : 65535.0;

    for(y=0 ; !m_cancel && (y < m_destImage.height()) ; y++)
    {
        sy = y-(kernelWidth/2);

        for(x=0 ; !m_cancel && (x < m_destImage.width()) ; x++)
        {
            k   = normal_kernel;
            red = green = blue = alpha = 0;
            sy  = y-(kernelWidth/2);
            
            for(mcy=0 ; !m_cancel && (mcy < kernelWidth) ; mcy++, sy++)
            {
                my = sy < 0 ? 0 : sy > (int)m_destImage.height()-1 ? m_destImage.height()-1 : sy;
                sx = x+(-kernelWidth/2);

                for(mcx=0 ; !m_cancel && (mcx < kernelWidth) ; mcx++, sx++)
                {
                    mx     = sx < 0 ? 0 : sx > (int)m_destImage.width()-1 ? m_destImage.width()-1 : sx;
                    color  = m_orgImage.getPixelColor(mx, my);
                    red   += (*k)*(color.red()   * 257.0);
                    green += (*k)*(color.green() * 257.0);
                    blue  += (*k)*(color.blue()  * 257.0);
                    alpha += (*k)*(color.alpha() * 257.0);
                    k++;
                }
            }

            red   =   red < 0.0 ? 0.0 :   red > maxClamp ? maxClamp :   red+0.5;
            green = green < 0.0 ? 0.0 : green > maxClamp ? maxClamp : green+0.5;
            blue  =  blue < 0.0 ? 0.0 :  blue > maxClamp ? maxClamp :  blue+0.5;
            alpha = alpha < 0.0 ? 0.0 : alpha > maxClamp ? maxClamp : alpha+0.5;

            m_destImage.setPixelColor(x, y, Digikam::DColor((int)(red / 257UL),  (int)(green / 257UL),
                                            (int)(blue / 257UL), (int)(alpha / 257UL),
                                            m_destImage.sixteenBit()));
        }

        progress = (int)(((double)y * 50.0) / m_destImage.height());
        if ( progress%5 == 0 )
           postProgress( progress );          
    }

    delete [] normal_kernel;
    return(true);
}

int Charcoal::getOptimalKernelWidth(double radius, double sigma)
{
    double        normalize, value;
    long          kernelWidth;
    long u;

    if(radius > 0.0)
        return((int)(2.0*ceil(radius)+1.0));
        
    for(kernelWidth=5; ;)
    {
        normalize=0.0;
        
        for(u=(-kernelWidth/2) ; u <= (kernelWidth/2) ; u++)
            normalize += exp(-((double) u*u)/(2.0*sigma*sigma))/(SQ2PI*sigma);

        u     = kernelWidth/2;
        value = exp(-((double) u*u)/(2.0*sigma*sigma))/(SQ2PI*sigma)/normalize;
        
        if((long)(65535*value) <= 0)
            break;
            
        kernelWidth+=2;
    }
    
    return((int)kernelWidth-2);
}

}  // NameSpace DigikamCharcoalImagesPlugin
