Fast Blur (Box Blur with accumulator)

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];
		}
	}
}
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]; } } }

About mfagerlund
Writes code in my sleep - and sometimes it even compiles!

7 Responses to Fast Blur (Box Blur with accumulator)

  1. xusan says:

    Hi MFAGERLUND thanks great job , any chance to port it to WPF ?

    Regards…

    • Charlie Hess says:

      To port to WPF, write two more extension methods, for Get and Set pixels:

      private static int[] GetPixels(this WriteableBitmap bmp)
      {
      int[] pixels = new int[bmp.PixelHeight * bmp.PixelWidth * bmp.Format.BitsPerPixel / 8];

      bmp.CopyPixels(new Int32Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight),
      pixels, bmp.PixelWidth * bmp.Format.BitsPerPixel / 8, 0);

      return pixels;
      }

      private static void SetPixels(this WriteableBitmap bmp, int[] pixels)
      {
      bmp.WritePixels(new Int32Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight),
      pixels, bmp.PixelWidth * bmp.Format.BitsPerPixel / 8, 0);
      }

  2. Maven says:

    Hi,
    Where is the .Pixels came from?, I couldnt find it at WriteableBitmapEx.

    Thanks.

  3. Martin says:

    perhaps it should be noted that this code doesn’t blur the alpha component (although that is very easy to add)..

  4. Chuan Che Hu says:

    Hi,
    Thanks for sharing the code. It helps me a lot.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: