/*
 * Copyright (c) 2005 Dmitry Baryshev <ksquirrel.iv@gmail.com>
 */

/* This file is part of the KDE libraries
    Copyright (C) 1998, 1999, 2001, 2002 Daniel M. Duley <mosfet@kde.org>
    (C) 1998, 1999 Christian Tibirna <ctibirna@total.net>
    (C) 1998, 1999 Dirk A. Mueller <mueller@kde.org>
    (C) 1999 Geert Jansen <g.t.jansen@stud.tue.nl>
    (C) 2000 Josef Weidendorfer <weidendo@in.tum.de>
    (C) 2004 Zack Rusin <zack@kde.org>

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

//
// ===================================================================
// Effects originally ported from ImageMagick for PixiePlus, plus a few
// new ones. (mosfet 05/26/2003)
// ===================================================================
//
/*
 Portions of this software are based on ImageMagick. Such portions are clearly
marked as being ported from ImageMagick. ImageMagick is copyrighted under the
following conditions:

Copyright (C) 2003 ImageMagick Studio, a non-profit organization dedicated to
making software imaging solutions freely available.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files ("ImageMagick"), to deal
in ImageMagick without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense,  and/or sell
copies of ImageMagick, and to permit persons to whom the ImageMagick is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of ImageMagick.

The software is provided "as is", without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose and noninfringement.  In no event shall
ImageMagick Studio be liable for any claim, damages or other liability,
whether in an action of contract, tort or otherwise, arising from, out of or
in connection with ImageMagick or the use or other dealings in ImageMagick.

Except as contained in this notice, the name of the ImageMagick Studio shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in ImageMagick without prior written authorization from the
ImageMagick Studio.
*/

#include "fmt_filters.h"

#include <cmath>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

namespace fmt_filters
{

#define MaxRGB 255L
#define DegreesToRadians(x) ((x)*M_PI/180.0)
#define MagickSQ2PI 2.50662827463100024161235523934010416269302368164062
#define MagickEpsilon  1.0e-12
#define MagickPI  3.14159265358979323846264338327950288419716939937510

#define F_MAX(a, b)  ((b) < (a) ? (a) : (b))
#define F_MIN(a, b)  ((a) < (b) ? (a) : (b))

static void rgb2hsv(const fmt_filters::rgb &rgb, s32 *h, s32 *s, s32 *v);
static void hsv2rgb(s32 h, s32 s, s32 v, fmt_filters::rgb *rgb);
static fmt_filters::rgba interpolateColor(const fmt_filters::image &image, double x_offset, double y_offset, const fmt_filters::rgba &background);
static u32 generateNoise(u32 pixel, fmt_filters::NoiseType noise_type);
static u32 intensityValue(s32 r, s32 g, s32 b);
static u32 intensityValue(const fmt_filters::rgba &rr);
static s32 getBlurKernel(s32 width, double sigma, double **kernel);
static void blurScanLine(double *kernel, s32 width, fmt_filters::rgba *src, fmt_filters::rgba *dest, s32 columns);
static void hull(const s32 x_offset, const s32 y_offset, const s32 polarity, const s32 columns,
                        const s32 rows, u8 *f, u8 *g);
static bool convolveImage(fmt_filters::image *image, fmt_filters::rgba **dest, const unsigned int order, const double *kernel);
static int getOptimalKernelWidth(double radius, double sigma);

template<class T>
static void scaleDown(T &val, T min, T max);

struct double_packet
{
    double red;
    double green;
    double blue;
    double alpha;
};

struct short_packet
{
    unsigned short int red;
    unsigned short int green;
    unsigned short int blue;
    unsigned short int alpha;
};

bool checkImage(const image &im)
{
    return (im.rw && im.rh && im.w && im.h && im.data);
}

// colorize tool
void colorize(const image &im, s32 red, s32 green, s32 blue)
{
    // check if all parameters are good
    if(!checkImage(im))
	return;

    if(!red && !green && !blue)
	return;

    u8 *bits;
    s32 val;
    s32 V[3] = { red, green, blue };

    // add to RED component 'red' value, and check if the result is out of bounds.
    // do the same with GREEN and BLUE channels.
    for(s32 y = 0;y < im.h;++y)
    {
        bits = im.data + im.rw * y * sizeof(rgba);

        for(s32 x = 0;x < im.w;x++)
        {
    	    for(s32 v = 0;v < 3;++v)
    	    {
	        val = (s32)*(bits + v) + V[v];

	        if(val > 255)
		    *(bits + v) = 255;
	        else if(val < 0)
		    *(bits + v) = 0;
	        else
		    *(bits + v) = val;
	    }

            bits += 4;
        }
    }
}

// brightness tool
void brightness(const image &im, s32 bn)
{
    // check if all parameters are good
    if(!checkImage(im))
	return;

    u8 *bits;
    s32 val;

    // add to all color components 'bn' value, and check if the result is out of bounds.
    for(s32 y = 0;y < im.h;++y)
    {
        bits = im.data + im.rw * y * sizeof(rgba);

        for(s32 x = 0;x < im.w;x++)
        {
            for(s32 v = 0;v < 3;v++)
            {
    	        val = bn + *bits;
	        *bits = val < 0 ? 0 : (val > 255 ? 255 : val);

                bits++;
            }

	    bits++;
        }
    }
}

// gamma tool
void gamma(const image &im, double L)
{
    // check if all parameters are good
    if(!checkImage(im))
	return;

    if(L == 0 || L < 0) L = 0.01;

    rgba *_rgba;
    u8 R, G, B;
    u8 GT[256];

    GT[0] = 0;

    // fill the array with gamma koefficients
    for (s32 x = 1; x < 256; ++x)
	GT[x] = (u8)round(255 * pow((double)x / 255.0, 1.0 / L));

    // now change gamma
    for(s32 y = 0;y < im.h;++y)
    {
        _rgba = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;x++)
        {
	    R = _rgba[x].r;
	    G = _rgba[x].g;
	    B = _rgba[x].b;

            _rgba[x].r = GT[R];
    	    _rgba[x].g = GT[G];
    	    _rgba[x].b = GT[B];
        }
    }
}

// contrast tool
void contrast(const image &im, s32 contrast)
{
    if(!checkImage(im) || !contrast)
        return;

    if(contrast < -255) contrast = -255;
    if(contrast >  255) contrast = 255;

    rgba *bits;
    u8 Ravg, Gavg, Bavg;
    s32 Ra = 0, Ga = 0, Ba = 0, Rn, Gn, Bn;

    // calculate the average values for RED, GREEN and BLUE
    // color components
    for(s32 y = 0;y < im.h;y++)
    {
        bits = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;x++)
        {
	    Ra += bits->r;
	    Ga += bits->g;
	    Ba += bits->b;

	    bits++;
        }
    }

    s32 S = im.w * im.h;

    Ravg = Ra / S;
    Gavg = Ga / S;
    Bavg = Ba / S;

    // ok, now change contrast
    // with the terms of alghoritm:
    //
    // if contrast > 0: I = (I - Avg) * 256 / (256 - contrast) + Avg
    // if contrast < 0: I = (I - Avg) * (256 + contrast) / 256 + Avg
    //
    // where
    //   I - current color component value
    //   Avg - average value of this component (Ravg, Gavg or Bavg)
    //
    for(s32 y = 0;y < im.h;y++)
    {
        bits = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;x++)
        {
	    Rn = (contrast > 0) ? ((bits->r - Ravg) * 256 / (256 - contrast) + Ravg) : ((bits->r - Ravg) * (256 + contrast) / 256 + Ravg);
	    Gn = (contrast > 0) ? ((bits->g - Gavg) * 256 / (256 - contrast) + Gavg) : ((bits->g - Gavg) * (256 + contrast) / 256 + Gavg);
	    Bn = (contrast > 0) ? ((bits->b - Bavg) * 256 / (256 - contrast) + Bavg) : ((bits->b - Bavg) * (256 + contrast) / 256 + Bavg);

	    bits->r = Rn < 0 ? 0 : (Rn > 255 ? 255 : Rn);
	    bits->g = Gn < 0 ? 0 : (Gn > 255 ? 255 : Gn);
	    bits->b = Bn < 0 ? 0 : (Bn > 255 ? 255 : Bn);

	    bits++;
        }
    }
}

// negative
void negative(const image &im)
{
    // check if all parameters are good
    if(!checkImage(im))
	return;

    rgba *bits;
    u8 R, G, B;

    for(s32 y = 0;y < im.h;y++)
    {
        bits = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;x++)
        {
	    R = bits->r;
	    G = bits->g;
	    B = bits->b;

	    bits->r = 255 - R;
	    bits->g = 255 - G;
	    bits->b = 255 - B;

	    bits++;
        }
    }
}

// swap RGB values
void swapRGB(const image &im, s32 type)
{
    // check if all parameters are good
    if(!checkImage(im) || (type != GBR && type != BRG))
	return;

    rgba *bits;
    u8 R, G, B;

    for(s32 y = 0;y < im.h;y++)
    {
        bits = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;x++)
        {
	    R = bits->r;
	    G = bits->g;
	    B = bits->b;

	    bits->r = (type == GBR) ? G : B;
	    bits->g = (type == GBR) ? B : R;
	    bits->b = (type == GBR) ? R : G;

            bits++;
        }
    }
}

// blend
void blend(const image &im, const rgb &rgb, float opacity)
{
    // check parameters
    if(!checkImage(im))
	return;

    scaleDown(opacity, 0.0f, 1.0f);

    rgba *bits;
    s32 r = rgb.r, g = rgb.g, b = rgb.b;

    // blend!
    for(s32 y = 0;y < im.h;++y)
    {
        bits = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;x++)
        {
            bits->r = bits->r + (u8)((b - bits->r) * opacity);
            bits->g = bits->g + (u8)((g - bits->g) * opacity);
            bits->b = bits->b + (u8)((r - bits->b) * opacity);

            bits++;
        }
    }
}

void flatten(const image &im, const rgb &ca, const rgb &cb)
{
    if(!checkImage(im))
      return;

    s32 r1 = ca.r; s32 r2 = cb.r;
    s32 g1 = ca.g; s32 g2 = cb.g;
    s32 b1 = ca.b; s32 b2 = cb.b;
    s32 min = 0, max = 255;
    s32 mean;

    rgba *bits;
    rgb _rgb;

    for(s32 y = 0;y < im.h;++y)
    {
        bits = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;++x)
        {
            mean = (bits->r + bits->g + bits->b) / 3;
            min = F_MIN(min, mean);
            max = F_MAX(max, mean);
            bits++;
        }
    }

    // Conversion factors
    float sr = ((float) r2 - r1) / (max - min);
    float sg = ((float) g2 - g1) / (max - min);
    float sb = ((float) b2 - b1) / (max - min);

    // Repaint the image
    for(s32 y = 0;y < im.h;++y)
    {
        bits = (rgba *)im.data + im.w*y;

        for(s32 x = 0;x < im.w;++x)
        {
            mean = (bits->r + bits->g + bits->b) / 3;

            bits->r = (s32)(sr * (mean - min) + r1 + 0.5);
            bits->g = (s32)(sg * (mean - min) + g1 + 0.5);
            bits->b = (s32)(sb * (mean - min) + b1 + 0.5);

            bits++;
        }
    }
}

void fade(const image &im, const rgb &rgb, float val)
{
    if(!checkImage(im))
        return;

    u8 tbl[256];

    for (s32 i = 0;i < 256;i++)
        tbl[i] = (s32)(val * i + 0.5);

    s32 r, g, b, cr, cg, cb;

    rgba *bits;

    for(s32 y = 0;y < im.h;y++)
    {
        bits = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;x++)
        {
            cr = bits->r;
            cg = bits->g;
            cb = bits->b;

            r = (cr > rgb.r) ? (cr - tbl[cr - rgb.r]) : (cr + tbl[rgb.r - cr]);
            g = (cg > rgb.g) ? (cg - tbl[cg - rgb.g]) : (cg + tbl[rgb.g - cg]);
            b = (cb > rgb.b) ? (cb - tbl[cb - rgb.b]) : (cb + tbl[rgb.b - cb]);

            bits->r = r;
            bits->g = g;
            bits->b = b;

            bits++;
        }
    }
}

void gray(const image &im)
{
    if(!checkImage(im))
        return;

    rgba *bits;
    s32 g;

    for(s32 y = 0;y < im.h;y++)
    {
        bits = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;x++)
        {
            g = (bits->r * 11 + bits->g * 16 + bits->b * 5)/32;

            bits->r = g;
            bits->g = g;
            bits->b = g;

            bits++;
        }
    }
}

void desaturate(const image &im, float desat)
{
    if(!checkImage(im))
      return;

    scaleDown(desat, 0.0f, 1.0f);

    rgba *bits;
    s32 h = 0, s = 0, v = 0;

    for(s32 y = 0;y < im.h;y++)
    {
        bits = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;x++)
        {
            rgb _rgb(bits->r, bits->g, bits->b);
            rgb2hsv(_rgb, &h, &s, &v);
            hsv2rgb(h, (s32)(s * (1.0 - desat)), v, &_rgb);

            bits->r = _rgb.r;
            bits->g = _rgb.g;
            bits->b = _rgb.b;

            bits++;
        }
    }
}

void threshold(const image &im, u32 trh)
{
    if(!checkImage(im))
        return;

    scaleDown(trh, (u32)0, (u32)255);

    rgba *bits;

    for(s32 y = 0;y < im.h;y++)
    {
        bits = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;x++)
        {
            if(intensityValue(bits->r, bits->g, bits->b) < trh)
                bits->r = bits->g = bits->b = 0;
            else
                bits->r = bits->g = bits->b = 255;

            bits++;
        }
    }
}

void solarize(const image &im, double factor)
{
    if(!checkImage(im))
        return;

    s32 threshold;
    rgba *bits;

    threshold = (s32)(factor * (MaxRGB+1)/100.0);

    for(s32 y = 0;y < im.h;y++)
    {
        bits = (rgba *)im.data + im.rw * y;

        for(s32 x = 0;x < im.w;x++)
        {
            bits->r = bits->r > threshold ? MaxRGB-bits->r : bits->r;
            bits->g = bits->g > threshold ? MaxRGB-bits->g : bits->g;
            bits->b = bits->b > threshold ? MaxRGB-bits->b : bits->b;

            bits++;
        }
    }
}

void spread(const image &im, u32 amount)
{
    if(!checkImage(im) || im.w < 3 || im.h < 3)
        return;

    rgba *n = new rgba [im.rw * im.rh];

    if(!n)
        return;

    s32 quantum;
    s32 x_distance, y_distance;
    rgba *bits = (rgba *)im.data, *q;

    memcpy(n, im.data, im.rw * im.rh * sizeof(rgba));

    quantum = (amount+1) >> 1;

    for(s32 y = 0;y < im.h;y++)
    {
        q = n + im.rw*y;

        for(s32 x = 0;x < im.w;x++)
        {
            x_distance = x + ((rand() & (amount+1))-quantum);
            y_distance = y + ((rand() & (amount+1))-quantum);
            x_distance = F_MIN(x_distance, im.w-1);
            y_distance = F_MIN(y_distance, im.h-1);

            if(x_distance < 0) x_distance = 0;
            if(y_distance < 0) y_distance = 0;

            *q++ = *(bits + y_distance*im.rw + x_distance);
        }
    }

    memcpy(im.data, n, im.rw * im.rh * sizeof(rgba));

    delete [] n;
}

void swirl(const image &im, double degrees, const rgba &background)
{
    if(!checkImage(im))
        return;

    double cosine, distance, factor, radius, sine, x_center, x_distance,
            x_scale, y_center, y_distance, y_scale;
    s32 x, y;

    rgba *q, *p;
    rgba *bits = (rgba *)im.data;
    rgba *dest = new rgba [im.rw * im.rh];

    if(!dest)
        return;

    memcpy(dest, im.data, im.rw * im.rh * sizeof(rgba));

    // compute scaling factor
    x_center = im.w / 2.0;
    y_center = im.h / 2.0;

    radius = F_MAX(x_center, y_center);
    x_scale=1.0;
    y_scale=1.0;

    if(im.w > im.h)
        y_scale=(double)im.w / im.h;
    else if(im.w < im.h)
        x_scale=(double)im.h / im.w;

    degrees = DegreesToRadians(degrees);

    // swirl each row

    for(y = 0;y < im.h;y++)
    {
        p = bits + im.rw * y;
        q = dest + im.rw * y;
        y_distance = y_scale * (y-y_center);

        for(x = 0;x < im.w;x++)
        {
            // determine if the pixel is within an ellipse
            *q = *p;
            x_distance = x_scale*(x-x_center);
            distance = x_distance*x_distance+y_distance*y_distance;

            if(distance < (radius*radius))
            {
                // swirl
                factor = 1.0 - sqrt(distance)/radius;
                sine = sin(degrees*factor*factor);
                cosine = cos(degrees*factor*factor);

                *q = interpolateColor(im,
                                      (cosine*x_distance-sine*y_distance)/x_scale+x_center,
                                      (sine*x_distance+cosine*y_distance)/y_scale+y_center,
                                      background);
            }

            p++;
            q++;
        }
    }

    memcpy(im.data, dest, im.rw * im.rh * sizeof(rgba));

    delete [] dest;
}

void noise(const image &im, NoiseType noise_type)
{
    if(!checkImage(im))
        return;

    s32 x, y;
    rgba *dest = new rgba [im.rw * im.rh];

    if(!dest)
        return;

    rgba *bits;
    rgba *destData;

    for(y = 0;y < im.h;++y)
    {
        bits = (rgba *)im.data + im.rw * y;
        destData = dest + im.rw * y;

        for(x = 0;x < im.w;++x)
        {
            destData[x].r = generateNoise(bits->r, noise_type);
            destData[x].g = generateNoise(bits->g, noise_type);
            destData[x].b = generateNoise(bits->b, noise_type);
            destData[x].a = bits->a;

            bits++;
        }
    }

    memcpy(im.data, dest, im.rw * im.rh * sizeof(rgba));

    delete [] dest;
}

void implode(const image &im, double _factor, const rgba &background)
{
    if(!checkImage(im))
        return;

    double amount, distance, radius;
    double x_center, x_distance, x_scale;
    double y_center, y_distance, y_scale;
    rgba *dest;
    s32 x, y;

    rgba *n = new rgba [im.rw * im.rh];

    if(!n)
        return;

    rgba *bits;

    // compute scaling factor
    x_scale = 1.0;
    y_scale = 1.0;
    x_center = (double)0.5*im.w;
    y_center = (double)0.5*im.h;
    radius=x_center;

    if(im.w > im.h)
        y_scale = (double)im.w/im.h;
    else if(im.w < im.h)
    {
        x_scale = (double)im.h/im.w;
        radius = y_center;
    }

    amount=_factor/10.0;

    if(amount >= 0)
        amount/=10.0;

    double factor;

    for(y = 0;y < im.h;++y)
    {
        bits = (rgba *)im.data + im.rw * y;
        dest =  n + im.rw * y;

        y_distance = y_scale * (y-y_center);

        for(x = 0;x < im.w;++x)
        {
            x_distance = x_scale*(x-x_center);
            distance= x_distance*x_distance+y_distance*y_distance;

            if(distance < (radius*radius))
            {
                // Implode the pixel.
                factor = 1.0;

                if(distance > 0.0)
                    factor = pow(sin(0.5000000000000001*M_PI*sqrt(distance)/radius),-amount);

                *dest = interpolateColor(im, factor*x_distance/x_scale+x_center,
                                           factor*y_distance/y_scale+y_center,
                                           background);
            }
            else
                *dest = *bits;

            bits++;
            dest++;
        }
    }

    memcpy(im.data, n, im.rw * im.rh * sizeof(rgba));

    delete [] n;
}

