summaryrefslogtreecommitdiffstats
path: root/src/fmt_filters.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/fmt_filters.cpp')
-rw-r--r--src/fmt_filters.cpp2288
1 files changed, 2288 insertions, 0 deletions
diff --git a/src/fmt_filters.cpp b/src/fmt_filters.cpp
new file mode 100644
index 0000000..9f6222c
--- /dev/null
+++ b/src/fmt_filters.cpp
@@ -0,0 +1,2288 @@
+/*
+ * 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