/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2005-05-25
 * Description : Blur FX threaded image filter.
 *
 * Copyright 2005-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
 * Copyright 2006-2007 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
 * 
 * Original Blur algorithms copyrighted 2004 by 
 * Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot com>.
 *
 * 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.
 * 
 * ============================================================ */

// Represents 1 
#define ANGLE_RATIO  0.017453292519943295769236907685   

// C++ includes.

#include <cmath>
#include <cstdlib>
#include <cstring>

// TQt includes.

#include <tqdatetime.h>

// Local includes.

#include "dimg.h"
#include "dimggaussianblur.h"
#include "blurfx.h"

namespace DigikamBlurFXImagesPlugin
{

BlurFX::BlurFX(Digikam::DImg *orgImage, TQObject *parent, int blurFXType, int distance, int level)
      : Digikam::DImgThreadedFilter(orgImage, parent, "BlurFX")
{
    m_blurFXType = blurFXType;
    m_distance   = distance;
    m_level      = level;

    initFilter();
}

void BlurFX::filterImage(void)
{
    int w = m_orgImage.width();
    int h = m_orgImage.height();

    switch (m_blurFXType)
    {
        case ZoomBlur:
            zoomBlur(&m_orgImage, &m_destImage, w/2, h/2, m_distance);
            break;

        case RadialBlur:
            radialBlur(&m_orgImage, &m_destImage, w/2, h/2, m_distance);
            break;

        case FarBlur:
            farBlur(&m_orgImage, &m_destImage, m_distance);
            break;

        case MotionBlur:
            motionBlur(&m_orgImage, &m_destImage, m_distance, (double)m_level);
            break;

        case SoftenerBlur:
            softenerBlur(&m_orgImage, &m_destImage);
            break;

        case ShakeBlur:
            shakeBlur(&m_orgImage, &m_destImage, m_distance);
            break;

        case FocusBlur:
            focusBlur(&m_orgImage, &m_destImage, w/2, h/2, m_distance, m_level*10);
            break;

        case SmartBlur:
            smartBlur(&m_orgImage, &m_destImage, m_distance, m_level);
            break;

        case FrostGlass:
            frostGlass(&m_orgImage, &m_destImage, m_distance);
            break;

        case Mosaic:
            mosaic(&m_orgImage, &m_destImage, m_distance, m_distance);
            break;
    }
}

/* Function to apply the ZoomBlur effect backported from ImageProcessing version 2                                           
 *                                                                                  
 * data             => The image data in RGBA mode.                            
 * Width            => Width of image.                          
 * Height           => Height of image.  
 * X, Y             => Center of zoom in the image
 * Distance         => Distance value         
 * pArea            => Preview area.
 *                                                                                  
 * Theory           => Here we have a effect similar to RadialBlur mode Zoom from  
 *                     Photoshop. The theory is very similar to RadialBlur, but has one
 *                     difference. Instead we use pixels with the same radius and      
 *                     near angles, we take pixels with the same angle but near radius 
 *                     This radius is always from the center to out of the image, we   
 *                     calc a proportional radius from the center.
 */
void BlurFX::zoomBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int X, int Y, int Distance, TQRect pArea)
{
    if (Distance <= 1) return;
    int progress;

    int Width       = orgImage->width();
    int Height      = orgImage->height();
    uchar* data     = orgImage->bits();
    bool sixteenBit = orgImage->sixteenBit();
    int bytesDepth  = orgImage->bytesDepth();
    uchar* pResBits = destImage->bits();

    // We working on full image.
    int xMin = 0;
    int xMax = Width;
    int yMin = 0;
    int yMax = Height;

    // If we working in preview mode, else we using the preview area.
    if ( pArea.isValid() )
    {
        xMin = pArea.x();
        xMax = pArea.x() + pArea.width();
        yMin = pArea.y();
        yMax = pArea.y() + pArea.height();
    }

    int h, w, nh, nw, r;
    int sumR, sumG, sumB, nCount;
    double lfRadius, lfNewRadius, lfRadMax, lfAngle;

    Digikam::DColor color;
    int offset;

    lfRadMax = sqrt (Height * Height + Width * Width);

    // number of added pixels
    nCount = 0;

    // we have reached the main loop
    for (h = yMin; !m_cancel && (h < yMax); h++)
    {
        for (w = xMin; !m_cancel && (w < xMax); w++)
        {
            // ...we enter this loop to sum the bits

            // we initialize the variables
            sumR = sumG = sumB = nCount = 0;

            nw = X - w;
            nh = Y - h;

            lfRadius = sqrt (nw * nw + nh * nh);
            lfAngle = atan2 ((double)nh, (double)nw);
            lfNewRadius = (lfRadius * Distance) / lfRadMax;

            for (r = 0; !m_cancel && (r <= lfNewRadius); r++)
            {
                // we need to calc the positions
                nw = (int)(X - (lfRadius - r) * cos (lfAngle));
                nh = (int)(Y - (lfRadius - r) * sin (lfAngle));

                if (IsInside(Width, Height, nw, nh))
                {
                    // read color
                    offset = GetOffset(Width, nw, nh, bytesDepth);
                    color.setColor(data + offset, sixteenBit);

                    // we sum the bits
                    sumR += color.red();
                    sumG += color.green();
                    sumB += color.blue();
                    nCount++;
                }
            }

            if (nCount == 0) nCount = 1;

            // calculate pointer
            offset = GetOffset(Width, w, h, bytesDepth);
            // read color to preserve alpha
            color.setColor(data + offset, sixteenBit);

            // now, we have to calc the arithmetic average
            color.setRed  (sumR / nCount);
            color.setGreen(sumG / nCount);
            color.setBlue (sumB / nCount);

            // write color to destination
            color.setPixel(pResBits + offset);
        }

        // Update the progress bar in dialog.
        progress = (int) (((double)(h - yMin) * 100.0) / (yMax - yMin));

        if (progress%5 == 0)
            postProgress(progress);
    }
}

