Click to See Complete Forum and Search --> : Fast Blurring Algorithms in Managed Code?


eshbach
01-04-2007, 04:23 PM
Someone on another forum suggested an image host (to be used for certain types of images where content may be... suggestive) in which the image is hosted in both a normal form and a blurred version which could be used as a link.

I thought this sounded like a funny idea, so I wrote a quick page to do it and hosted on an unused domain I have. The page is here: http://aaron.doomray.com/blurhost

The problem is that I don't know much about Image Processing and my blurring algorithm is far too slow. For a large picture with a high "blur amount", it can take over a minute for the page to return. Now, part of the problem is the site hosting, which is a shared server, so I'm not getting all the CPU time I could. More importantly, the host does not allow me to use compiled DLL's or unmanaged code. That means I can't use a library blur function, and I can't use pointers to access the pixels in the bitmap (this is the real problem).

Since I can't use pointers, accessing each pixel takes far too long, so I'm looking for a way to blur an image without having to loop through each pixel multiple times (taking averages of the colors).

Here's my current blur method:


public Bitmap Blur(Bitmap image, int amount)
{
Int32 horz = amount;
Int32 vert = amount;

Single weightsum;
Single[] weights;

Bitmap t = image;
weights = new Single[horz * 2 + 1];
for (Int32 i = 0; i < horz * 2 + 1; i++)
{
Single y = Gauss(-horz + i, 0, horz);
weights[i] = y;
}
for (Int32 row = 0; row < image.Height; row++)
{
for (Int32 col = 0; col < image.Width; col++)
{
Double r = 0;
Double g = 0;
Double b = 0;
Double a = 0;
weightsum = 0;
for (Int32 i = 0; i < horz * 2 + 1; i++)
{
Int32 x = col - horz + i;
if (x < 0)
{
i += -x;
x = 0;
}
if (x > image.Width - 1)
break;
Color c = image.GetPixel(x, row);
r += c.R * weights[i] / 255.0 * c.A;
g += c.G * weights[i] / 255.0 * c.A;
b += c.B * weights[i] / 255.0 * c.A;
a += c.A * weights[i];
weightsum += weights[i];
}
r /= weightsum;
g /= weightsum;
b /= weightsum;
a /= weightsum;
Byte br = (Byte)Math.Round(r);
Byte bg = (Byte)Math.Round(g);
Byte bb = (Byte)Math.Round(b);
Byte ba = (Byte)Math.Round(a);
if (br > 255) br = 255;
if (bg > 255) bg = 255;
if (bb > 255) bb = 255;
if (ba > 255) ba = 255;
t.SetPixel(col, row, Color.FromArgb(ba, br, bg, bb));
}
}
// vertical blur

weights = new Single[vert * 2 + 1];
for (Int32 i = 0; i < vert * 2 + 1; i++)
{
Single y = Gauss(-vert + i, 0, vert);
weights[i] = y;
}

for (Int32 col = 0; col < image.Width; col++)
{
for (Int32 row = 0; row < image.Height; row++)
{
Double r = 0;
Double g = 0;
Double b = 0;
Double a = 0;
weightsum = 0;
for (Int32 i = 0; i < vert * 2 + 1; i++)
{
Int32 y = row - vert + i;
if (y < 0)
{
i += -y;
y = 0;
}
if (y > image.Height - 1)
break;
Color c = t.GetPixel(col, y);
r += c.R * weights[i] / 255.0 * c.A;
g += c.G * weights[i] / 255.0 * c.A;
b += c.B * weights[i] / 255.0 * c.A;
a += c.A * weights[i];
weightsum += weights[i];
}
r /= weightsum;
g /= weightsum;
b /= weightsum;
a /= weightsum;
Byte br = (Byte)Math.Round(r);
Byte bg = (Byte)Math.Round(g);
Byte bb = (Byte)Math.Round(b);
Byte ba = (Byte)Math.Round(a);
if (br > 255) br = 255;
if (bg > 255) bg = 255;
if (bb > 255) bb = 255;
if (ba > 255) ba = 255;
image.SetPixel(col, row, Color.FromArgb(ba, br, bg, bb));
}
}
return image;
}