void despeckle(const image &im)
{
    if(!checkImage(im))
        return;

    s32 i, j, x, y;
    u8 *blue_channel, *red_channel, *green_channel, *buffer, *alpha_channel;
    s32 packets;

    static const s32
                    X[4] = {0, 1, 1,-1},
                    Y[4] = {1, 0, 1, 1};

    rgba *n = new rgba [im.rw * im.rh];

    if(!n)
        return;

    packets = (im.w+2) * (im.h+2);

    red_channel = new u8 [packets];
    green_channel = new u8 [packets];
    blue_channel = new u8 [packets];
    alpha_channel = new u8 [packets];
    buffer = new u8 [packets];

    if(!red_channel || ! green_channel || ! blue_channel || ! alpha_channel || !buffer)
    {
        if(red_channel)   delete [] red_channel;
        if(green_channel) delete [] green_channel;
        if(blue_channel)  delete [] blue_channel;
        if(alpha_channel) delete [] alpha_channel;
        if(buffer)        delete [] buffer;

        delete [] n;

        return;
    }

    // copy image pixels to color component buffers
    j = im.w+2;

    rgba *bits;

    for(y = 0;y < im.h;++y)
    {
        bits = (rgba *)im.data + im.rw*y;
        ++j;

        for(x = 0;x < im.w;++x)
        {
            red_channel[j] = bits->r;
            green_channel[j] = bits->g;
            blue_channel[j] = bits->b;
            alpha_channel[j] = bits->a;

            bits++;
            ++j;
        }

        ++j;
    }

    // reduce speckle in red channel
    for(i = 0;i < 4;i++)
    {
        hull(X[i],Y[i],1,im.w,im.h,red_channel,buffer);
        hull(-X[i],-Y[i],1,im.w,im.h,red_channel,buffer);
        hull(-X[i],-Y[i],-1,im.w,im.h,red_channel,buffer);
        hull(X[i],Y[i],-1,im.w,im.h,red_channel,buffer);
    }

    // reduce speckle in green channel
    for(i = 0;i < packets;i++)
        buffer[i] = 0;

    for(i = 0;i < 4;i++)
    {
        hull(X[i],Y[i],1,im.w,im.h,green_channel,buffer);
        hull(-X[i],-Y[i],1,im.w,im.h,green_channel,buffer);
        hull(-X[i],-Y[i],-1,im.w,im.h,green_channel,buffer);
        hull(X[i],Y[i],-1,im.w,im.h,green_channel,buffer);
    }

    // reduce speckle in blue channel
    for(i = 0;i < packets;i++)
        buffer[i] = 0;

    for(i = 0;i < 4;i++)
    {
        hull(X[i],Y[i],1,im.w,im.h,blue_channel,buffer);
        hull(-X[i],-Y[i],1,im.w,im.h,blue_channel,buffer);
        hull(-X[i],-Y[i],-1,im.w,im.h,blue_channel,buffer);
        hull(X[i],Y[i],-1,im.w,im.h,blue_channel,buffer);
    }

    // copy color component buffers to despeckled image
    j = im.w+2;

    for(y = 0;y < im.h;++y)
    {
        bits = n + im.rw*y;
        ++j;

        for(x = 0;x < im.w;++x)
        {
            *bits = rgba(red_channel[j], green_channel[j], blue_channel[j], alpha_channel[j]);

            bits++;
            ++j;
        }

        ++j;
    }

    delete [] buffer;
    delete [] red_channel;
    delete [] green_channel;
    delete [] blue_channel;
    delete [] alpha_channel;

    memcpy(im.data, n, im.rw * im.rh * sizeof(rgba));

    delete [] n;
}

void blur(const image &im, double radius, double sigma)
{
    if(!checkImage(im))
        return;

    double *kernel;
    rgba *dest;
    s32 width;
    s32 x, y;
    rgba *scanline, *temp;
    rgba *p, *q;

    if(sigma == 0.0)
        return;

    kernel = 0;

    if(radius > 0)
        width = getBlurKernel((s32)(2*ceil(radius)+1), sigma, &kernel);
    else
    {
        double *last_kernel = 0;

        width = getBlurKernel(3, sigma, &kernel);

        while((long)(MaxRGB * kernel[0]) > 0)
        {
            if(last_kernel)
                delete [] last_kernel;

            last_kernel = kernel;
            kernel = 0;

            width = getBlurKernel(width+2, sigma, &kernel);
        }

        if(last_kernel)
        {
            delete [] kernel;
            width -= 2;
            kernel = last_kernel;
        }
    }

    if(width < 3)
    {
        delete [] kernel;
        return;
    }

    dest = new rgba [im.rw * im.rh];

    if(!dest)
    {
        delete [] kernel;
        return;
    }

    scanline = new rgba [im.h];
    temp = new rgba [im.h];

    if(!scanline || !temp)
    {
        if(scanline) delete [] scanline;
        if(temp) delete [] temp;

        delete [] kernel;
        return;
    }

    rgba *bits = (rgba *)im.data;

    for(y = 0;y < im.h;++y)
    {
        p = bits + im.rw*y;
        q = dest + im.rw*y;

        blurScanLine(kernel, width, p, q, im.w);
    }

    for(x = 0;x < im.w;++x)
    {
        for(y = 0;y < im.h;++y)
            scanline[y] = *(bits + im.rw*y + x);

        blurScanLine(kernel, width, scanline, temp, im.h);

        for(y = 0;y < im.h;++y)
            *(dest + im.rw*y + x) = temp[y];
        
    }

    delete [] scanline;
    delete [] temp;
    delete [] kernel;

    memcpy(im.data, dest, im.rw * im.rh * sizeof(rgba));

    delete [] dest;
}

void equalize(const image &im)
{
    if(!checkImage(im))
        return;

    double_packet high, low, intensity, *map, *histogram;
    short_packet *equalize_map;
    s32 x, y;
    rgba *p, *q;
    long i;
    u8 r, g, b, a;

    histogram = new double_packet [256];
    map = new double_packet [256];
    equalize_map = new short_packet [256];

    if(!histogram || !map || !equalize_map)
    {
        if(histogram)    delete [] histogram;
        if(map)          delete [] map;
        if(equalize_map) delete [] equalize_map;

        return;
    }

    rgba *bits = (rgba *)im.data;

    /*
     *  Form histogram.
     */
    memset(histogram, 0, 256 * sizeof(double_packet));

    for(y = 0;y < im.h;++y)
    {
        p = bits + im.rw * y;

        for(x = 0;x < im.w;++x)
        {
            histogram[p->r].red++;
            histogram[p->g].green++;
            histogram[p->b].blue++;
            histogram[p->a].alpha++;

            p++;
        }
    }
    /*
     Integrate the histogram to get the equalization map.
     */
    memset(&intensity, 0 ,sizeof(double_packet));

    for(i = 0;i < 256;++i)
    {
        intensity.red   += histogram[i].red;
        intensity.green += histogram[i].green;
        intensity.blue  += histogram[i].blue;
        intensity.alpha += histogram[i].alpha;

        map[i] = intensity;
    }

    low=map[0];
    high=map[255];
    memset(equalize_map, 0, 256 * sizeof(short_packet));

    for(i = 0;i < 256;++i)
    {
        if(high.red != low.red)
            equalize_map[i].red=(unsigned short)
                ((65535*(map[i].red-low.red))/(high.red-low.red));
        if(high.green != low.green)
            equalize_map[i].green=(unsigned short)
                ((65535*(map[i].green-low.green))/(high.green-low.green));
        if(high.blue != low.blue)
            equalize_map[i].blue=(unsigned short)
                ((65535*(map[i].blue-low.blue))/(high.blue-low.blue));
        if(high.alpha != low.alpha)
            equalize_map[i].alpha=(unsigned short)
                ((65535*(map[i].alpha-low.alpha))/(high.alpha-low.alpha));
    }

    delete [] histogram;
    delete [] map;

    /*
     Stretch the histogram.
     */
    for(y = 0;y < im.h;++y)
    {
        q = bits + im.rw*y;

        for(x = 0;x < im.w;++x)
        {
            if(low.red != high.red)
                r = (equalize_map[(unsigned short)(q->r)].red/257);
            else
                r = q->r;
            if(low.green != high.green)
                g = (equalize_map[(unsigned short)(q->g)].green/257);
            else
                g = q->g;
            if(low.blue != high.blue)
                b = (equalize_map[(unsigned short)(q->b)].blue/257);
            else
                b = q->b;
            if(low.alpha != high.alpha)
                a = (equalize_map[(unsigned short)(q->a)].alpha/257);
            else
                a = q->a;

            *q = rgba(r, g, b, a);

            q++;
        }
    }

    delete [] equalize_map;
}