/* Function to apply the radialBlur effect backported from ImageProcessing version 2                                           
 *                                                                                  
 * data             => The image data in RGBA mode.                            
 * Width            => Width of image.                          
 * Height           => Height of image.     
 * X, Y             => Center of radial in the image                       
 * Distance         => Distance value                                            
 * pArea            => Preview area.
 *                                                                                  
 * Theory           => Similar to RadialBlur from Photoshop, its an amazing effect    
 *                     Very easy to understand but a little hard to implement.           
 *                     We have all the image and find the center pixel. Now, we analize
 *                     all the pixels and calc the radius from the center and find the 
 *                     angle. After this, we sum this pixel with others with the same  
 *                     radius, but different angles. Here I'm using degrees angles.
 */
void BlurFX::radialBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int X, int Y, int Distance, TQRect pArea)
{
    if (Distance <= 1) return;
    int progress;

    int Width       = orgImage->width();
    int Height      = orgImage->height();
    uchar* data     = orgImage->bits();
    bool sixteenBit = orgImage->sixteenBit();
    int bytesDepth  = orgImage->bytesDepth();
    uchar* pResBits = destImage->bits();

    // We working on full image.
    int xMin = 0;
    int xMax = Width;
    int yMin = 0;
    int yMax = Height;

    // If we working in preview mode, else we using the preview area.
    if ( pArea.isValid() )
    {
        xMin = pArea.x();
        xMax = pArea.x() + pArea.width();
        yMin = pArea.y();
        yMax = pArea.y() + pArea.height();
    }

    int sumR, sumG, sumB, nw, nh;
    double Radius, Angle, AngleRad;

    Digikam::DColor color;
    int offset;

    double *nMultArray = new double[Distance * 2 + 1];

    for (int i = -Distance; i <= Distance; i++)
        nMultArray[i + Distance] = i * ANGLE_RATIO;

    // number of added pixels
    int nCount = 0;

    // we have reached the main loop

    for (int h = yMin; !m_cancel && (h < yMax); h++)
    {
        for (int w = xMin; !m_cancel && (w < xMax); w++)
        {
            // ...we enter this loop to sum the bits

            // we initialize the variables
            sumR = sumG = sumB = nCount = 0;

            nw = X - w;
            nh = Y - h;

            Radius = sqrt (nw * nw + nh * nh);
            AngleRad = atan2 ((double)nh, (double)nw);

            for (int a = -Distance; !m_cancel && (a <= Distance); a++)
            {
                Angle = AngleRad + nMultArray[a + Distance];
                // we need to calc the positions
                nw = (int)(X - Radius * cos (Angle));
                nh = (int)(Y - Radius * sin (Angle));

                if (IsInside(Width, Height, nw, nh))
                {
                    // read color
                    offset = GetOffset(Width, nw, nh, bytesDepth);
                    color.setColor(data + offset, sixteenBit);

                    // we sum the bits
                    sumR += color.red();
                    sumG += color.green();
                    sumB += color.blue();
                    nCount++;
                }
            }

            if (nCount == 0) nCount = 1;

            // calculate pointer
            offset = GetOffset(Width, w, h, bytesDepth);
            // read color to preserve alpha
            color.setColor(data + offset, sixteenBit);

            // now, we have to calc the arithmetic average
            color.setRed  (sumR / nCount);
            color.setGreen(sumG / nCount);
            color.setBlue (sumB / nCount);

            // write color to destination
            color.setPixel(pResBits + offset);
        }

        // Update the progress bar in dialog.
        progress = (int) (((double)(h - yMin) * 100.0) / (yMax - yMin));

        if (progress%5 == 0)
            postProgress(progress);
    }

    delete [] nMultArray;
}

/* Function to apply the focusBlur effect backported from ImageProcessing version 2                                              
 *                                                                                  
 * data             => The image data in RGBA mode.                            
 * Width            => Width of image.                          
 * Height           => Height of image.                            
 * BlurRadius       => Radius of blurred image. 
 * BlendRadius      => Radius of blending effect.
 * bInversed        => If true, invert focus effect.
 * pArea            => Preview area.
 *                                                                                 
 */