If anyone can think of a faster way to do this that will work in ASP.NET, please let me know. There might be some way to do it using GDI+, but I'm not sure.

Strogian
01-04-2007, 05:16 PM
GetPixel and SetPixel are always slow. There is a way you can get at raw data, but you might need to use unsafe code (so that you can use pointers). It also might only work for plain bitmaps, not for all image formats. But it looks like that's what you're doing, and I guess you can draw any image into a Bitmap.

Look at the documentation for Bitmap.LockBits().

There's also a page with tons of GDI+ examples in .NET, I forget what it's called though. I think there might be examples there of image processing in .NET, and he never uses GetPixel or SetPixel.

http://www.bobpowell.net/faqmain.htm

There, look at that.

eshbach
01-04-2007, 07:09 PM
after looking at the GDI stuff, most of it seems to rely on unmanaged code at some point. Since my web host won't allow unmanaged code, I don't think it's going to be an option :(

In the mean time, I've gone with a much simpler algorithm and dodged the slowness of getPixel by storing the pixel colors in a seperate array and then operating on that.

It's the same speed, maybe a tad slower, for blur level 1, but it's much much faster for anything higher than that. With a blur level of 100 it still returns in under 30 seconds even on fairly large images.

here's the new code, if you see any obvious improvements, let me know. I'm also considering trying the original algorithm with this sort of approach.


Color[][] pixels = new Color[image.Width][];
for (int i = 0; i < pixels.Length; ++i)
{
pixels[i] = new Color[image.Height];
for (int j = 0; j < pixels[i].Length; ++j)
{
pixels[i][j] = image.GetPixel(i, j);
}
}

int addR = 0, addG = 0, addB = 0;
for (int i = 0; i < amount; ++i)
{
for (int x = 1; x < pixels.Length - 1; ++x)
{
for (int y = 1; y < pixels[x].Length - 1; ++y)
{
addR = pixels[x][y - 1].R;
addR += pixels[x][y + 1].R;
addR += pixels[x][y].R;
addR += pixels[x + 1][y].R;
addR += pixels[x - 1][y].R;
addR = addR / 5;

addG = pixels[x][y - 1].G;
addG += pixels[x][y + 1].G;
addG += pixels[x][y].G;
addG += pixels[x + 1][y].G;
addG += pixels[x - 1][y].G;
addG = addG / 5;

addB = pixels[x][y - 1].B;
addB += pixels[x][y + 1].B;
addB += pixels[x][y].B;
addB += pixels[x + 1][y].B;
addB += pixels[x - 1][y].B;
addB = addB / 5;

//image.SetPixel(x, y, Color.FromArgb(addR, addG, addB));
pixels[x][y] = Color.FromArgb(addR, addG, addB);

addR = 0;
addG = 0;
addB = 0;
}
}
}

for (int i = 0; i < pixels.Length; ++i)
{
for (int j = 0; j < pixels[i].Length; ++j)
{
image.SetPixel(i, j, pixels[i][j]);
}
}

return image;

Handyman
01-04-2007, 07:37 PM
One interesting idea that was once discussed in class was to find a way to "Learn" what the basic color range of a human skin tone is (we were discussing how to reject potentially obscene images). Then you can only attack pixels that fall in the potential range. The less pixels to process the better.

In the end though, you are basically being hampered with the library you are using, the LockBits/UnSafe method must be using to get any speed. Does your host allow PHP? There is a pretty well built and documented image library that may or may not perform faster than your current C# approach.

rock
01-05-2007, 09:32 AM
Have a look at the this CodeProject page (http://www.codeproject.com/cs/media/csharpfilters.asp). I've used some code from this project before with pretty good results, though I haven't tried smoothing/blurring. But of course, it uses some unsafe code too, but might spawn other ideas.