]> git.saurik.com Git - wxWidgets.git/commitdiff
[ 1537065 ] wxImage: Higher quality scaling/sampling
authorRobert Roebling <robert@roebling.de>
Sun, 24 Sep 2006 12:47:16 +0000 (12:47 +0000)
committerRobert Roebling <robert@roebling.de>
Sun, 24 Sep 2006 12:47:16 +0000 (12:47 +0000)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@41412 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

docs/latex/wx/image.tex
include/wx/image.h
src/common/image.cpp

index 198750f4268994980680c680bf359c31d5148101..efcbd1119186c25e2c3d15773f4b588691f9b5ff 100644 (file)
@@ -243,6 +243,43 @@ returns true if the current image handlers can read this file
 
 \pythonnote{In wxPython this static method is named {\tt wxImage\_AddHandler}.}
 
 
 \pythonnote{In wxPython this static method is named {\tt wxImage\_AddHandler}.}
 
+
+\membersection{wxImage::Blur}\label{wximageblur}
+
+\func{wxImage}{Blur}{\param{int}{ blurRadius}}
+
+Blurs the image in both horizontal and vertical directions by the specified pixel {\it blurRadius}.
+
+\wxheading{See also}
+
+\helpref{BlurHorizontal}{wximagehorzblur}
+\helpref{BlurVertical}{wximagevertblur}
+
+
+\membersection{wxImage::BlurHorizontal}\label{wximagehorzblur}
+
+\func{wxImage}{BlurHorizontal}{\param{int}{ blurRadius}}
+
+Blurs the image in the horizontal direction only.
+
+\wxheading{See also}
+
+\helpref{Blur}{wximageblur}
+\helpref{BlurVertical}{wximagevertblur}
+
+
+\membersection{wxImage::BlurVertical}\label{wximagevertblur}
+
+\func{wxImage}{BlurVertical}{\param{int}{ blurRadius}}
+
+Blurs the image in the vertical direction only.
+
+\wxheading{See also}
+
+\helpref{Blur}{wximageblur}
+\helpref{BlurHorizontal}{wximagehorzblur}
+
+
 \membersection{wxImage::CleanUpHandlers}\label{wximagecleanuphandlers}
 
 \func{static void}{CleanUpHandlers}{\void}
 \membersection{wxImage::CleanUpHandlers}\label{wximagecleanuphandlers}
 
 \func{static void}{CleanUpHandlers}{\void}
@@ -855,7 +892,7 @@ Returns true if image data is present.
 
 \func{}{RGBValue}{\param{unsigned char }{r = 0}, \param{unsigned char }{g = 0}, \param{unsigned char }{b = 0}}
 
 
 \func{}{RGBValue}{\param{unsigned char }{r = 0}, \param{unsigned char }{g = 0}, \param{unsigned char }{b = 0}}
 
-Constructor for RGBValue, an object that contains values for red, green and blud which
+Constructor for RGBValue, an object that contains values for red, green and blue which
 represent the value of a color. It is used by \helpref{wxImage::HSVtoRGB}{wximagehsvtorgb}
 and \helpref{wxImage::RGBtoHSV}{wximagergbtohsv}, which
 converts between HSV color space and RGB color space.
 represent the value of a color. It is used by \helpref{wxImage::HSVtoRGB}{wximagehsvtorgb}
 and \helpref{wxImage::RGBtoHSV}{wximagergbtohsv}, which
 converts between HSV color space and RGB color space.
@@ -906,11 +943,13 @@ Replaces the colour specified by {\it r1,g1,b1} by the colour {\it r2,g2,b2}.
 
 \membersection{wxImage::Rescale}\label{wximagerescale}
 
 
 \membersection{wxImage::Rescale}\label{wximagerescale}
 
-\func{wxImage \&}{Rescale}{\param{int}{ width}, \param{int}{ height}}
+\func{wxImage \&}{Rescale}{\param{int}{ width}, \param{int}{ height}, \param{int}{ quality = wxIMAGE\_QUALITY\_NORMAL}}
 
 Changes the size of the image in-place by scaling it: after a call to this function,
 the image will have the given width and height.
 
 
 Changes the size of the image in-place by scaling it: after a call to this function,
 the image will have the given width and height.
 