void BlurFX::focusBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage,
                       int X, int Y, int BlurRadius, int BlendRadius,
                       bool bInversed, TQRect pArea)
{
    int progress;

    int Width       = orgImage->width();
    int Height      = orgImage->height();
    uchar* data     = orgImage->bits();
    bool sixteenBit = orgImage->sixteenBit();
    int bytesDepth  = orgImage->bytesDepth();
    uchar* pResBits = destImage->bits();

    // We working on full image.
    int xMin = 0;
    int xMax = Width;
    int yMin = 0;
    int yMax = Height;

    // If we working in preview mode, else we using the preview area.
    if ( pArea.isValid() )
    {
        xMin = pArea.x();
        xMax = pArea.x() + pArea.width();
        yMin = pArea.y();
        yMax = pArea.y() + pArea.height();
    }

    if (pArea.isValid())
    {
        //UNTESTED (unused)

        // We do not have access to the loop of the Gaussian blur,
        // so we have to cut the image that we run the effect on.
        int xMinBlur = xMin - BlurRadius;
        int xMaxBlur = xMax + BlurRadius;
        int yMinBlur = yMin - BlurRadius;
        int yMaxBlur = yMax + BlurRadius;
        Digikam::DImg areaImage = orgImage->copy(xMinBlur, yMaxBlur, xMaxBlur - xMinBlur, yMaxBlur - yMinBlur);

        Digikam::DImgGaussianBlur(this, *orgImage, *destImage, 10, 75, BlurRadius);

        // I am unsure about differences of 1 pixel
        destImage->bitBltImage(&areaImage, xMinBlur, yMinBlur);
        destImage->bitBltImage(orgImage, 0, 0, Width, yMinBlur, 0, 0);
        destImage->bitBltImage(orgImage, 0, yMinBlur, xMinBlur, yMaxBlur - yMinBlur, 0, yMinBlur);
        destImage->bitBltImage(orgImage, xMaxBlur + 1, yMinBlur, Width - xMaxBlur - 1, yMaxBlur - yMinBlur, yMaxBlur, yMinBlur);
        destImage->bitBltImage(orgImage, 0, yMaxBlur + 1, Width, Height - yMaxBlur - 1, 0, yMaxBlur);

        postProgress(80);
    }
    else
    {
        // copy bits for blurring
        memcpy(pResBits, data, orgImage->numBytes());

        // Gaussian blur using the BlurRadius parameter.
        Digikam::DImgGaussianBlur(this, *orgImage, *destImage, 10, 80, BlurRadius);
    }

    // Blending results.

    int nBlendFactor;
    double lfRadius;
    int offset;

    Digikam::DColor colorOrgImage, colorBlurredImage;
    int alpha;
    uchar *ptr;

    // get composer for default blending
    Digikam::DColorComposer *composer = Digikam::DColorComposer::getComposer(Digikam::DColorComposer::PorterDuffNone);

    int nh = 0, nw = 0;

    for (int h = yMin; !m_cancel && (h < yMax); h++)
    {
        nh = Y - h;

        for (int w = xMin; !m_cancel && (w < xMax); w++)
        {
            nw = X - w;

            lfRadius = sqrt (nh * nh + nw * nw);

            if (sixteenBit)
                nBlendFactor = LimitValues16 ((int)(65535.0 * lfRadius / (double)BlendRadius));
            else
                nBlendFactor = LimitValues8  ((int)(255.0 * lfRadius / (double)BlendRadius));

            // Read color values
            offset = GetOffset(Width, w, h, bytesDepth);
            ptr = pResBits + offset;
            colorOrgImage.setColor(data + offset, sixteenBit);
            colorBlurredImage.setColor(ptr, sixteenBit);

            // Preserve alpha
            alpha = colorOrgImage.alpha();

            // In normal mode, the image is focused in the middle
            // and less focused towards the border.
            // In inversed mode, the image is more focused towards the edge
            // and less focused in the middle.
            // This is achieved by swapping src and dest while blending.
            if (bInversed)
            {
                // set blending alpha value as src alpha. Original value is stored above.
                colorOrgImage.setAlpha(nBlendFactor);
                // compose colors, writing to dest - colorBlurredImage
                composer->compose(colorBlurredImage, colorOrgImage);
                // restore alpha
                colorBlurredImage.setAlpha(alpha);
                // write color to destination
                colorBlurredImage.setPixel(ptr);
            }
            else
            {
                // set blending alpha value as src alpha. Original value is stored above.
                colorBlurredImage.setAlpha(nBlendFactor);
                // compose colors, writing to dest - colorOrgImage
                composer->compose(colorOrgImage, colorBlurredImage);
                // restore alpha
                colorOrgImage.setAlpha(alpha);
                // write color to destination
                colorOrgImage.setPixel(ptr);
            }
        }

        // Update the progress bar in dialog.
        progress = (int) (80.0 + ((double)(h - yMin) * 20.0) / (yMax - yMin));

        if (progress%5 == 0)
            postProgress(progress);
    }

    delete composer;
}

/* Function to apply the farBlur effect backported from ImageProcessing version 2                                            
 *                                                                                  
 * data             => The image data in RGBA mode.                            
 * Width            => Width of image.                          
 * Height           => Height of image.                            
 * Distance         => Distance value                                            
 *                                                                                  
 * Theory           => This is an interesting effect, the blur is applied in that   
 *                     way: (the value "1" means pixel to be used in a blur calc, ok?)
 *                     e.g. With distance = 2 
 *                                            |1|1|1|1|1| 
 *                                            |1|0|0|0|1| 
 *                                            |1|0|C|0|1| 
 *                                            |1|0|0|0|1| 
 *                                            |1|1|1|1|1| 
 *                     We sum all the pixels with value = 1 and apply at the pixel with*
 *                     the position "C".
 */
void BlurFX::farBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Distance)
{
    if (Distance < 1) return;

    // we need to create our kernel
    // e.g. distance = 3, so kernel={3 1 1 2 1 1 3}

    int *nKern = new int[Distance * 2 + 1];

    for (int i = 0; i < Distance * 2 + 1; i++)
    {
        // the first element is 3
        if (i == 0)
            nKern[i] = 2;
        // the center element is 2
        else if (i == Distance)
            nKern[i] = 3;
        // the last element is 3
        else if (i == Distance * 2)
            nKern[i] = 3;
        // all other elements will be 1
        else
            nKern[i] = 1;
    }

    // now, we apply a convolution with kernel
    MakeConvolution(orgImage, destImage, Distance, nKern);

    // we must delete to free memory
    delete [] nKern;
}

/* Function to apply the SmartBlur effect                                           
 *                                                                                  
 * data             => The image data in RGBA mode.  
 * Width            => Width of image.                          
 * Height           => Height of image.                            
 * Radius           => blur matrix radius.                                         
 * Strenght         => Color strenght.                                         
 *                                                                                  
 * Theory           => Similar to SmartBlur from Photoshop, this function has the   
 *                     same engine as Blur function, but, in a matrix with n        
 *                     dimentions, we take only colors that pass by sensibility filter
 *                     The result is a clean image, not totally blurred, but a image  
 *                     with correction between pixels.      
 */