struct PointInfo
{
    double x, y, z;
};

void shade(const image &im, bool color_shading, double azimuth,
             double elevation)
{
    if(!checkImage(im))
        return;

    rgba *n = new rgba [im.rw * im.rh];

    if(!n)
        return;

    double distance, normal_distance, shade;
    s32 x, y;

    struct PointInfo light, normal;

    rgba *bits;
    rgba *q;

    azimuth = DegreesToRadians(azimuth);
    elevation = DegreesToRadians(elevation);
    light.x = MaxRGB*cos(azimuth)*cos(elevation);
    light.y = MaxRGB*sin(azimuth)*cos(elevation);
    light.z = MaxRGB*sin(elevation);
    normal.z= 2*MaxRGB;  // constant Z of surface normal

    rgba *s0, *s1, *s2;

    for(y = 0;y < im.h;++y)
    {
        bits = (rgba *)im.data + im.rw * (F_MIN(F_MAX(y-1,0),im.h-3));
        q = n + im.rw * y;

        // shade this row of pixels.
        *q++ = (*(bits+im.rw));
        bits++;

        s0 = bits;
        s1 = bits + im.rw;
        s2 = bits + 2*im.rw;

        for(x = 1;x < im.w-1;++x)
        {
            // determine the surface normal and compute shading.
            normal.x = intensityValue(*(s0-1))+intensityValue(*(s1-1))+intensityValue(*(s2-1))-
                (double) intensityValue(*(s0+1))-(double) intensityValue(*(s1+1))-
                (double) intensityValue(*(s2+1));

            normal.y = intensityValue(*(s2-1))+intensityValue(*s2)+intensityValue(*(s2+1))-
                (double) intensityValue(*(s0-1))-(double) intensityValue(*s0)-
                (double) intensityValue(*(s0+1));

            if(normal.x == 0 && normal.y == 0)
                shade = light.z;
            else
            {
                shade = 0.0;
                distance = normal.x*light.x+normal.y*light.y+normal.z*light.z;

                if(distance > 0.0)
                {
                    normal_distance = normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;

                    if(fabs(normal_distance) > 0.0000001)
                        shade=distance/sqrt(normal_distance);
                }
            }

            if(!color_shading)
            {
                *q = rgba((u8)(shade),
                           (u8)(shade),
                           (u8)(shade),
                           s1->a);
            }
            else
            {
                *q = rgba((u8)((shade * s1->r)/(MaxRGB+1)),
                           (u8)((shade * s1->g)/(MaxRGB+1)),
                           (u8)((shade * s1->b)/(MaxRGB+1)),
                           s1->a);
            }

            ++s0;
            ++s1;
            ++s2;
            q++;
        }

        *q++ = (*s1);
    }

    memcpy(im.data, n, im.rw * im.rh * sizeof(rgba));

    delete [] n;
}

void edge(image &im, double radius)
{
    if(!checkImage(im))
        return;

    double *kernel;
    int width;
    long i;
    rgba *dest = 0;

    width = getOptimalKernelWidth(radius, 0.5);

    const int W = width*width;

    if(im.w < width || im.h < width)
        return;

    kernel = new double [W];

    if(!kernel)
        return;

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

    kernel[i/2] = W-1.0;

    if(!convolveImage(&im, &dest, width, kernel))
    {
        delete [] kernel;

        if(dest)
            delete [] dest;

        return;
    }

    delete [] kernel;

    memcpy(im.data, dest, im.rw * im.rh * sizeof(rgba));

    delete [] dest;
}

void emboss(image &im, double radius, double sigma)
{
    if(!checkImage(im))
        return;

    double alpha, *kernel;
    int j, width;
    long i, u, v;
    rgba *dest = 0;

    if(sigma == 0.0)
        return;

    width = getOptimalKernelWidth(radius, sigma);

    if(im.w < width || im.h < width)
        return;
    
    kernel = new double [width*width];

    if(!kernel)
        return;

    i = 0;
    j = width/2;

    const double S = sigma * sigma;

    for(v = (-width/2);v <= (width/2);v++)
    {
        for(u=(-width/2); u <= (width/2); u++)
        {
            alpha = exp(-((double) u*u+v*v)/(2.0*S));

            kernel[i] = ((u < 0) || (v < 0) ? -8.0 : 8.0)*alpha/(2.0*MagickPI*S);

            if (u == j)
                kernel[i]=0.0;

            i++;
        }

        j--;
    }

    if(!convolveImage(&im, &dest, width, kernel))
    {
        delete [] kernel;
        return;
    }

    delete [] kernel;

    fmt_filters::image mm((u8 *)dest, im.w, im.h, im.rw, im.rh);

    equalize(mm);

    memcpy(im.data, dest, im.rw * im.rh * sizeof(rgba));

    delete [] dest;
}

void sharpen(image &im, double radius, double sigma)
{
    if(!checkImage(im))
        return;

    double alpha, normalize, *kernel;
    int width;
    long i, u, v;
    rgba *dest = 0;

    if(sigma == 0.0)
        sigma = 0.01;

    width = getOptimalKernelWidth(radius, sigma);

    if(im.w < width)
        return;

    kernel = new double [width*width];

    if(!kernel)
        return;

    i = 0;
    normalize = 0.0;
    const double S = sigma * sigma;
    const int w2 = width / 2;

    for(v = -w2; v <= w2; v++)
    {
        for(u = -w2; u <= w2; u++)
        {
            alpha = exp(-((double) u*u+v*v)/(2.0*S));
            kernel[i] = alpha/(2.0*MagickPI*S);
            normalize += kernel[i];

            i++;
        }
    }

    kernel[i/2] = (-2.0)*normalize;

    if(!convolveImage(&im, &dest, width, kernel))
    {
        delete [] kernel;

        if(dest)
            delete [] dest;

        return;
    }

    delete [] kernel;

    memcpy(im.data, dest, im.rw * im.rh * sizeof(rgba));

    delete [] dest;
}

void oil(const image &im, double radius)
{
    if(!checkImage(im))
        return;

    unsigned long count;
    unsigned long histogram[256];
    unsigned int k;
    int width;
    int x, y, mx, my, sx, sy;
    int mcx, mcy;
    rgba *s = 0, *q;

    scaleDown(radius, 1.0, 5.0);

    rgba *n = new rgba [im.rw * im.rh];

    if(!n)
        return;

    memcpy(n, im.data, im.rw * im.rh * sizeof(rgba));

    width = getOptimalKernelWidth(radius, 0.5);

    if(im.w < width)
    {
        delete [] n;
        return;
    }

    rgba *bits = (rgba *)im.data;

    for(y = 0;y < im.h;++y)
    {
        sy = y-(width/2);
        q = n + im.rw*y;

        for(x = 0;x < im.w;++x)
        {
            count = 0;
            memset(histogram, 0, 256 * sizeof(unsigned long));
            sy = y-(width/2);

            for(mcy = 0;mcy < width;++mcy,++sy)
            {
                my = sy < 0 ? 0 : sy > im.h-1 ? im.h-1 : sy;
                sx = x+(-width/2);

                for(mcx = 0; mcx < width;++mcx,++sx)
                {
                    mx = sx < 0 ? 0 : sx > im.w-1 ? im.w-1 : sx;

                    k = intensityValue(*(bits + my*im.rw + mx));

                    if(k > 255) k = 255;

                    histogram[k]++;

                    if(histogram[k] > count)
                    {
                        count = histogram[k];
                        s = bits + my*im.rw + mx;
                    }
                }
            }

            *q++ = (*s);
        }
    }

    memcpy(im.data, n, im.rw * im.rh * sizeof(rgba));

    delete [] n;
}
/*
 * Red-eye removal was taken from "redeye" plugin for GIMP
 */