+For a description of the {\it quality} parameter, see the \helpref{Scale}{wximagescale} function.
+
 Returns the (modified) image itself.
 
 \wxheading{See also}
 Returns the (modified) image itself.
 
 \wxheading{See also}
@@ -1050,7 +1089,7 @@ mimetype to the named file}
 
 \membersection{wxImage::Scale}\label{wximagescale}
 
 
 \membersection{wxImage::Scale}\label{wximagescale}
 
-\constfunc{wxImage}{Scale}{\param{int}{ width}, \param{int}{ height}}
+\constfunc{wxImage}{Scale}{\param{int}{ width}, \param{int}{ height}, \param{int}{ quality = wxIMAGE\_QUALITY\_NORMAL}}
 
 Returns a scaled version of the image. This is also useful for
 scaling bitmaps in general as the only other way to scale bitmaps
 
 Returns a scaled version of the image. This is also useful for
 scaling bitmaps in general as the only other way to scale bitmaps
@@ -1059,6 +1098,20 @@ is to blit a wxMemoryDC into another wxMemoryDC.
 It may be mentioned that the GTK port uses this function internally
 to scale bitmaps when using mapping modes in wxDC.
 
 It may be mentioned that the GTK port uses this function internally
 to scale bitmaps when using mapping modes in wxDC.
 
+\docparam{quality}{Determines what method to use for resampling the image.  Can be one of the following:
+
+\twocolwidtha{5cm}%
+\begin{twocollist}
+\twocolitem{{\bf wxIMAGE\_QUALITY\_NORMAL}}{Uses the normal default scaling method of pixel replication}
+\twocolitem{{\bf wxIMAGE\_QUALITY\_HIGH}}{Uses bicubic and box averaging resampling methods for upsampling and downsampling respectively}
+\end{twocollist}}
+
+It should be noted that although using wxIMAGE\_QUALITY\_HIGH produces much nicer
+looking results it is a slower method.  Downsampling will use the box averaging method
+which seems to operate very fast.  If you are upsampling larger images using
+this method you will most likely notice that it is a bit slower and in extreme cases
+it will be quite substantially slower as the bicubic algorithm has to process a lot of data.
+
 Example:
 
 \begin{verbatim}
 Example:
 
 \begin{verbatim}
index 5aa6984d2844fa3bd23d401f9c4d6aa6734f09ac..3e3e8d46f59033a9955691771a2af6db7d3e32ce 100644 (file)
@@ -43,6 +43,13 @@ enum
     wxIMAGE_RESOLUTION_CM = 2
 };
 
     wxIMAGE_RESOLUTION_CM = 2
 };
 
+// Constants for wxImage::Scale() for determining the level of quality
+enum
+{
+    wxIMAGE_QUALITY_NORMAL = 0,
+    wxIMAGE_QUALITY_HIGH = 1
+};
+
 // alpha channel values: fully transparent, default threshold separating
 // transparent pixels from opaque for a few functions dealing with alpha and
 // fully opaque
 // alpha channel values: fully transparent, default threshold separating
 // transparent pixels from opaque for a few functions dealing with alpha and
 // fully opaque
@@ -216,12 +223,21 @@ public:
     void Paste( const wxImage &image, int x, int y );
 
     // return the new image with size width*height
     void Paste( const wxImage &image, int x, int y );
 
     // return the new image with size width*height
-    wxImage Scale( int width, int height ) const;
+    wxImage Scale( int width, int height, int quality = wxIMAGE_QUALITY_NORMAL ) const;
+
+    // box averager and bicubic filters for up/down sampling
+    wxImage ResampleBox(int width, int height) const;
+    wxImage ResampleBicubic(int width, int height) const;
+
+    // blur the image according to the specified pixel radius
+    wxImage Blur(int radius);
+    wxImage BlurHorizontal(int radius);
+    wxImage BlurVertical(int radius);
 
     wxImage ShrinkBy( int xFactor , int yFactor ) const ;
 
     // rescales the image in place
 
     wxImage ShrinkBy( int xFactor , int yFactor ) const ;
 
     // rescales the image in place