void BlurFX::smartBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Radius, int Strength)
{
    if (Radius <= 0) return;

    int Width       = orgImage->width();
    int Height      = orgImage->height();
    uchar* data     = orgImage->bits();
    bool sixteenBit = orgImage->sixteenBit();
    int bytesDepth  = orgImage->bytesDepth();
    uchar* pResBits = destImage->bits();

    int progress;
    int sumR, sumG, sumB, nCount, w, h, a;

    int StrengthRange = Strength;
    if (sixteenBit)
        StrengthRange = (StrengthRange + 1) * 256 - 1;

    Digikam::DColor color, radiusColor, radiusColorBlur;
    int offset, loopOffset;

    uchar* pBlur    = new uchar[orgImage->numBytes()];

    // We need to copy our bits to blur bits

    memcpy (pBlur, data, orgImage->numBytes());

    // we have reached the main loop

    for (h = 0; !m_cancel && (h < Height); h++)
    {
        for (w = 0; !m_cancel && (w < Width); w++)
        {
            // we initialize the variables
            sumR = sumG = sumB = nCount = 0;

            // read color
            offset = GetOffset(Width, w, h, bytesDepth);
            color.setColor(data + offset, sixteenBit);

            // ...we enter this loop to sum the bits
            for (a = -Radius; !m_cancel && (a <= Radius); a++)
            {
                // verify if is inside the rect
                if (IsInside( Width, Height, w + a, h))
                {
                    // read color
                    loopOffset = GetOffset(Width, w+a, h, bytesDepth);
                    radiusColor.setColor(data + loopOffset, sixteenBit);

                    // now, we have to check if is inside the sensibility filter
                    if (IsColorInsideTheRange (color.red(), color.green(), color.blue(),
                        radiusColor.red(), radiusColor.green(), radiusColor.blue(),
                        StrengthRange))
                    {
                        // finally we sum the bits
                        sumR += radiusColor.red();
                        sumG += radiusColor.green();
                        sumB += radiusColor.blue();
                    }
                    else
                    {
                        // finally we sum the bits
                        sumR += color.red();
                        sumG += color.green();
                        sumB += color.blue();
                    }

                    // increment counter
                    nCount++;
                }
            }

            // now, we have to calc the arithmetic average
            color.setRed  (sumR / nCount);
            color.setGreen(sumG / nCount);
            color.setBlue (sumB / nCount);

            // write color to destination
            color.setPixel(pBlur + offset);
        }

        // Update the progress bar in dialog.
        progress = (int) (((double)h * 50.0) / Height);

        if (progress%5 == 0)
            postProgress(progress);
    }

    // we have reached the second part of main loop

    for (w = 0; !m_cancel && (w < Width); w++)
    {
        for (h = 0;!m_cancel && ( h < Height); h++)
        {
            // we initialize the variables
            sumR = sumG = sumB = nCount = 0;

            // read color
            offset = GetOffset(Width, w, h, bytesDepth);
            color.setColor(data + offset, sixteenBit);

            // ...we enter this loop to sum the bits
            for (a = -Radius; !m_cancel && (a <= Radius); a++)
            {
                // verify if is inside the rect
                if (IsInside( Width, Height, w, h + a))
                {
                    // read color
                    loopOffset = GetOffset(Width, w, h+a, bytesDepth);
                    radiusColor.setColor(data + loopOffset, sixteenBit);

                    // now, we have to check if is inside the sensibility filter
                    if (IsColorInsideTheRange (color.red(), color.green(), color.blue(),
                        radiusColor.red(), radiusColor.green(), radiusColor.blue(),
                        StrengthRange))
                    {
                        radiusColorBlur.setColor(pBlur + loopOffset, sixteenBit);
                        // finally we sum the bits
                        sumR += radiusColorBlur.red();
                        sumG += radiusColorBlur.green();
                        sumB += radiusColorBlur.blue();
                    }
                    else
                    {
                        // finally we sum the bits
                        sumR += color.red();
                        sumG += color.green();
                        sumB += color.blue();
                    }

                    // increment counter
                    nCount++;
                }
            }

            // now, we have to calc the arithmetic average
            color.setRed  (sumR / nCount);
            color.setGreen(sumG / nCount);
            color.setBlue (sumB / nCount);

            // write color to destination
            color.setPixel(pResBits + offset);
        }

        // Update the progress bar in dialog.
        progress = (int) (50.0 + ((double)w * 50.0) / Width);

        if (progress%5 == 0)
            postProgress(progress);
    }

    // now, we must free memory
    delete [] pBlur;
}

/* Function to apply the motionBlur effect backported from ImageProcessing version 2                                              
 *                                                                                  
 * data             => The image data in RGBA mode.                            
 * Width            => Width of image.                          
 * Height           => Height of image.                            
 * Distance         => Distance value 
 * Angle            => Angle direction (degrees)                                               
 *                                                                                  
 * Theory           => Similar to MotionBlur from Photoshop, the engine is very       
 *                     simple to undertand, we take a pixel (duh!), with the angle we   
 *                     will taking near pixels. After this we blur (add and do a       
 *                     division).
 */