/* redeye.c: redeye remover plugin code
 *
 * Copyright (C) 2004  Robert Merkel <robert.merkel@benambra.org> (the "Author").
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of the Author of the
 * Software shall not be used in advertising or otherwise to promote the
 * sale, use or other dealings in this Software without prior written
 * authorization from the Author.
1;3B */

void redeye(const image &im, const int w, const int h, const int x, const int y, int th)
{
    const double RED_FACTOR = 0.5133333;
    const double GREEN_FACTOR = 1;
    const double BLUE_FACTOR = 0.1933333;

    if(!checkImage(im))
        return;

    scaleDown(th, 0, 255);

    int y1, x1;
    int adjusted_red, adjusted_green, adjusted_blue;

    rgba *src = (rgba *)im.data, *s;

    for(y1 = y;y1 < y+h;++y1)
    {
        s = src + im.w*y1 + x;

        for(x1 = x;x1 < x+w;x1++)
        {
            adjusted_red = int(s->r * RED_FACTOR);
            adjusted_green = int(s->g * GREEN_FACTOR);
            adjusted_blue = int(s->b * BLUE_FACTOR);

            if(adjusted_red >= adjusted_green - th && adjusted_red >= adjusted_blue - th)
                s->r = (int)(((double)(adjusted_green + adjusted_blue)) / (2.0  * RED_FACTOR));

            s++;
        }
    }
}


/*************************************************************************/

/*
 *
 *  Helper functions
 *
 */

/*************************************************************************/

static bool convolveImage(image *image, rgba **dest, const unsigned int order,
                                 const double *kernel)
{
    long width;
    double red, green, blue;
    u8 alpha;
    double normalize, *normal_kernel;
    const double *k;
    rgba *q;
    int x, y, mx, my, sx, sy;
    long i;
    int mcx, mcy;

    width = order;

    if((width % 2) == 0)
        return false;

    const int W = width*width;

    normal_kernel = new double [W];

    if(!normal_kernel)
        return false;

    *dest = new rgba [image->rw * image->rh];

    if(!*dest)
    {
        delete [] normal_kernel;
        return false;
    }

    normalize = 0.0;

    for(i = 0;i < W;i++)
        normalize += kernel[i];

    if(fabs(normalize) <= MagickEpsilon)
        normalize = 1.0;

    normalize=1.0/normalize;

    for(i = 0;i < W;i++)
        normal_kernel[i] = normalize*kernel[i];

    rgba *bits = (rgba *)image->data;

    for(y = 0;y < image->h;++y)
    {
        sy = y-(width/2);
        q = *dest + image->rw * y;

        for(x = 0;x < image->w;++x)
        {
            k = normal_kernel;
            red = green = blue = alpha = 0;
            sy = y-(width/2);
            alpha = (bits + image->rw*y+x)->a;

            for(mcy=0; mcy < width; ++mcy, ++sy)
            {
                my = sy < 0 ? 0 : sy > image->h-1 ? image->h-1 : sy;
                sx = x+(-width/2);

                for(mcx=0; mcx < width; ++mcx, ++sx)
                {
                    mx = sx < 0 ? 0 : sx > image->w-1 ? image->w-1 : sx;
                    red +=   (*k) * ((bits + image->rw*my+mx)->r*257);
                    green += (*k) * ((bits + image->rw*my+mx)->g*257);
                    blue +=  (*k) * ((bits + image->rw*my+mx)->b*257);
//                    alpha += (*k) * ((bits + image->rw*my+mx)->a*257);

                    ++k;
                }
            }

            red = red < 0 ? 0 : red > 65535 ? 65535 : red+0.5;
            green = green < 0 ? 0 : green > 65535 ? 65535 : green+0.5;
            blue = blue < 0 ? 0 : blue > 65535 ? 65535 : blue+0.5;
//            alpha = alpha < 0 ? 0 : alpha > 65535 ? 65535 : alpha+0.5;

            *q++ = rgba((unsigned char)(red/257UL),
                         (unsigned char)(green/257UL),
                         (unsigned char)(blue/257UL),
                         alpha);
        }
    }

    delete [] normal_kernel;

    return true;
}

static void rgb2hsv(const rgb &rgb, s32 *h, s32 *s, s32 *v)
{
    if(!h || !s || !v)
        return;

    s32 r = rgb.r;
    s32 g = rgb.g;
    s32 b = rgb.b;

    u32 max = r;
    s32 whatmax = 0;                // r=>0, g=>1, b=>2

    if((u32)g > max)
    {
        max = g;
        whatmax = 1;
    }

    if((u32)b > max)
    {
        max = b;
        whatmax = 2;
    }

    u32 min = r;               // find minimum value
    if((u32)g < min) min = g;
    if((u32)b < min) min = b;

    s32 delta = max-min;
    *v = max;                   // calc value
    *s = max ? (510*delta+max)/(2*max) : 0;

    if(*s == 0)
    {
        *h = -1;                // undefined hue
    }
    else
    {
        switch(whatmax)
        {
            case 0:             // red is max component
                if(g >= b)
                    *h = (120*(g-b)+delta)/(2*delta);
                else
                    *h = (120*(g-b+delta)+delta)/(2*delta) + 300;
            break;

            case 1:             // green is max component
                if(b > r)
                    *h = 120 + (120*(b-r)+delta)/(2*delta);
                else
                    *h = 60 + (120*(b-r+delta)+delta)/(2*delta);
            break;

            case 2:             // blue is max component
                if(r > g)
                    *h = 240 + (120*(r-g)+delta)/(2*delta);
                else
                    *h = 180 + (120*(r-g+delta)+delta)/(2*delta);
            break;
        }
    }
}

static void hsv2rgb(s32 h, s32 s, s32 v, rgb *rgb)
{
    if(h < -1 || (u32)s > 255 || (u32)v > 255 || !rgb)
        return;

    s32 r = v, g = v, b = v;

    if(s == 0 || h == -1)
    {
        // Ignore
    }
    else
    {                    // chromatic case
        if((u32)h >= 360)
            h %= 360;

        u32 f = h%60;
        h /= 60;
        u32 p = (u32)(2*v*(255-s)+255)/510;
        u32 q, t;

        if(h&1)
        {
            q = (u32)(2*v*(15300-s*f)+15300)/30600;

            switch(h)
            {
                case 1: r=(s32)q; g=(s32)v, b=(s32)p; break;
                case 3: r=(s32)p; g=(s32)q, b=(s32)v; break;
                case 5: r=(s32)v; g=(s32)p, b=(s32)q; break;
            }
        }
        else
        {
            t = (u32)(2*v*(15300-(s*(60-f)))+15300)/30600;

            switch(h)
            {
                case 0: r=(s32)v; g=(s32)t, b=(s32)p; break;
                case 2: r=(s32)p; g=(s32)v, b=(s32)t; break;
                case 4: r=(s32)t; g=(s32)p, b=(s32)v; break;
            }
        }
    }

    rgb->r = r;
    rgb->g = g;
    rgb->b = b;
}