-    wxImage& Rescale( int width, int height ) { return *this = Scale(width, height); }
+    wxImage& Rescale( int width, int height, int quality = wxIMAGE_QUALITY_NORMAL ) { return *this = Scale(width, height, quality); }
 
     // resizes the image in place
     wxImage& Resize( const wxSize& size, const wxPoint& pos,
 
     // resizes the image in place
     wxImage& Resize( const wxSize& size, const wxPoint& pos,
index f771aecf99231262a0e75a788d919d5324d61d3b..a4a74d45efe6c4ef2ab5a4036ed2bd8fa40ab9d6 100644 (file)
@@ -414,7 +414,7 @@ wxImage wxImage::ShrinkBy( int xFactor , int yFactor ) const
     return image;
 }
 
     return image;
 }
 
-wxImage wxImage::Scale( int width, int height ) const
+wxImage wxImage::Scale( int width, int height, int quality ) const
 {
     wxImage image;
 
 {
     wxImage image;
 
@@ -429,64 +429,86 @@ wxImage wxImage::Scale( int width, int height ) const
     wxCHECK_MSG( (old_height > 0) && (old_width > 0), image,
                  wxT("invalid old image size") );
 
     wxCHECK_MSG( (old_height > 0) && (old_width > 0), image,
                  wxT("invalid old image size") );
 
-    if ( old_width % width == 0 && old_width >= width &&
-        old_height % height == 0 && old_height >= height )
+    // If the image's new width and height are the same as the original, no need to waste time or CPU cycles
+    if(old_width == width && old_height == height)
+        return *this;
+
+    // Scale the image (...or more appropriately, resample the image) using either the high-quality or normal method as specified
+    if(quality == wxIMAGE_QUALITY_HIGH)
     {
     {
-        return ShrinkBy( old_width / width , old_height / height ) ;
+        // We need to check whether we are downsampling or upsampling the image
+        if(width < old_width && height < old_height)
+        {
+            // Downsample the image using the box averaging method for best results
+            image = ResampleBox(width, height);
+        }
+        else
+        {
+            // For upsampling or other random/wierd image dimensions we'll use a bicubic b-spline scaling method
+            image = ResampleBicubic(width, height);
+        }
     }
     }
-    image.Create( width, height, false );
+    else    // Default scaling method == simple pixel replication
+    {
+        if ( old_width % width == 0 && old_width >= width &&
+            old_height % height == 0 && old_height >= height )
+        {
+            return ShrinkBy( old_width / width , old_height / height ) ;
+        }
+        image.Create( width, height, false );
 
 
-    unsigned char *data = image.GetData();
+        unsigned char *data = image.GetData();
 
 
-    wxCHECK_MSG( data, image, wxT("unable to create image") );
+        wxCHECK_MSG( data, image, wxT("unable to create image") );
 
 
-    unsigned char *source_data = M_IMGDATA->m_data;
-    unsigned char *target_data = data;
-    unsigned char *source_alpha = 0 ;
-    unsigned char *target_alpha = 0 ;
+        unsigned char *source_data = M_IMGDATA->m_data;
+        unsigned char *target_data = data;
+        unsigned char *source_alpha = 0 ;
+        unsigned char *target_alpha = 0 ;
 
 
-    if (M_IMGDATA->m_hasMask)
-    {
-        image.SetMaskColour( M_IMGDATA->m_maskRed,
-                             M_IMGDATA->m_maskGreen,
-                             M_IMGDATA->m_maskBlue );
-    }
-    else
-    {
-        source_alpha = M_IMGDATA->m_alpha ;
-        if ( source_alpha )
+        if (M_IMGDATA->m_hasMask)
         {
         {
-            image.SetAlpha() ;
-            target_alpha = image.GetAlpha() ;
+            image.SetMaskColour( M_IMGDATA->m_maskRed,
+                                M_IMGDATA->m_maskGreen,
+                                M_IMGDATA->m_maskBlue );
+        }
+        else
+        {
+            source_alpha = M_IMGDATA->m_alpha ;
+            if ( source_alpha )
+            {
+                image.SetAlpha() ;
+                target_alpha = image.GetAlpha() ;
+            }
         }
         }
-    }
 
 
-    long x_delta = (old_width<<16) / width;
-    long y_delta = (old_height<<16) / height;
+        long x_delta = (old_width<<16) / width;
+        long y_delta = (old_height<<16) / height;
 
 
-    unsigned char* dest_pixel = target_data;
+        unsigned char* dest_pixel = target_data;
 
 
-    long y = 0;
-    for ( long j = 0; j < height; j++ )
+        long y = 0;
+        for ( long j = 0; j < height; j++ )
         {
         {
-        unsigned char* src_line = &source_data[(y>>16)*old_width*3];
-        unsigned char* src_alpha_line = source_alpha ? &source_alpha[(y>>16)*old_width] : 0 ;
+            unsigned char* src_line = &source_data[(y>>16)*old_width*3];
+            unsigned char* src_alpha_line = source_alpha ? &source_alpha[(y>>16)*old_width] : 0 ;
 
 
-        long x = 0;
-        for ( long i = 0; i < width; i++ )
-        {
-             unsigned char* src_pixel = &src_line[(x>>16)*3];
-             unsigned char* src_alpha_pixel = source_alpha ? &src_alpha_line[(x>>16)] : 0 ;
-             dest_pixel[0] = src_pixel[0];
-             dest_pixel[1] = src_pixel[1];
-             dest_pixel[2] = src_pixel[2];
-             dest_pixel += 3;
-             if ( source_alpha )
-                *(target_alpha++) = *src_alpha_pixel ;
-             x += x_delta;
-        }
+            long x = 0;
+            for ( long i = 0; i < width; i++ )
+            {
+                unsigned char* src_pixel = &src_line[(x>>16)*3];
+                unsigned char* src_alpha_pixel = source_alpha ? &src_alpha_line[(x>>16)] : 0 ;
+                dest_pixel[0] = src_pixel[0];
+                dest_pixel[1] = src_pixel[1];
+                dest_pixel[2] = src_pixel[2];
+                dest_pixel += 3;
+                if ( source_alpha )
+                    *(target_alpha++) = *src_alpha_pixel ;
+                x += x_delta;
+            }
 
 
-        y += y_delta;
+            y += y_delta;
+        }
     }
 
     // In case this is a cursor, make sure the hotspot is scaled accordingly:
     }
 
     // In case this is a cursor, make sure the hotspot is scaled accordingly:
@@ -500,6 +522,393 @@ wxImage wxImage::Scale( int width, int height ) const
     return image;
 }
 
     return image;
 }
 