void BlurFX::motionBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Distance, double Angle)
{
    if (Distance == 0) return;
    int progress;

    int Width       = orgImage->width();
    int Height      = orgImage->height();
    uchar* data     = orgImage->bits();
    bool sixteenBit = orgImage->sixteenBit();
    int bytesDepth  = orgImage->bytesDepth();
    uchar* pResBits = destImage->bits();

    Digikam::DColor color;
    int offset;

    // we try to avoid division by 0 (zero)
    if (Angle == 0.0) Angle = 360.0;

    int sumR, sumG, sumB, nCount, nw, nh;
    double nAngX, nAngY;

    // we initialize cos and sin for a best performance
    nAngX = cos ((2.0 * M_PI) / (360.0 / Angle));
    nAngY = sin ((2.0 * M_PI) / (360.0 / Angle));

    // total of bits to be taken is given by this formula
    nCount = Distance * 2 + 1;

    // we will alloc size and calc the possible results
    int *lpXArray = new int[nCount];
    int *lpYArray = new int[nCount];

    for (int i = 0; i < nCount; i++)
    {
        lpXArray[i] = lround( (double)(i - Distance) * nAngX);
        lpYArray[i] = lround( (double)(i - Distance) * nAngY);
    }

    // we have reached the main loop

    for (int h = 0; !m_cancel && (h < Height); h++)
    {
        for (int w = 0; !m_cancel && (w < Width); w++)
        {
            // we initialize the variables
            sumR = sumG = sumB = 0;

            // ...we enter this loop to sum the bits
            for (int a = -Distance; !m_cancel && (a <= Distance); a++)
            {
                // we need to calc the positions
                nw = w + lpXArray[a + Distance];
                nh = h + lpYArray[a + Distance];

                offset = GetOffsetAdjusted(Width, Height, nw, nh, bytesDepth);
                color.setColor(data + offset, sixteenBit);

                // we sum the bits
                sumR += color.red();
                sumG += color.green();
                sumB += color.blue();
            }

            if (nCount == 0) nCount = 1;

            // calculate pointer
            offset = GetOffset(Width, w, h, bytesDepth);
            // read color to preserve alpha
            color.setColor(data + offset, sixteenBit);

            // now, we have to calc the arithmetic average
            color.setRed  (sumR / nCount);
            color.setGreen(sumG / nCount);
            color.setBlue (sumB / nCount);

            // write color to destination
            color.setPixel(pResBits + offset);
        }

        // Update the progress bar in dialog.
        progress = (int) (((double)h * 100.0) / Height);

        if (progress%5 == 0)
            postProgress(progress);
    }

    delete [] lpXArray;
    delete [] lpYArray;
}

/* Function to apply the softenerBlur effect                                            
 *                                                                                  
 * data             => The image data in RGBA mode.                            
 * Width            => Width of image.                          
 * Height           => Height of image.                            
 *                                                                                  
 * Theory           => An interesting blur-like function. In dark tones we apply a   
 *                     blur with 3x3 dimentions, in light tones, we apply a blur with   
 *                     5x5 dimentions. Easy, hun?
 */
void BlurFX::softenerBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage)
{
    int progress;

    int Width       = orgImage->width();
    int Height      = orgImage->height();
    uchar* data     = orgImage->bits();
    bool sixteenBit = orgImage->sixteenBit();
    int bytesDepth  = orgImage->bytesDepth();
    uchar* pResBits = destImage->bits();

    int SomaR = 0, SomaG = 0, SomaB = 0;
    int Gray;

    Digikam::DColor color, colorSoma;
    int offset, offsetSoma;

    int grayLimit = sixteenBit ? 32767 : 127;

    for (int h = 0; !m_cancel && (h < Height); h++)
    {
        for (int w = 0; !m_cancel && (w < Width); w++)
        {
            SomaR = SomaG = SomaB = 0;

            offset = GetOffset(Width, w, h, bytesDepth);
            color.setColor(data + offset, sixteenBit);

            Gray = (color.red() + color.green() + color.blue()) / 3;

            if (Gray > grayLimit)
            {
                // 7x7
                for (int a = -3; !m_cancel && (a <= 3); a++)
                {
                    for (int b = -3; !m_cancel && (b <= 3); b++)
                    {
                        if ((h + a < 0) || (w + b < 0))
                            offsetSoma = offset;
                        else
                            offsetSoma = GetOffset(Width, (w + Lim_Max (w, b, Width)),
                                                   (h + Lim_Max (h, a, Height)), bytesDepth);
                        colorSoma.setColor(data + offsetSoma, sixteenBit);

                        SomaR += colorSoma.red();
                        SomaG += colorSoma.green();
                        SomaB += colorSoma.blue();
                    }
                }

                // 7*7 = 49
                color.setRed  (SomaR / 49);
                color.setGreen(SomaG / 49);
                color.setBlue (SomaB / 49);
                color.setPixel(pResBits + offset);
            }
            else
            {
                // 3x3
                for (int a = -1; !m_cancel && (a <= 1); a++)
                {
                    for (int b = -1; !m_cancel && (b <= 1); b++)
                    {
                        if ((h + a < 0) || (w + b < 0))
                            offsetSoma = offset;
                        else
                            offsetSoma = GetOffset(Width, (w + Lim_Max (w, b, Width)),
                                (h + Lim_Max (h, a, Height)), bytesDepth);
                        colorSoma.setColor(data + offsetSoma, sixteenBit);

                        SomaR += colorSoma.red();
                        SomaG += colorSoma.green();
                        SomaB += colorSoma.blue();
                    }
                }

                // 3*3 = 9
                color.setRed  (SomaR / 9);
                color.setGreen(SomaG / 9);
                color.setBlue (SomaB / 9);
                color.setPixel(pResBits + offset);
            }
        }

        // Update the progress bar in dialog.
        progress = (int) (((double)h * 100.0) / Height);

        if (progress%5 == 0)
            postProgress(progress);
    }
}

/* Function to apply the shake blur effect                                            
 *                                                                                  
 * data             => The image data in RGBA mode.                            
 * Width            => Width of image.                          
 * Height           => Height of image.                            
 * Distance         => Distance between layers (from origin)                        
 *                                                                                  
 * Theory           => Similar to Fragment effect from Photoshop. We create 4 layers
 *                    each one has the same distance from the origin, but have       
 *                    different positions (top, button, left and right), with these 4 
 *                    layers, we join all the pixels.                 
 */
void BlurFX::shakeBlur(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Distance)
{
    int progress;

    int Width       = orgImage->width();
    int Height      = orgImage->height();
    uchar* data     = orgImage->bits();
    bool sixteenBit = orgImage->sixteenBit();
    int bytesDepth  = orgImage->bytesDepth();
    uchar* pResBits = destImage->bits();

    Digikam::DColor color, colorLayer, color1, color2, color3, color4;
    int offset, offsetLayer;

    int numBytes = orgImage->numBytes();
    uchar* Layer1 = new uchar[numBytes];
    uchar* Layer2 = new uchar[numBytes];
    uchar* Layer3 = new uchar[numBytes];
    uchar* Layer4 = new uchar[numBytes];

    int h, w, nw, nh;

    for (h = 0; !m_cancel && (h < Height); h++)
    {
        for (w = 0; !m_cancel && (w < Width); w++)
        {
            offsetLayer = GetOffset(Width, w, h, bytesDepth);

            nh = (h + Distance >= Height) ? Height - 1 : h + Distance;
            offset = GetOffset(Width, w, nh, bytesDepth);
            color.setColor(data + offset, sixteenBit);
            color.setPixel(Layer1 + offsetLayer);

            nh = (h - Distance < 0) ? 0 : h - Distance;
            offset = GetOffset(Width, w, nh, bytesDepth);
            color.setColor(data + offset, sixteenBit);
            color.setPixel(Layer2 + offsetLayer);

            nw = (w + Distance >= Width) ? Width - 1 : w + Distance;
            offset = GetOffset(Width, nw, h, bytesDepth);
            color.setColor(data + offset, sixteenBit);
            color.setPixel(Layer3 + offsetLayer);

            nw = (w - Distance < 0) ? 0 : w - Distance;
            offset = GetOffset(Width, nw, h, bytesDepth);
            color.setColor(data + offset, sixteenBit);
            color.setPixel(Layer4 + offsetLayer);
        }

        // Update the progress bar in dialog.
        progress = (int) (((double)h * 50.0) / Height);

        if (progress%5 == 0)
            postProgress(progress);
    }

    for (int h = 0; !m_cancel && (h < Height); h++)
    {
        for (int w = 0; !m_cancel && (w < Width); w++)
        {
            offset = GetOffset(Width, w, h, bytesDepth);
            // read original data to preserve alpha
            color.setColor(data + offset, sixteenBit);
            // read colors from all four layers
            color1.setColor(Layer1 + offset, sixteenBit);
            color2.setColor(Layer2 + offset, sixteenBit);
            color3.setColor(Layer3 + offset, sixteenBit);
            color4.setColor(Layer4 + offset, sixteenBit);

            // set color components of resulting color
            color.setRed  ( (color1.red()   + color2.red()   + color3.red()   + color4.red())   / 4 );
            color.setGreen( (color1.green() + color2.green() + color3.green() + color4.green()) / 4 );
            color.setBlue ( (color1.blue()  + color2.blue()  + color3.blue()  + color4.blue())  / 4 );

            color.setPixel(pResBits + offset);
        }

        // Update the progress bar in dialog.
        progress = (int) (50.0 + ((double)h * 50.0) / Height);

        if (progress%5 == 0)
            postProgress(progress);
    }

    delete [] Layer1;
    delete [] Layer2;
    delete [] Layer3;
    delete [] Layer4;
}

/* Function to apply the frostGlass effect                                            
 *                                                                                  
 * data             => The image data in RGBA mode.                            
 * Width            => Width of image.                          
 * Height           => Height of image.                            
 * Frost            => Frost value 
 *                                                                                  
 * Theory           => Similar to Diffuse effect, but the random byte is defined   
 *                     in a matrix. Diffuse uses a random diagonal byte.
 */
void BlurFX::frostGlass(Digikam::DImg *orgImage, Digikam::DImg *destImage, int Frost)
{
    int progress;

    int Width       = orgImage->width();
    int Height      = orgImage->height();
    uchar* data     = orgImage->bits();
    bool sixteenBit = orgImage->sixteenBit();
    int bytesDepth  = orgImage->bytesDepth();
    uchar* pResBits = destImage->bits();

    Frost = (Frost < 1) ? 1 : (Frost > 10) ? 10 : Frost;

    int h, w;

    Digikam::DColor color;
    int offset;

    // Randomize.

    TQDateTime dt = TQDateTime::currentDateTime();
    TQDateTime Y2000( TQDate(2000, 1, 1), TQTime(0, 0, 0) );
    uint seed = dt.secsTo(Y2000);

    int range = sixteenBit ? 65535 : 255;

    // it is a huge optimizsation to allocate these here once
    uchar *IntensityCount = new uchar[range + 1];
    uint *AverageColorR = new uint[range + 1];
    uint *AverageColorG = new uint[range + 1];
    uint *AverageColorB = new uint[range + 1];

    for (h = 0; !m_cancel && (h < Height); h++)
    {
        for (w = 0; !m_cancel && (w < Width); w++)
        {
            offset = GetOffset(Width, w, h, bytesDepth);
            // read color to preserve alpha
            color.setColor(data + offset, sixteenBit);

            // get random color from surrounding of w|h
            color = RandomColor (data, Width, Height, sixteenBit, bytesDepth, 
                                 w, h, Frost, color.alpha(), &seed, range, IntensityCount,
                                 AverageColorR, AverageColorG, AverageColorB);

            // write color to destination
            color.setPixel(pResBits + offset);
        }

        // Update the progress bar in dialog.
        progress = (int) (((double)h * 100.0) / Height);

        if (progress%5 == 0)
            postProgress(progress);
    }

    delete [] IntensityCount;
    delete [] AverageColorR;
    delete [] AverageColorG;
    delete [] AverageColorB;
}