static rgba interpolateColor(const image &im, double x_offset, double y_offset, const rgba &background)
{
    double alpha, beta;
    rgba p, q, r, s;
    s32 x, y;
    rgba *bits = (rgba *)im.data;

    if(!checkImage(im))
        return background;

    x = (s32)x_offset;
    y = (s32)y_offset;

    if((x < -1) || (x >= im.w) || (y < -1) || (y >= im.h))
        return background;

    if((x >= 0) && (y >= 0) && (x < (im.w-1)) && (y < (im.h-1)))
    {
        rgba *t = bits + y * im.rw;

        p = t[x];
        q = t[x+1];
        r = t[x+im.rw];
        s = t[x+im.rw+1];
    }
    else
    {
        rgba *t = bits + y * im.rw;

        p = background;

        if((x >= 0) && (y >= 0))
            p = t[x];

        q = background;

        if(((x+1) < im.w) && (y >= 0))
            q = t[x+1];

        r = background;

        if((x >= 0) && ((y+1) < im.h))
        {
            t = bits + (y+1) * im.rw;
            r = t[x+im.rw];
        }

        s = background;

        if(((x+1) < im.w) && ((y+1) < im.h))
        {
            t = bits + (y+1) * im.rw;
            s = t[x+im.rw+1];
        }
    }

    x_offset -= floor(x_offset);
    y_offset -= floor(y_offset);
    alpha = 1.0-x_offset;
    beta = 1.0-y_offset;

    rgba _r;

    _r.r = (u8)(beta * (alpha*p.r + x_offset*q.r) + y_offset * (alpha*r.r + x_offset*s.r));
    _r.g = (u8)(beta * (alpha*p.g + x_offset*q.g) + y_offset * (alpha*r.g + x_offset*s.g));
    _r.b = (u8)(beta * (alpha*p.b + x_offset*q.b) + y_offset * (alpha*r.b + x_offset*s.b));
    _r.a = (u8)(beta * (alpha*p.a + x_offset*q.a) + y_offset * (alpha*r.a + x_offset*s.a));

    return _r;
}

static u32 generateNoise(u32 pixel, NoiseType noise_type)
{
#define NoiseEpsilon  1.0e-5
#define NoiseMask  0x7fff
#define SigmaUniform  4.0
#define SigmaGaussian  4.0
#define SigmaImpulse  0.10
#define SigmaLaplacian 10.0
#define SigmaMultiplicativeGaussian  0.5
#define SigmaPoisson  0.05
#define TauGaussian  20.0

    double alpha, beta, sigma, value;
    alpha=(double) (rand() & NoiseMask)/NoiseMask;
    if (alpha == 0.0)
        alpha=1.0;
    switch(noise_type){
    case UniformNoise:
    default:
        {
            value=(double) pixel+SigmaUniform*(alpha-0.5);
            break;
        }
    case GaussianNoise:
        {
            double tau;

            beta=(double) (rand() & NoiseMask)/NoiseMask;
            sigma=sqrt(-2.0*log(alpha))*cos(2.0*M_PI*beta);
            tau=sqrt(-2.0*log(alpha))*sin(2.0*M_PI*beta);
            value=(double) pixel+
                (sqrt((double) pixel)*SigmaGaussian*sigma)+(TauGaussian*tau);
            break;
        }
    case MultiplicativeGaussianNoise:
        {
            if (alpha <= NoiseEpsilon)
                sigma=MaxRGB;
            else
                sigma=sqrt(-2.0*log(alpha));
            beta=(rand() & NoiseMask)/NoiseMask;
            value=(double) pixel+
                pixel*SigmaMultiplicativeGaussian*sigma*cos(2.0*M_PI*beta);
            break;
        }
    case ImpulseNoise:
        {
            if (alpha < (SigmaImpulse/2.0))
                value=0;
            else
                if (alpha >= (1.0-(SigmaImpulse/2.0)))
                    value=MaxRGB;
                else
                    value=pixel;
            break;
        }
    case LaplacianNoise:
        {
            if (alpha <= 0.5)
            {
                if (alpha <= NoiseEpsilon)
                    value=(double) pixel-MaxRGB;
                else
                    value=(double) pixel+SigmaLaplacian*log(2.0*alpha);
                break;
            }
            beta=1.0-alpha;
            if (beta <= (0.5*NoiseEpsilon))
                value=(double) pixel+MaxRGB;
            else
                value=(double) pixel-SigmaLaplacian*log(2.0*beta);
            break;
        }
    case PoissonNoise:
        {
            s32
                i;

            for (i=0; alpha > exp(-SigmaPoisson*pixel); i++)
            {
                beta=(double) (rand() & NoiseMask)/NoiseMask;
                alpha=alpha*beta;
            }
            value=i/SigmaPoisson;
            break;
        }
    }

    if(value < 0.0)
        return 0;
    else if(value > MaxRGB)
        return MaxRGB;
    else
        return ((u32) (value+0.5));
}

static inline u32 intensityValue(s32 r, s32 g, s32 b)
{
    return ((u32)((0.299*r + 0.587*g + 0.1140000000000001*b)));
}

static inline u32 intensityValue(const rgba &rr)
{
    return ((u32)((0.299*rr.r + 0.587*rr.g + 0.1140000000000001*rr.b)));
}

template<class T>
static inline void scaleDown(T &val, T min, T max)
{
    if(val < min)
        val = min;
    else if(val > max)
        val = max;
}

