Welcome to CSharp Labs

Fast Bitmap Pixel Processing Using BitmapPixels

Wednesday, May 22, 2013

Processing an image's pixels can appear to be a simple undertaking using the .NET Framework's Bitmap.GetPixel and Bitmap.SetPixel methods. However, both methods are notoriously slow and ineffective for processing even small images.

To combat these inefficiencies, I have created the BitmapPixels class which accesses a Bitmap's underlying pixel data in an unsafe context and projects them into an RGBAColor structure. Please note that using BitmapPixels requires building the project with the /unsafe compiler option. (How to allow unsafe code.)

The RGBAColor structure is immutable and defined with an explicit layout:

[StructLayoutAttribute(LayoutKind.Explicit)]
public struct RGBAColor : IComparable, IComparable<RGBAColor>, IEquatable<RGBAColor>
{
    /// <summary>
    /// Defines the Blue component value.
    /// </summary>
    [FieldOffset(0)]
    public readonly byte B;

    /// <summary>
    /// Defines the Green component value.
    /// </summary>
    [FieldOffset(1)]
    public readonly byte G;

    /// <summary>
    /// Defines the Red component value.
    /// </summary>
    [FieldOffset(2)]
    public readonly byte R;

    /// <summary>
    /// Defines the Alpha component.
    /// </summary>
    [FieldOffset(3)]
    public readonly byte A;

    /// <summary>
    /// Defines the 4 color components as an integer.
    /// </summary>
    [FieldOffset(0)]
    public readonly int RGBA;

    ...
}

BitmapPixels can be used by initializing the class with a Bitmap and locking the Bitmap into memory by calling BitmapPixels.Lock to allow access to BitmapPixels.Colors (RGBAColor two dimensional array) or BitmapPixels[,] (single RGBAColor value):

            using (BitmapPixels pixels = new BitmapPixels(bmp)) //initialize BitmapPixels with a Bitmap
            {
                pixels.Lock(); //lock Bitmap in memory

                //access colors by x,y coordinates:
                for (int y = 0; y < pixels.Height; y++)
                {
                    for (int x = 0; x < pixels.Width; x++)
                    {
                        //pixels[x, y] //accesses color from memory
                    }
                }

                //access colors using an array:
                RGBAColor[,] colors = pixels.Colors; //copies all the pixels to an array
                for (int y = 0; y < pixels.Height; y++)
                {
                    for (int x = 0; x < pixels.Width; x++)
                    {
                        //colors[x, y] //accesses color from the local array
                    }
                }

                //pixels.Unlock(); //optional, automatically called when BitmapPixels is disposed
            }
Comparison

I created a 1000x1000 pixel bitmap and using the System.Diagnostics.Stopwatch class, profiled three different ways to access pixel data for comparison. Here are the timing results:

  • Bitmap.GetPixel : 1074 milliseconds
  • BitmapPixels.Colors : 52 milliseconds
  • BitmapPixels[,] : 35 milliseconds

Above result is an average of 50 runs, BitmapPixels timings include initialization, locking / unlocking and disposing. Using the BitmapPixels class produces performance gains of up to 30 times the .NET Frameworks Bitmap.GetPixel method.

Usage

BitmapPixels also provides access to the underlying pixel pointer through the BitmapPixels.Pointer property. This can be used to enumerate pixels in an entirely unsafe context. Here is an example which converts the image's pixels to grayscale (using Rec. 709 luma R,G,B coefficients 0.2126, 0.7152, and 0.0722):

            using (BitmapPixels pixels = new BitmapPixels(bmp)) //initialize BitmapPixels with a Bitmap
            {
                pixels.Lock(); //lock Bitmap in memory

                unsafe
                {
                    RGBAColor* color_ps = pixels.Pointer;

                    byte new_pixel;
                    for (int pixel = 0; pixel < pixels.Height * pixels.Width; pixel++, color_ps++)
                    {
                        new_pixel = (byte)(color_ps->R * 0.2126 + color_ps->G * 0.7152 + color_ps->B * 0.0722);
                        *color_ps = new RGBAColor(new_pixel, new_pixel, new_pixel, color_ps->A); //set the new pixel using the existing alpha component
                    }
                }

                //pixels.Unlock(); //optional, automatically called when BitmapPixels is disposed
            }

Download BitmapPixels and RGBAColor

Comments