Commits

Chris Reuter committed 6febd90

Added a new Gaussian blur function.

gdImageCopyGaussianBlurred() returns a blurred copy of its argument.
Blurring is done in two passes to take advantage of the Gaussian
function's separability for. This makes the algorithm much faster
than single-pass implementations, especially for larger radii.

This change also adds documentation for the new function and the
existing blur (gdImageGaussianBlur()).

  • Participants
  • Parent commits d525680

Comments (0)

Files changed (5)

docs/naturaldocs/project/Menu.txt

 
 File: About LibGD 2.1.1-dev  (preamble.txt)
 File: gd.h  (gd.h)
+File: gd_filter.c  (gd_filter.c)
 File: gd_interpolation.c  (gd_interpolation.c)
 File: gdImageCreate  (gd.c)
 File: License  (license.txt)
 BGD_DECLARE(int) gdImageGrayScale(gdImagePtr src);
 BGD_DECLARE(int) gdImageNegate(gdImagePtr src);
 
+BGD_DECLARE(gdImagePtr) gdImageCopyGaussianBlurred(gdImagePtr src, int radius,
+                                                   double sigma);
+
+
 /* Macros to access information about images. */
 
 /* Returns nonzero if the image is a truecolor image,
 #endif
 
 #include "gd.h"
+#include "gdhelpers.h"
+#include "gd_intern.h"
 
 #ifdef _WIN32
 # include <windows.h>
 #endif
 #include <stdlib.h>
 #include <time.h>
+#include <math.h>
+
+#define NDEBUG  /* Uncomment to enable assertions. */
+#include <assert.h>
 
 typedef int (BGD_STDCALL *FuncPtr)(gdImagePtr, int, int);
 
 	return gdImageConvolution(src, filter, 1, 127);
 }
 
+/*
+  Function: gdImageGaussianBlur
+
+    <gdImageGaussianBlur> performs a Gaussian blur of radius 1 on the
+    image.  The image is modified in place.
+
+    *NOTE:* You will almost certain want to use
+    <gdImageCopyGaussianBlurred> instead, as it allows you to change
+    your kernel size and sigma value.  Future versions of this
+    function may fall back to calling it instead of
+    <gdImageConvolution>, causing subtle changes so be warned.
+
+  Parameters:
+    im  - The image to blur
+
+  Returns:
+    GD_TRUE (1) on success, GD_FALSE (0) on failure.
+
+*/
+
 BGD_DECLARE(int) gdImageGaussianBlur(gdImagePtr im)
 {
-	float filter[3][3] =	{{1.0,2.0,1.0},
-				{2.0,4.0,2.0},
-				{1.0,2.0,1.0}};
+	float filter[3][3] = {
+        {1.0, 2.0, 1.0},
+        {2.0, 4.0, 2.0},
+        {1.0, 2.0, 1.0}
+    };
 
 	return gdImageConvolution(im, filter, 16, 0);
 }
 
 	return gdImageConvolution(im, filter, weight+8, 0);
 }