static void blurScanLine(double *kernel, s32 width, rgba *src, rgba *dest, s32 columns)
{
    double *p;
    rgba *q;
    s32 x;
    long i;
    double red, green, blue, alpha;
    double scale = 0.0;

    if(width > columns)
    {
        for(x = 0;x < columns;++x)
        {
            scale = 0.0;
            red = blue = green = alpha = 0.0;
            p = kernel;
            q = src;

            for(i = 0;i < columns;++i)
            {
                if((i >= (x-width/2)) && (i <= (x+width/2)))
                {
                    red += (*p)*(q->r * 257);
                    green += (*p)*(q->g*257);
                    blue += (*p)*(q->b*257);
                    alpha += (*p)*(q->a*257);
                }

                if(((i+width/2-x) >= 0) && ((i+width/2-x) < width))
                    scale += kernel[i+width/2-x];

                p++;
                q++;
            }

            scale = 1.0/scale;
            red = scale*(red+0.5);
            green = scale*(green+0.5);
            blue = scale*(blue+0.5);
            alpha = scale*(alpha+0.5);

            red = red < 0 ? 0 : red > 65535 ? 65535 : red;
            green = green < 0 ? 0 : green > 65535 ? 65535 : green;
            blue = blue < 0 ? 0 : blue > 65535 ? 65535 : blue;
            alpha = alpha < 0 ? 0 : alpha > 65535 ? 65535 : alpha;

            dest[x] = rgba((u8)(red/257UL),
                            (u8)(green/257UL),
                            (u8)(blue/257UL),
                            (u8)(alpha/257UL));
        }

        return;
    }

    for(x = 0;x < width/2;++x)
    {
        scale = 0.0;
        red = blue = green = alpha = 0.0;
        p = kernel+width/2-x;
        q = src;

        for(i = width/2-x;i < width;++i)
        {
            red += (*p)*(q->r*257);
            green += (*p)*(q->g*257);
            blue += (*p)*(q->b*257);
            alpha += (*p)*(q->a*257);
            scale += (*p);
            p++;
            q++;
        }

        scale=1.0/scale;

        red = scale*(red+0.5);
        green = scale*(green+0.5);
        blue = scale*(blue+0.5);
        alpha = scale*(alpha+0.5);

        red = red < 0 ? 0 : red > 65535 ? 65535 : red;
        green = green < 0 ? 0 : green > 65535 ? 65535 : green;
        blue = blue < 0 ? 0 : blue > 65535 ? 65535 : blue;
        alpha = alpha < 0 ? 0 : alpha > 65535 ? 65535 : alpha;

        dest[x] = rgba((u8)(red/257UL),
                        (u8)(green/257UL),
                        (u8)(blue/257UL),
                        (u8)(alpha/257UL));
    }

    for(;x < columns-width/2;++x)
    {
        red = blue = green = alpha = 0.0;
        p = kernel;
        q = src+(x-width/2);

        for(i = 0;i < (long)width;++i)
        {
            red += (*p)*(q->r*257);
            green += (*p)*(q->g*257);
            blue += (*p)*(q->b*257);
            alpha += (*p)*(q->a*257);
            p++;
            q++;
        }

        red = scale*(red+0.5);
        green = scale*(green+0.5);
        blue = scale*(blue+0.5);
        alpha = scale*(alpha+0.5);

        red = red < 0 ? 0 : red > 65535 ? 65535 : red;
        green = green < 0 ? 0 : green > 65535 ? 65535 : green;
        blue = blue < 0 ? 0 : blue > 65535 ? 65535 : blue;
        alpha = alpha < 0 ? 0 : alpha > 65535 ? 65535 : alpha;

        dest[x] = rgba((u8)(red/257UL),
                        (u8)(green/257UL),
                        (u8)(blue/257UL),
                        (u8)(alpha/257UL));
    }

    for(;x < columns;++x)
    {
        red = blue = green = alpha = 0.0;
        scale=0;
        p = kernel;
        q = src+(x-width/2);

        for(i = 0;i < columns-x+width/2;++i)
        {
            red += (*p)*(q->r*257);
            green += (*p)*(q->g*257);
            blue += (*p)*(q->b*257);
            alpha += (*p)*(q->a*257);
            scale += (*p);
            p++;
            q++;
        }

        scale=1.0/scale;
        red = scale*(red+0.5);
        green = scale*(green+0.5);
        blue = scale*(blue+0.5);
        alpha = scale*(alpha+0.5);

        red = red < 0 ? 0 : red > 65535 ? 65535 : red;
        green = green < 0 ? 0 : green > 65535 ? 65535 : green;
        blue = blue < 0 ? 0 : blue > 65535 ? 65535 : blue;
        alpha = alpha < 0 ? 0 : alpha > 65535 ? 65535 : alpha;

        dest[x] = rgba((u8)(red/257UL),
                        (u8)(green/257UL),
                        (u8)(blue/257UL),
                        (u8)(alpha/257UL));
    }
}

static s32 getBlurKernel(s32 width, double sigma, double **kernel)
{

#define KernelRank  3
#define KernelRankQ 18.0

    double alpha, normalize;
    long i;
    s32 bias;

    if(sigma == 0.0)
        return 0;

    if(width == 0)
        width = 3;

    *kernel = new double [width];

    if(!*kernel)
        return 0;

    memset(*kernel, 0, width * sizeof(double));
    bias = KernelRank * width/2;

    for(i = (-bias);i <= bias; i++)
    {
        alpha = exp(-((double) i*i)/(KernelRankQ*sigma*sigma));
        (*kernel)[(i+bias)/KernelRank] += alpha/(MagickSQ2PI*sigma);
    }

    normalize = 0;
 
    for(i = 0;i < width;i++)
        normalize += (*kernel)[i];

    for(i = 0;i < width;i++)
        (*kernel)[i] /= normalize;

    return width;

#undef KernelRankQ
#undef KernelRank

}

static void hull(const s32 x_offset, const s32 y_offset, const s32 polarity, const s32 columns,
                        const s32 rows, u8 *f, u8 *g)
{
    s32 x, y;

    u8 *p, *q, *r, *s;
    u32 v;

    if(f == 0 || g == 0)
        return;

    p = f+(columns+2);
    q = g+(columns+2);
    r = p+(y_offset*(columns+2)+x_offset);

    for(y = 0;y < rows;y++)
    {
        p++;
        q++;
        r++;
        if(polarity > 0)
            for(x = 0;x < columns;x++)
            {
                v=(*p);
                if (*r > v)
                    v++;
                *q=v > 255 ? 255 : v;
                p++;
                q++;
                r++;
            }
        else
            for(x = 0;x < columns;x++)
            {
                v=(*p);
                if (v > (u32) (*r+1))
                    v--;
                *q=v;
                p++;
                q++;
                r++;
            }
        p++;
        q++;
        r++;
    }

    p = f+(columns+2);
    q = g+(columns+2);
    r = q+(y_offset*(columns+2)+x_offset);
    s = q-(y_offset*(columns+2)+x_offset);

    for(y = 0;y < rows;y++)
    {
        p++;
        q++;
        r++;
        s++;

        if(polarity > 0)
            for(x=0; x < (s32) columns; x++)
            {
                v=(*q);
                if (((u32) (*s+1) > v) && (*r > v))
                    v++;
                *p=v > 255 ? 255 : v;
                p++;
                q++;
                r++;
                s++;
            }
        else
            for (x=0; x < columns; x++)
            {
                v=(*q);
                if (((u32) (*s+1) < v) && (*r < v))
                    v--;
                *p=v;
                p++;
                q++;
                r++;
                s++;
            }

        p++;
        q++;
        r++;
        s++;
    }
}

static int getOptimalKernelWidth(double radius, double sigma)
{
    double normalize, value;
    long width;
    long u;

    if(sigma == 0.0)
        sigma = 0.01;

    if(radius > 0.0)
        return((int)(2.0*ceil(radius)+1.0));

    const double S = sigma * sigma;

    for(width = 5;;)
    {
        normalize = 0.0;

        for(u = (-width/2);u <= (width/2);u++)
            normalize+=exp(-((double) u*u)/(2.0*S))/(MagickSQ2PI*sigma);

        u = width/2;
        value = exp(-((double) u*u)/(2.0*S))/(MagickSQ2PI*sigma)/normalize;

        if((long)(65535*value) <= 0)
            break;

        width+=2;
    }

    return ((int)width-2);
}

} // namespace
