I created a class called QuickBitmap to quickly get/set pixels in a Bitmap
object. Using intertops I'm able to safely lock/unlock the bits of the Bitmap
and with the use of methods like SaveBits
, LoadBits
, UpdateUnderlayingBitmap
, GetPixel
and SetPixel
. I can quickly and easily manipulate the image.
The problem:
One of my methods changes the bitdepth to 32 bits. If I use it I'm no longer able to quickly determine if the image I'm manipulating was, originally, composed of only black and white colors.
I created the following methods do it:
private IEnumerable<Color> AllPixels()
{
for (int i = 0; i < Width;i++ )
{
for(int j=0; j<Height; j++)
{
yield return GetPixel(i, j);
}
}
}
public bool IsBlackAndWhiteParallel()
{
return AllPixels().AsParallel().All(x => x.IsBlackOrWhite());
}
But as you can guess, it's quite a slow method. So I googled a little and found out that I can break a parallel.for loop. So I rewrote my method do this:
public bool IsBlackAndWhiteParallel()
{
bool ret = true;
Parallel.For(0, Width, (i, loopstate) =>
{
for(int j=0; j<Height; j++)
{
if(!GetPixel(i,j).IsBlackOrWhite())
{
ret = false;
loopstate.Stop();
}
}
});
return ret;
}
I benchmarked the code using the benchmark class (I found it in an old question on SO) with 100 iterations:
public static class Benchmarker { public static void Profile(string description, int iterations, Action func) { // warm up func(); Stopwatch watch = new Stopwatch(); // clean up GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); watch.Start(); for (int i = 0; i < iterations; i++) { func(); } watch.Stop(); Console.Write(description); Console.WriteLine(" average time: {0} ms", watch.Elapsed.TotalMilliseconds/iterations); } }
Results:
- First code: 1170 ms
- Second code: 99 ms
This is how the getpixe/setpixel methods are implemented for 32 bits image:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color GetPixel(int x, int y)
{
int pos;
int r, g, b, a;
pos = (y * stride) + (x * 4);
b = bytes[pos];
g = bytes[pos + 1];
r = bytes[pos + 2];
a = bytes[pos + 3];
return Color.FromArgb(a, r, g, b);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetPixel(int x, int y, Color c)
{
int pos;
pos = (y * stride) + (x * 4);
bytes[pos] = c.B;
bytes[pos + 1] = c.G;
bytes[pos + 2] = c.R;
bytes[pos + 3] = c.A;
}
I'm satisfied with the result, but I'd like to know if you have any suggestions for improvements.