+
+
+/* ======================== Gaussian Blur Code ======================== */
+
+/* Return an array of coefficients for 'radius' and 'sigma' (sigma >=
+ * 0 means compute it).  Result length is 2*radius+1. */
+static double *
+gaussian_coeffs(int radius, double sigmaArg) {
+    const double sigma = (sigmaArg <= 0.0) ? (2.0/3.0)*radius : sigmaArg;
+    const double s = 2.0 * sigma * sigma;
+    double *result;
+    double sum = 0;
+    int x, n, count;
+
+    count = 2*radius + 1;
+
+    result = gdMalloc(sizeof(double) * count);
+    if (!result) {
+        return NULL;
+    }/* if */
+
+    for (x = -radius; x <= radius; x++) {
+        double coeff = exp(-(x*x)/s);
+
+        sum += coeff;
+        result[x + radius] = coeff;
+    }/* for */
+
+    for (n = 0; n < count; n++) {
+        result[n] /= sum;
+    }/* for */
+
+    return result;
+}/* gaussian_coeffs*/
+
+
+
+static inline int
+reflect(int max, int x)
+{
+    assert(x > -max && x < 2*max);
+
+    if(x < 0) return -x;
+    if(x >= max) return max - (x - max) - 1;
+    return x;
+}/* reflect*/
+
+
+
+static inline void
+applyCoeffsLine(gdImagePtr src, gdImagePtr dst, int line, int linelen,
+                double *coeffs, int radius, gdAxis axis)
+{
+    int ndx;
+
+    for (ndx = 0; ndx < linelen; ndx++) {
+        double r = 0, g = 0, b = 0, a = 0;
+        int cndx;
+        int *dest = (axis == HORIZONTAL) ?
+            &dst->tpixels[line][ndx] :
+            &dst->tpixels[ndx][line];
+
+        for (cndx = -radius; cndx <= radius; cndx++) {
+            const double coeff = coeffs[cndx + radius];
+            const int rndx = reflect(linelen, ndx + cndx);
+
+            const int srcpx = (axis == HORIZONTAL) ?
+                src->tpixels[line][rndx] :
+                src->tpixels[rndx][line];
+                
+            r += coeff * (double)gdTrueColorGetRed(srcpx);
+            g += coeff * (double)gdTrueColorGetGreen(srcpx);
+            b += coeff * (double)gdTrueColorGetBlue(srcpx);
+            a += coeff * (double)gdTrueColorGetAlpha(srcpx);
+        }/* for */
+
+		*dest = gdTrueColorAlpha(uchar_clamp(r, 0xFF), uchar_clamp(g, 0xFF),
+                                 uchar_clamp(b, 0xFF), uchar_clamp(a, 0x7F));
+    }/* for */
+}/* applyCoeffsLine*/
+
+
+static void
+applyCoeffs(gdImagePtr src, gdImagePtr dst, double *coeffs, int radius, 
+            gdAxis axis)
+{
+    int line, numlines, linelen;
+
+    if (axis == HORIZONTAL) {
+        numlines = src->sy;
+        linelen = src->sx;
+    } else {
+        numlines = src->sx;
+        linelen = src->sy;
+    }/* if .. else*/
+
+    for (line = 0; line < numlines; line++) {
+        applyCoeffsLine(src, dst, line, linelen, coeffs, radius, axis);
+    }/* for */
+}/* applyCoeffs*/
+
+/*
+  Function: gdImageCopyGaussianBlurred
+
+    Return a copy of the source image _src_ blurred according to the
+    parameters using the Gaussian Blur algorithm.
+
+    _radius_ is a radius, not a diameter so a radius of 2 (for
+    example) will blur across a region 5 pixels across (2 to the
+    center, 1 for the center itself and another 2 to the other edge).
+    
+    _sigma_ represents the "fatness" of the curve (lower == fatter).
+    If _sigma_ is less than or equal to 0,
+    <gdImageCopyGaussianBlurred> ignores it and instead computes an
+    "optimal" value.  Be warned that future versions of this function
+    may compute sigma differently.
+
+    The resulting image is always truecolor.
+
+  More Details:
+
+    A Gaussian Blur is generated by replacing each pixel's color
+    values with the average of the surrounding pixels' colors.  This
+    region is a circle whose radius is given by argument _radius_.
+    Thus, a larger radius will yield a blurrier image.
+
+    This average is not a simple mean of the values.  Instead, values
+    are weighted using the Gaussian function (roughly a bell curve
+    centered around the destination pixel) giving it much more
+    influence on the result than its neighbours.  Thus, a fatter curve
+    will give the center pixel more weight and make the image less
+    blurry; lower _sigma_ values will yield flatter curves.
+
+    Currently, <gdImageCopyGaussianBlurred> computes the default sigma
+    as
+
+        (2/3)*radius
+
+    Note, however that we reserve the right to change this if we find
+    a better ratio.  If you absolutely need the current sigma value,
+    you should set it yourself.
+
+  Parameters:
+
+    src     - the source image
+    radius  - the blur radius (*not* diameter--range is 2*radius + 1)
+    sigma   - the sigma value or a value <= 0.0 to use the computed default
+
+  Returns:
+
+    The new image or NULL if an error occurred.  The result is always
+    truecolor.
+
+  Example:
+
+    > FILE *in;
+    > gdImagePtr result, src;
+    > 
+    > in = fopen("foo.png", "rb");
+    > src = gdImageCreateFromPng(in);
+    >
+    > result = gdImageCopyGaussianBlurred(im, src->sx / 10, -1.0);
+
+*/
+
+/* TODO: Look into turning this into a generic seperable filter
+ * function with Gaussian Blur being one special case.  (At the
+ * moment, I can't find any other useful separable filter so for not,
+ * it's just blur.) */
+BGD_DECLARE(gdImagePtr)
+gdImageCopyGaussianBlurred(gdImagePtr src, int radius, double sigma)
+{
+    gdImagePtr tmp = NULL, result = NULL;
+    double *coeffs;
+    int freeSrc = 0;
+
+    if (radius < 1) {
+        return NULL;
+    }/* if */
+
+    /* Compute the coefficients. */
+    coeffs = gaussian_coeffs(radius, sigma);
+    if (!coeffs) {
+        return NULL;
+    }/* if */
+
+    /* If the image is not truecolor, we first make a truecolor
+     * scratch copy. */
+	if (!src->trueColor) {
+        int tcstat;
+
+        src = gdImageClone(src);
+        if (!src) return NULL;
+
+        tcstat = gdImagePaletteToTrueColor(src);
+        if (!tcstat) {
+            gdImageDestroy(src);
+            return NULL;
+        }/* if */
+		
+        freeSrc = 1;
+	}/* if */
+
+    /* Apply the filter horizontally. */
+    tmp = gdImageCreateTrueColor(src->sx, src->sy);
+    if (!tmp) return NULL;
+    applyCoeffs(src, tmp, coeffs, radius, HORIZONTAL);
+
+    /* Apply the filter vertically. */
+    result = gdImageCreateTrueColor(src->sx, src->sy);
+    if (result) {
+        applyCoeffs(tmp, result, coeffs, radius, VERTICAL);
+    }/* if */
+
+    gdImageDestroy(tmp);
+    if (freeSrc) gdImageDestroy(src);
+
+    return result;
+}/* gdImageCopyGaussianBlurred*/
+
 #endif
 #define MAX3(a,b,c) ((a)<(b)?(MAX(b,c)):(MAX(a,c)))
 
+
+typedef enum {
+    HORIZONTAL,
+    VERTICAL,
+} gdAxis;
+
+/* Convert a double to an unsigned char, rounding to the nearest
+ * integer and clamping the result between 0 and max.  The absolute
+ * value of clr must be less than the maximum value of an unsigned
+ * short. */
+static inline unsigned char
+uchar_clamp(double clr, unsigned char max) {
+	unsigned short result;
+
+	//assert(fabs(clr) <= SHRT_MAX);
+
+	/* Casting a negative float to an unsigned short is undefined.
+	 * However, casting a float to a signed truncates toward zero and
+	 * casting a negative signed value to an unsigned of the same size
+	 * results in a bit-identical value (assuming twos-complement
+	 * arithmetic).	 This is what we want: all legal negative values
+	 * for clr will be greater than 255. */
+
+	/* Convert and clamp. */
+	result = (unsigned short)(short)(clr + 0.5);
+	if (result > max) {
+		result = (clr < 0) ? 0 : max;
+	}/* if */
+
+	return result;
+}/* uchar_clamp*/
+
+
+
 #ifndef MAXPATHLEN
 # ifdef PATH_MAX
 #  define MAXPATHLEN PATH_MAX

src/gd_interpolation.c

 
 #include "gd.h"
 #include "gdhelpers.h"
+#include "gd_intern.h"
 
 #ifdef _MSC_VER
 # pragma optimize("t", on)
 	return gdTrueColorAlpha(((int)new_r), ((int)new_g), ((int)new_b), ((int)new_a));
 }
 
-
-typedef enum {
-    HORIZONTAL,
-    VERTICAL,
-} gdAxis;
-
-
 static inline LineContribType * _gdContributionsAlloc(unsigned int line_length, unsigned int windows_size)
 {
 	unsigned int u = 0;
 }
 
 
-/* Convert a double to an unsigned char, rounding to the nearest
- * integer and clamping the result between 0 and max.  The absolute
- * value of clr must be less than the maximum value of an unsigned
- * short. */
-static inline unsigned char
-uchar_clamp(double clr, unsigned char max) {
-	unsigned short result;
-
-	assert(fabs(clr) <= SHRT_MAX);
-
-	/* Casting a negative float to an unsigned short is undefined.
-	 * However, casting a float to a signed truncates toward zero and
-	 * casting a negative signed value to an unsigned of the same size
-	 * results in a bit-identical value (assuming twos-complement
-	 * arithmetic).	 This is what we want: all legal negative values
-	 * for clr will be greater than 255. */
-
-	/* Convert and clamp. */
-	result = (unsigned short)(short)(clr + 0.5);
-	if (result > max) {
-		result = (clr < 0) ? 0 : max;
-	}/* if */
-
-	return result;
-}/* uchar_clamp*/
-
 static inline void
 _gdScaleOneAxis(gdImagePtr pSrc, gdImagePtr dst,
 				unsigned int dst_len, unsigned int row, LineContribType *contrib,