/* Function to apply the mosaic effect backported from ImageProcessing version 2                                             
 *                                                                                  
 * data             => The image data in RGBA mode.                            
 * Width            => Width of image.                          
 * Height           => Height of image.                            
 * Size             => Size of mosaic .
 *                                                                                  
 * Theory           => Ok, you can find some mosaic effects on PSC, but this one   
 *                     has a great feature, if you see a mosaic in other code you will
 *                     see that the corner pixel doesn't change. The explanation is   
 *                     simple, the color of the mosaic is the same as the first pixel 
 *                     get. Here, the color of the mosaic is the same as the mosaic   
 *                     center pixel. 
 *                     Now the function scan the rows from the top (like photoshop).
 */
void BlurFX::mosaic(Digikam::DImg *orgImage, Digikam::DImg *destImage, int SizeW, int SizeH)
{
    int progress;

    int Width       = orgImage->width();
    int Height      = orgImage->height();
    uchar* data     = orgImage->bits();
    bool sixteenBit = orgImage->sixteenBit();
    int bytesDepth  = orgImage->bytesDepth();
    uchar* pResBits = destImage->bits();

    // we need to check for valid values
    if (SizeW < 1) SizeW = 1;
    if (SizeH < 1) SizeH = 1;
    if ((SizeW == 1) && (SizeH == 1)) return;

    Digikam::DColor color;
    int offsetCenter, offset;

    // this loop will never look for transparent colors

    for (int h = 0; !m_cancel && (h < Height); h += SizeH)
    {
        for (int w = 0; !m_cancel && (w < Width); w += SizeW)
        {
            // we have to find the center pixel for mosaic's rectangle

            offsetCenter = GetOffsetAdjusted(Width, Height, w + (SizeW / 2), h + (SizeH / 2), bytesDepth);
            color.setColor(data + offsetCenter, sixteenBit);

            // now, we fill the mosaic's rectangle with the center pixel color

            for (int subw = w; !m_cancel && (subw <= w + SizeW); subw++)
            {
                for (int subh = h; !m_cancel && (subh <= h + SizeH); subh++)
                {
                    // if is inside...
                    if (IsInside(Width, Height, subw, subh))
                    {
                        // set color
                        offset = GetOffset(Width, subw, subh, bytesDepth);
                        color.setPixel(pResBits + offset);
                    }
                }
            }
        }

        // Update the progress bar in dialog.
        progress = (int) (((double)h * 100.0) / Height);

        if (progress%5 == 0)
            postProgress(progress);
    }
}

/* Function to get a color in a matriz with a determined size                       
 *                                                                                  
 * Bits              => Bits array                                                   
 * Width             => Image width                                                  
 * Height            => Image height                                                
 * X                 => Position horizontal                                          
 * Y                 => Position vertical                                            
 * Radius            => The radius of the matrix to be created                     
 *                                                                                 
 * Theory            => This function takes from a distinct matrix a random color  
 */
Digikam::DColor BlurFX::RandomColor(uchar *Bits, int Width, int Height, bool sixteenBit, int bytesDepth,
                                    int X, int Y, int Radius,
                                    int alpha, uint *randomSeed, int range, uchar *IntensityCount,
                                    uint *AverageColorR, uint *AverageColorG, uint *AverageColorB)
{
    Digikam::DColor color;
    int offset;

    int w, h, counter = 0;

    int I;

    // For 16 bit we have a problem here because this takes 255 times longer,
    // and the algorithm is really slow for 16 bit, but I think this cannot be avoided.
    memset(IntensityCount, 0, range );
    memset(AverageColorR,  0, range );
    memset(AverageColorG,  0, range );
    memset(AverageColorB,  0, range );

    for (w = X - Radius; !m_cancel && (w <= X + Radius); w++)
    {
        for (h = Y - Radius; !m_cancel && (h <= Y + Radius); h++)
        {
            if ((w >= 0) && (w < Width) && (h >= 0) && (h < Height))
            {
                offset = GetOffset(Width, w, h, bytesDepth);
                color.setColor(Bits + offset, sixteenBit);
                I = GetIntensity (color.red(), color.green(), color.blue());
                IntensityCount[I]++;
                counter++;

                if (IntensityCount[I] == 1)
                {
                    AverageColorR[I] = color.red();
                    AverageColorG[I] = color.green();
                    AverageColorB[I] = color.blue();
                }
                else
                {
                    AverageColorR[I] += color.red();
                    AverageColorG[I] += color.green();
                    AverageColorB[I] += color.blue();
                }
            }
        }
    }

    // check for m_cancel here before entering the do loop (will crash with SIGFPE otherwise)
    if (m_cancel)
        return Digikam::DColor(0, 0, 0, 0, sixteenBit);

    int RandNumber, count, Index, ErrorCount = 0;
    int J;

    do
    {
        RandNumber = abs( (int)((rand_r(randomSeed) + 1) * ((double)counter / (1 + (double) RAND_MAX))) );
        count = 0;
        Index = 0;

        do
        {
            count += IntensityCount[Index];
            Index++;
        }
        while (count < RandNumber && !m_cancel);

        J = Index - 1;
        ErrorCount++;
    }
    while ((IntensityCount[J] == 0) && (ErrorCount <= counter)  && !m_cancel);

    if (m_cancel)
        return Digikam::DColor(0, 0, 0, 0, sixteenBit);


    color.setSixteenBit(sixteenBit);
    color.setAlpha(alpha);

    if (ErrorCount >= counter)
    {
        color.setRed  (AverageColorR[J] / counter);
        color.setGreen(AverageColorG[J] / counter);
        color.setBlue (AverageColorB[J] / counter);
    }
    else
    {
        color.setRed  (AverageColorR[J] / IntensityCount[J]);
        color.setGreen(AverageColorG[J] / IntensityCount[J]);
        color.setBlue (AverageColorB[J] / IntensityCount[J]);
    }

    return color;
}