+wxImage wxImage::ResampleBox(int width, int height) const
+{
+    // This function implements a simple pre-blur/box averaging method for downsampling that gives reasonably smooth results
+    // To scale the image down we will need to gather a grid of pixels of the size of the scale factor in each direction
+    // and then do an averaging of the pixels.
+
+    wxImage ret_image(width, height, false);
+
+    double scale_factor_x = double(M_IMGDATA->m_width) / width;
+    double scale_factor_y = double(M_IMGDATA->m_height) / height;
+
+    // If we want good-looking results we need to pre-blur the image a bit first
+    wxImage src_image(*this);
+    src_image = src_image.BlurHorizontal(scale_factor_x / 2);
+    src_image = src_image.BlurVertical(scale_factor_y / 2);
+
+    unsigned char* src_data = src_image.GetData();
+    unsigned char* src_alpha = src_image.GetAlpha();
+    unsigned char* dst_data = ret_image.GetData();
+    unsigned char* dst_alpha = NULL;
+
+    if(src_alpha)
+    {
+        ret_image.SetAlpha();
+        dst_alpha = ret_image.GetAlpha();
+    }
+
+    int x, y, i, j;
+    int averaged_pixels, src_pixel_index, src_x, src_y;
+    double sum_r, sum_g, sum_b, sum_a;
+
+    for(y = 0; y < height; y++)         // Destination image - Y direction
+    {
+        // Source pixel in the Y direction
+        src_y = y * scale_factor_y;
+
+        for(x = 0; x < width; x++)      // Destination image - X direction
+        {
+            // Source pixel in the X direction
+            src_x = x * scale_factor_x;
+
+            // Box of pixels to average
+            averaged_pixels = 0;
+            sum_r = sum_g = sum_b = sum_a = 0.0;
+
+            for(j = src_y - scale_factor_y / 2 + 1; j <= int(src_y + scale_factor_y / 2); j++)          // Y direction
+            {
+                // We don't care to average pixels that don't exist (edges)
+                if(j < 0 || j > M_IMGDATA->m_height)
+                    continue;
+
+                for(i = src_x - scale_factor_x / 2 + 1; i <= int(src_x + scale_factor_x / 2); i++)      // X direction
+                {
+                    // Don't average edge pixels
+                    if(i < 0 || i > M_IMGDATA->m_width)
+                        continue;
+
+                    // Calculate the actual index in our source pixels
+                    src_pixel_index = src_y * M_IMGDATA->m_width + src_x;
+
+                    sum_r += src_data[src_pixel_index * 3 + 0];
+                    sum_g += src_data[src_pixel_index * 3 + 1];
+                    sum_b += src_data[src_pixel_index * 3 + 2];
+                    if(src_alpha)
+                        sum_a += src_alpha[src_pixel_index];
+
+                    averaged_pixels++;
+                }
+            }
+
+            // Calculate the average from the sum and number of averaged pixels
+            dst_data[0] = int(sum_r / averaged_pixels);
+            dst_data[1] = int(sum_g / averaged_pixels);
+            dst_data[2] = int(sum_b / averaged_pixels);
+            dst_data += 3;
+            if(src_alpha)
+                *dst_alpha++ = sum_a / averaged_pixels;
+        }
+    }
+
+    return ret_image;
+}
+
+// The following two local functions are for the B-spline weighting of the bicubic sampling algorithm
+static inline double spline_cube(double value)
+{
+    return value <= 0.0 ? 0.0 : value * value * value;
+}
+
+static inline double spline_weight(double value)
+{
+    return (spline_cube(value + 2) - 4 * spline_cube(value + 1) + 6 * spline_cube(value) - 4 * spline_cube(value - 1)) / 6;
+}
+
+// This is the bicubic resampling algorithm
+wxImage wxImage::ResampleBicubic(int width, int height) const
+{
+    // This function implements a Bicubic B-Spline algorithm for resampling.  This method is certainly a little slower than wxImage's default
+    // pixel replication method, however for most reasonably sized images not being upsampled too much on a fairly average CPU this
+    // difference is hardly noticeable and the results are far more pleasing to look at.
+    //
+    // This particular bicubic algorithm does pixel weighting according to a B-Spline that basically implements a Gaussian bell-like
+    // weighting kernel. Because of this method the results may appear a bit blurry when upsampling by large factors.  This is basically
+    // because a slight gaussian blur is being performed to get the smooth look of the upsampled image.
+
+    // Edge pixels: 3-4 possible solutions
+    // - (Wrap/tile) Wrap the image, take the color value from the opposite side of the image.
+    // - (Mirror)    Duplicate edge pixels, so that pixel at coordinate (2, n), where n is nonpositive, will have the value of (2, 1).
+    // - (Ignore)    Simply ignore the edge pixels and apply the kernel only to pixels which do have all neighbours.
+    // - (Clamp)     Choose the nearest pixel along the border. This takes the border pixels and extends them out to infinity.
+    //
+    // NOTE: below the y_offset and x_offset variables are being set for edge pixels using the "Mirror" method mentioned above
+
+    wxImage ret_image;
+
+    ret_image.Create(width, height, false);
+
+    unsigned char* src_data = M_IMGDATA->m_data;
+    unsigned char* src_alpha = M_IMGDATA->m_alpha;
+    unsigned char* dst_data = ret_image.GetData();
+    unsigned char* dst_alpha = NULL;
+
+    if(src_alpha)
+    {
+        ret_image.SetAlpha();
+        dst_alpha = ret_image.GetAlpha();
+    }
+
+    int k, i;
+    double srcpixx, srcpixy, dx, dy;
+    int dstx, dsty;
+    double sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0;  // Sums for each color channel
+    int x_offset = 0, y_offset = 0;
+    double pixel_weight;
+    long src_pixel_index;
+
+    for(dsty = 0; dsty < height; dsty++)
+    {
+        // We need to calculate the source pixel to interpolate from - Y-axis
+        srcpixy = double(dsty) * M_IMGDATA->m_height / height;
+        dy = srcpixy - (int)srcpixy;
+
+        for(dstx = 0; dstx < width; dstx++)
+        {
+            // X-axis of pixel to interpolate from
+            srcpixx = double(dstx) * M_IMGDATA->m_width / width;
+            dx = srcpixx - (int)srcpixx;
+
+            // Clear all the RGBA sum values
+            sum_r = sum_g = sum_b = sum_a = 0;
+
+            // Here we actually determine the RGBA values for the destination pixel
+            for(k = -1; k <= 2; k++)
+            {
+                // Y offset
+                y_offset = srcpixy + double(k) < 0.0 ? 0 : (srcpixy + double(k) >= M_IMGDATA->m_height ? M_IMGDATA->m_height - 1 : srcpixy + k);
+
+                // Loop across the X axis
+                for(i = -1; i <= 2; i++)
+                {
+                    // X offset
+                    x_offset = srcpixx + double(i) < 0.0 ? 0 : (srcpixx + double(i) >= M_IMGDATA->m_width ? M_IMGDATA->m_width - 1 : srcpixx + i);
+
+                    // Calculate the exact position where the source data should be pulled from based on the x_offset and y_offset
+                    src_pixel_index = (y_offset * M_IMGDATA->m_width) + x_offset;
+
+                    // Calculate the weight for the specified pixel according to the bicubic b-spline kernel we're using for interpolation
+                    pixel_weight = spline_weight(double(i) - dx) * spline_weight(double(k) - dy);
+
+                    // Create a sum of all velues for each color channel adjusted for the pixel's calculated weight
+                    sum_r += double(src_data[src_pixel_index * 3 + 0]) * pixel_weight;
+                    sum_g += double(src_data[src_pixel_index * 3 + 1]) * pixel_weight;
+                    sum_b += double(src_data[src_pixel_index * 3 + 2]) * pixel_weight;
+                    if(src_alpha)
+                        sum_a += double(src_alpha[src_pixel_index]) * pixel_weight;
+                }
+            }
+
+            // Put the data into the destination image.  The summed values are of double data type and are rounded here for accuracy
+            dst_data[0] = int(sum_r + 0.5);
+            dst_data[1] = int(sum_g + 0.5);
+            dst_data[2] = int(sum_b + 0.5);
+            dst_data += 3;
+
+            if(src_alpha)
+                *dst_alpha++  = sum_a;
+        }
+    }
+
+    return ret_image;
+}
+
+// Blur in the horizontal direction
+wxImage wxImage::BlurHorizontal(int blurRadius)
+{
+    wxImage ret_image;
+    ret_image.Create(M_IMGDATA->m_width, M_IMGDATA->m_height, false);
+
+    unsigned char* src_data = M_IMGDATA->m_data;
+    unsigned char* dst_data = ret_image.GetData();
+    unsigned char* src_alpha = M_IMGDATA->m_alpha;
+    unsigned char* dst_alpha = NULL;
+
+    // Check for a mask or alpha
+    if(M_IMGDATA->m_hasMask)
+        ret_image.SetMaskColour(M_IMGDATA->m_maskRed, M_IMGDATA->m_maskGreen, M_IMGDATA->m_maskBlue);
+    else
+        if(src_alpha)
+        {
+            ret_image.SetAlpha();
+            dst_alpha = ret_image.GetAlpha();
+        }
+
+    // Variables used in the blurring algorithm
+    int x, y;
+    int kernel_x;
+    long sum_r, sum_g, sum_b, sum_a;
+    long pixel_idx;
+
+    // Horizontal blurring algorithm - average all pixels in the specified blur radius in the X or horizontal direction
+    for(y = 0; y < M_IMGDATA->m_height; y++)
+    {
+        sum_r = sum_g = sum_b = sum_a = 0;
+
+        // Calculate the average of all pixels in the blur radius for the first pixel of the row
+        for(kernel_x = -blurRadius; kernel_x <= blurRadius; kernel_x++)
+        {
+            // To deal with the pixels at the start of a row so it's not grabbing GOK values from memory at negative indices of the image's data or grabbing from the previous row
+            if(kernel_x < 0)
+                pixel_idx = y * M_IMGDATA->m_width;
+            else
+                pixel_idx = kernel_x + y * M_IMGDATA->m_width;
+
+            sum_r += src_data[pixel_idx * 3 + 0];
+            sum_g += src_data[pixel_idx * 3 + 1];
+            sum_b += src_data[pixel_idx * 3 + 2];
+            sum_a += src_alpha ? src_alpha[pixel_idx] : 0;
+        }
+        dst_data[y * M_IMGDATA->m_width * 3 + 0] = sum_r / (blurRadius * 2 + 1);
+        dst_data[y * M_IMGDATA->m_width * 3 + 1] = sum_g / (blurRadius * 2 + 1);
+        dst_data[y * M_IMGDATA->m_width * 3 + 2] = sum_b / (blurRadius * 2 + 1);
+        if(src_alpha)
+            dst_alpha[y * M_IMGDATA->m_width] = sum_a / (blurRadius * 2 + 1);
+
+        // Now average the values of the rest of the pixels by just moving the blur radius box along the row
+        for(x = 1; x < M_IMGDATA->m_width; x++)
+        {
+            // Take care of edge pixels on the left edge by essentially duplicating the edge pixel
+            if(x - blurRadius - 1 < 0)
+                pixel_idx = y * M_IMGDATA->m_width;
+            else
+                pixel_idx = (x - blurRadius - 1) + y * M_IMGDATA->m_width;
+
+            // Subtract the value of the pixel at the left side of the blur radius box
+            sum_r -= src_data[pixel_idx * 3 + 0];
+            sum_g -= src_data[pixel_idx * 3 + 1];
+            sum_b -= src_data[pixel_idx * 3 + 2];
+            sum_a -= src_alpha ? src_alpha[pixel_idx] : 0;
+
+            // Take care of edge pixels on the right edge
+            if(x + blurRadius > M_IMGDATA->m_width - 1)
+                pixel_idx = M_IMGDATA->m_width - 1 + y * M_IMGDATA->m_width;
+            else
+                pixel_idx = x + blurRadius + y * M_IMGDATA->m_width;
+
+            // Add the value of the pixel being added to the end of our box
+            sum_r += src_data[pixel_idx * 3 + 0];
+            sum_g += src_data[pixel_idx * 3 + 1];
+            sum_b += src_data[pixel_idx * 3 + 2];
+            sum_a += src_alpha ? src_alpha[pixel_idx] : 0;
+
+            // Save off the averaged data
+            dst_data[x * 3 + y * M_IMGDATA->m_width * 3 + 0] = sum_r / (blurRadius * 2 + 1);
+            dst_data[x * 3 + y * M_IMGDATA->m_width * 3 + 1] = sum_g / (blurRadius * 2 + 1);
+            dst_data[x * 3 + y * M_IMGDATA->m_width * 3 + 2] = sum_b / (blurRadius * 2 + 1);
+            if(src_alpha)
+                dst_alpha[x + y * M_IMGDATA->m_width] = sum_a / (blurRadius * 2 + 1);
+        }
+    }
+
+    return ret_image;
+}
+
+// Blur in the vertical direction
+wxImage wxImage::BlurVertical(int blurRadius)
+{
+    wxImage ret_image;
+    ret_image.Create(M_IMGDATA->m_width, M_IMGDATA->m_height, false);
+
+    unsigned char* src_data = M_IMGDATA->m_data;
+    unsigned char* dst_data = ret_image.GetData();
+    unsigned char* src_alpha = M_IMGDATA->m_alpha;
+    unsigned char* dst_alpha = NULL;
+
+    // Check for a mask or alpha
+    if(M_IMGDATA->m_hasMask)
+        ret_image.SetMaskColour(M_IMGDATA->m_maskRed, M_IMGDATA->m_maskGreen, M_IMGDATA->m_maskBlue);
+    else
+        if(src_alpha)
+        {
+            ret_image.SetAlpha();
+            dst_alpha = ret_image.GetAlpha();
+        }
+
+    // Variables used in the blurring algorithm
+    int x, y;
+    int kernel_y;
+    long sum_r, sum_g, sum_b, sum_a;
+    long pixel_idx;
+
+    // Vertical blurring algorithm - same as horizontal but switched the opposite direction
+    for(x = 0; x < M_IMGDATA->m_width; x++)
+    {
+        sum_r = sum_g = sum_b = sum_a = 0;
+
+        // Calculate the average of all pixels in our blur radius box for the first pixel of the column
+        for(kernel_y = -blurRadius; kernel_y <= blurRadius; kernel_y++)
+        {
+            // To deal with the pixels at the start of a column so it's not grabbing GOK values from memory at negative indices of the image's data or grabbing from the previous column
+            if(kernel_y < 0)
+                pixel_idx = x;
+            else
+                pixel_idx = x + kernel_y * M_IMGDATA->m_width;
+
+            sum_r += src_data[pixel_idx * 3 + 0];
+            sum_g += src_data[pixel_idx * 3 + 1];
+            sum_b += src_data[pixel_idx * 3 + 2];
+            sum_a += src_alpha ? src_alpha[pixel_idx] : 0;
+        }
+        dst_data[x * 3 + 0] = sum_r / (blurRadius * 2 + 1);
+        dst_data[x * 3 + 1] = sum_g / (blurRadius * 2 + 1);
+        dst_data[x * 3 + 2] = sum_b / (blurRadius * 2 + 1);
+        if(src_alpha)
+            dst_alpha[x] = sum_a / (blurRadius * 2 + 1);
+
+        // Now average the values of the rest of the pixels by just moving the box along the column from top to bottom
+        for(y = 1; y < M_IMGDATA->m_height; y++)
+        {
+            // Take care of pixels that would be beyond the top edge by duplicating the top edge pixel for the column
+            if(y - blurRadius - 1 < 0)
+                pixel_idx = x;
+            else
+                pixel_idx = x + (y - blurRadius - 1) * M_IMGDATA->m_width;
+
+            // Subtract the value of the pixel at the top of our blur radius box
+            sum_r -= src_data[pixel_idx * 3 + 0];
+            sum_g -= src_data[pixel_idx * 3 + 1];
+            sum_b -= src_data[pixel_idx * 3 + 2];
+            sum_a -= src_alpha ? src_alpha[pixel_idx] : 0;
+
+            // Take care of the pixels that would be beyond the bottom edge of the image similar to the top edge
+            if(y + blurRadius > M_IMGDATA->m_height - 1)
+                pixel_idx = x + (M_IMGDATA->m_height - 1) * M_IMGDATA->m_width;
+            else
+                pixel_idx = x + (blurRadius + y) * M_IMGDATA->m_width;
+
+            // Add the value of the pixel being added to the end of our box
+            sum_r += src_data[pixel_idx * 3 + 0];
+            sum_g += src_data[pixel_idx * 3 + 1];
+            sum_b += src_data[pixel_idx * 3 + 2];
+            sum_a += src_alpha ? src_alpha[pixel_idx] : 0;
+
+            // Save off the averaged data
+            dst_data[(x + y * M_IMGDATA->m_width) * 3 + 0] = sum_r / (blurRadius * 2 + 1);
+            dst_data[(x + y * M_IMGDATA->m_width) * 3 + 1] = sum_g / (blurRadius * 2 + 1);
+            dst_data[(x + y * M_IMGDATA->m_width) * 3 + 2] = sum_b / (blurRadius * 2 + 1);
+            if(src_alpha)
+                dst_alpha[x + y * M_IMGDATA->m_width] = sum_a / (blurRadius * 2 + 1);
+        }
+    }
+
+    return ret_image;
+}
+
+// The new blur function
+wxImage wxImage::Blur(int blurRadius)
+{
+    wxImage ret_image;
+    ret_image.Create(M_IMGDATA->m_width, M_IMGDATA->m_height, false);
+
+    // Blur the image in each direction
+    ret_image = BlurHorizontal(blurRadius);
+    ret_image = ret_image.BlurVertical(blurRadius);
+
+    return ret_image;
+}
+
 wxImage wxImage::Rotate90( bool clockwise ) const
 {
     wxImage image;
 wxImage wxImage::Rotate90( bool clockwise ) const
 {
     wxImage image;