Fast Blur (Box Blur with accumulator)
December 8, 2010 4 Comments
This post is similar to my previous post, but this post is about blurring an existing image, the previous post was about rendering colored rectangles.
For a game I’m creating (check out a beta at Flow) I needed a really fast way to repeatedly blur images (WriteableBitmap in this case) in Silverlight.
There are blur functions as image effects, and if that satisfies your needs then you should go with that. But I needed to generate my bitmap, blur it, update it, blur it and so on.
I’ve submitted the code below to the WriteableBitmapEx project as a contribution, but it hasn’t been accepted nor rejected yet. I’m publishing it here in case anyone needs it.
Box Blur is a very fast method for blurring (http://www.vcskicks.com/box-blur.php) but for it to be fast you must implement it as an accumulator, not a convolution loop.
Finding implementations of Box Blur, in C#, that uses convolution kernels (as in the link above) is trivial, finding one that uses an accumulator proved more difficult. The method below uses an accumulator.
The code for using the blur method looks like this;
WriteableBitmap bitmap = new WriteableBitmap((BitmapSource)Image.Source); bitmap.BoxBlur(13); Image.Source = bitmap; bitmap.Invalidate();
Before Box Blur;
After Box Blur (range=13);
Running BoxBlur on this 512×512 takes about 32 ms (averaged over 1000 runs) on my computer.
public static void BoxBlur(this WriteableBitmap bmp, int range)
{
if ((range & 1) == 0)
{
throw new InvalidOperationException("Range must be odd!");
}
bmp.BoxBlurHorizontal(range);
bmp.BoxBlurVertical(range);
}
public static void BoxBlurHorizontal(this WriteableBitmap bmp, int range)
{
int[] pixels = bmp.Pixels;
int w = bmp.PixelWidth;
int h = bmp.PixelHeight;
int halfRange = range / 2;
int index = 0;
int[] newColors = new int[w];
for (int y = 0; y < h; y++)
{
int hits = 0;
int r = 0;
int g = 0;
int b = 0;
for (int x = -halfRange; x < w; x++)
{
int oldPixel = x - halfRange - 1;
if (oldPixel >= 0)
{
int col = pixels[index + oldPixel];
if (col != 0)
{
r -= ((byte)(col >> 16));
g -= ((byte)(col >> 8 ));
b -= ((byte)col);
}
hits--;
}
int newPixel = x + halfRange;
if (newPixel < w)
{
int col = pixels[index + newPixel];
if (col != 0)
{
r += ((byte)(col >> 16));
g += ((byte)(col >> 8 ));
b += ((byte)col);
}
hits++;
}
if (x >= 0)
{
int color =
(255 << 24)
| ((byte)(r / hits) << 16)
| ((byte)(g / hits) << 8 )
| ((byte)(b / hits));
newColors[x] = color;
}
}
for (int x = 0; x < w; x++)
{
pixels[index + x] = newColors[x];
}
index += w;
}
}
public static void BoxBlurVertical(this WriteableBitmap bmp, int range)
{
int[] pixels = bmp.Pixels;
int w = bmp.PixelWidth;
int h = bmp.PixelHeight;
int halfRange = range / 2;
int[] newColors = new int[h];
int oldPixelOffset = -(halfRange + 1) * w;
int newPixelOffset = (halfRange) * w;
for (int x = 0; x < w; x++)
{
int hits = 0;
int r = 0;
int g = 0;
int b = 0;
int index = -halfRange * w + x;
for (int y = -halfRange; y < h; y++)
{
int oldPixel = y - halfRange - 1;
if (oldPixel >= 0)
{
int col = pixels[index + oldPixelOffset];
if (col != 0)
{
r -= ((byte)(col >> 16));
g -= ((byte)(col >> 8 ));
b -= ((byte)col);
}
hits--;
}
int newPixel = y + halfRange;
if (newPixel < h)
{
int col = pixels[index + newPixelOffset];
if (col != 0)
{
r += ((byte)(col >> 16));
g += ((byte)(col >> 8 ));
b += ((byte)col);
}
hits++;
}
if (y >= 0)
{
int color =
(255 << 24)
| ((byte)(r / hits) << 16)
| ((byte)(g / hits) << 8 )
| ((byte)(b / hits));
newColors[y] = color;
}
index += w;
}
for (int y = 0; y < h; y++)
{
pixels[y * w + x] = newColors[y];
}
}
}