/* Function to simple convolve a unique pixel with a determined radius                
 *                                                                                    
 * data             => The image data in RGBA mode.  
 * Width            => Width of image.                          
 * Height           => Height of image.                            
 * Radius           => kernel radius, e.g. rad=1, so array will be 3X3               
 * Kernel           => kernel array to apply.
 *                                                                                    
 * Theory           => I've worked hard here, but I think this is a very smart       
 *                     way to convolve an array, its very hard to explain how I reach    
 *                     this, but the trick here its to store the sum used by the       
 *                     previous pixel, so we sum with the other pixels that wasn't get 
 */
void BlurFX::MakeConvolution (Digikam::DImg *orgImage, Digikam::DImg *destImage, int Radius, int Kernel[])
{
    if (Radius <= 0) return;

    int Width       = orgImage->width();
    int Height      = orgImage->height();
    uchar* data     = orgImage->bits();
    bool sixteenBit = orgImage->sixteenBit();
    int bytesDepth  = orgImage->bytesDepth();
    uchar* pOutBits = destImage->bits();

    int progress;
    int n, h, w;

    int nSumR, nSumG, nSumB, nCount;
    int nKernelWidth = Radius * 2 + 1;
    int range = sixteenBit ? 65536 : 256;
    Digikam::DColor color;
    int offset;

    uchar* pBlur = new uchar[orgImage->numBytes()];

    // We need to copy our bits to blur bits

    memcpy (pBlur, data, orgImage->numBytes());

    // We need to alloc a 2d array to help us to store the values

    int** arrMult = Alloc2DArray (nKernelWidth, range);

    for (int i = 0; i < nKernelWidth; i++)
        for (int j = 0; j < range; j++)
            arrMult[i][j] = j * Kernel[i];

    // Now, we enter in the main loop

    for (h = 0; !m_cancel && (h < Height); h++)
    {
        for (w = 0; !m_cancel && (w < Width); w++)
        {
            // initialize the variables
            nSumR = nSumG = nSumB = nCount = 0;

            // first of all, we need to blur the horizontal lines

            for (n = -Radius; !m_cancel && (n <= Radius); n++)
            {
                // if is inside...
                if (IsInside (Width, Height, w + n, h))
                {
                    // read color from orgImage
                    offset = GetOffset(Width, w+n, h, bytesDepth);
                    color.setColor(data + offset, sixteenBit);

                    // finally, we sum the pixels using a method similar to assigntables
                    nSumR += arrMult[n + Radius][color.red()];
                    nSumG += arrMult[n + Radius][color.green()];
                    nSumB += arrMult[n + Radius][color.blue()];

                    // we need to add the kernel value to the counter
                    nCount += Kernel[n + Radius];
                }
            }

            if (nCount == 0) nCount = 1;

            // calculate pointer
            offset = GetOffset(Width, w, h, bytesDepth);
            // read color from orgImage to preserve alpha
            color.setColor(data + offset, sixteenBit);

            // now, we have to calc the arithmetic average
            if (sixteenBit)
            {
                color.setRed  (LimitValues16(nSumR / nCount));
                color.setGreen(LimitValues16(nSumG / nCount));
                color.setBlue (LimitValues16(nSumB / nCount));
            }
            else
            {
                color.setRed  (LimitValues8(nSumR / nCount));
                color.setGreen(LimitValues8(nSumG / nCount));
                color.setBlue (LimitValues8(nSumB / nCount));
            }

            // write color to blur bits
            color.setPixel(pBlur + offset);
        }

        // Update the progress bar in dialog.
        progress = (int) (((double)h * 50.0) / Height);

        if (progress%5 == 0)
            postProgress(progress);
    }

    // We enter in the second main loop
    for (w = 0; !m_cancel && (w < Width); w++)
    {
        for (h = 0; !m_cancel && (h < Height); h++)
        {
            // initialize the variables
            nSumR = nSumG = nSumB = nCount = 0;

            // first of all, we need to blur the vertical lines
            for (n = -Radius; !m_cancel && (n <= Radius); n++)
            {
                // if is inside...
                if (IsInside(Width, Height, w, h + n))
                {
                    // read color from blur bits
                    offset = GetOffset(Width, w, h+n, bytesDepth);
                    color.setColor(pBlur + offset, sixteenBit);

                    // finally, we sum the pixels using a method similar to assigntables
                    nSumR += arrMult[n + Radius][color.red()];
                    nSumG += arrMult[n + Radius][color.green()];
                    nSumB += arrMult[n + Radius][color.blue()];

                    // we need to add the kernel value to the counter
                    nCount += Kernel[n + Radius];
                }
            }

            if (nCount == 0) nCount = 1;

            // calculate pointer
            offset = GetOffset(Width, w, h, bytesDepth);
            // read color from orgImage to preserve alpha
            color.setColor(data + offset, sixteenBit);

            // now, we have to calc the arithmetic average
            if (sixteenBit)
            {
                color.setRed  (LimitValues16(nSumR / nCount));
                color.setGreen(LimitValues16(nSumG / nCount));
                color.setBlue (LimitValues16(nSumB / nCount));
            }
            else
            {
                color.setRed  (LimitValues8(nSumR / nCount));
                color.setGreen(LimitValues8(nSumG / nCount));
                color.setBlue (LimitValues8(nSumB / nCount));
            }

            // write color to destination
            color.setPixel(pOutBits + offset);
        }

        // Update the progress bar in dialog.
        progress = (int) (50.0 + ((double)w * 50.0) / Width);

        if (progress%5 == 0)
            postProgress(progress);
    }

    // now, we must free memory
    Free2DArray (arrMult, nKernelWidth);
    delete [] pBlur;
}

}  // NameSpace DigikamBlurFXImagesPlugin
