Use the Bitmap24 class to manipulate image pixels very quickly in C#

manipulate image pixels

The Bitmap class’s GetPixel and SetPixel methods let you easily manipulate image pixels. They’re easy to use, but they’re also relatively slow.

This program inverts the pixels in an image several times. When you click the No Lock Bits button, the program uses the following code to loop through the pixels using GetPixel and SetPixel to invert the image.

private const int NUM_TRIALS = 10;

// Invert the image without Lockbits.
private void btnNoLockBits_Click(object sender, EventArgs e)
{
    Cursor = Cursors.WaitCursor;
    Stopwatch watch = new Stopwatch();
    watch.Start();

    Bitmap bm = new Bitmap(picHidden.Image);
    for (int trial = 0; trial < NUM_TRIALS; trial++)
    {
        for (int Y = 0; Y < bm.Height; Y++)
        {
            for (int X = 0; X < bm.Width; X++)
            {
                Color clr = bm.GetPixel(X, Y);
                clr = Color.FromArgb(
                    255 - clr.R,
                    255 - clr.G,
                    255 - clr.B);
                bm.SetPixel(X, Y, clr);
            }
        }
    }
    picVisible.Image = bm;
    watch.Stop();
    Cursor = Cursors.Default;

    lblElapsed.Text =
        watch.Elapsed.TotalSeconds.ToString("0.000000") +
        " seconds";
}

This code creates a Bitmap object that holds a copy of the image displayed in the picHidden PictureBox. It then creates and starts a Stopwatch. For NUM_TRIALS trials, the program loops over the image’s pixels. It uses the Bitmap object’s GetPixel method to get the pixel’s value. It inverts each of the pixel’s color components and uses SetPixel to save the new value in the Bitmap.

Finally the code displays the result and the elapsed time.

When you click the Lock Bits button, the program uses the following code to invert the image.

// Invert the image using Lockbits.
private void btnLockBits_Click(object sender, EventArgs e)
{
    const byte BYTE_255 = 255;
    Bitmap bm = new Bitmap(picHidden.Image);
    Cursor = Cursors.WaitCursor;
    Stopwatch watch = new Stopwatch();
    watch.Start();

    for (int trial = 0; trial < NUM_TRIALS; trial++)
    {
        // Make a Bitmap24 object.
        Bitmap24 bm24 = new Bitmap24(bm);

        // Lock the bitmap.
        bm24.LockBitmap();

        // Invert the pixels.
        for (int i = 0; i < bm.Height * bm24.RowSizeBytes; i++)
        {
            // bm24.ImageBytes[i] =
                Convert.ToByte(BYTE_255 - bm24.ImageBytes[i]);
            bm24.ImageBytes[i] =
                (byte)(BYTE_255 - bm24.ImageBytes[i]);
        }

        // Unlock the bitmap.
        bm24.UnlockBitmap();
    }
    picVisible.Image = bm;

    DateTime stop_time = DateTime.Now;
    Cursor = Cursors.Default;

    watch.Stop();
    lblElapsed.Text =
        watch.Elapsed.TotalSeconds.ToString("0.000000") +
        " seconds";
}

The key pieces of code are in the middle shown in blue. The program creates a Bitmap24 object (described shortly) and calls its LockBitmap method to lock the image in memory so it won’t move while the program manipulates it.

The code then loops through the image’s pixels using the object’s ImageBytes array to access the bytes in the image and inverts them. This array contains the red, green, and blue color components of the image’s pixel values. The bytes are all strung out in the array in a single long list. For example, the first three bytes give the blue, green, and red color components for the image’s first pixel.

After it has inverted all of the pixels, the code finishes by calling the Bitmap24 object’s UnlockBitmap method to unlock the bitmap.

The following code shows the Bitmap24 class. This class represents a bitmap with 24-bit pixels with 1 byte for each pixel’s red, green, and blue color component.

public class Bitmap24
{
    // Provide public access to the picture's byte data.
    public byte[] ImageBytes;
    public int RowSizeBytes;
    public const int PixelDataSize = 24;

    // A reference to the Bitmap.
    private Bitmap m_Bitmap;

    // Save a reference to the bitmap.
    public Bitmap24(Bitmap bm)
    {
        m_Bitmap = bm;
    }

    // Bitmap data.
    private BitmapData m_BitmapData;

    // Lock the bitmap's data.
    public void LockBitmap()
    {
        // Lock the bitmap data.
        Rectangle bounds = new Rectangle(
            0, 0, m_Bitmap.Width, m_Bitmap.Height);
        m_BitmapData = m_Bitmap.LockBits(bounds,
            ImageLockMode.ReadWrite,
            PixelFormat.Format24bppRgb);
        RowSizeBytes = m_BitmapData.Stride;

        // Allocate room for the data.
        int total_size = m_BitmapData.Stride * m_BitmapData.Height;
        ImageBytes = new byte[total_size];

        // Copy the data into the ImageBytes array.
        Marshal.Copy(m_BitmapData.Scan0, ImageBytes, 0, total_size);
    }

    // Copy the data back into the Bitmap
    // and release resources.
    public void UnlockBitmap()
    {
        // Copy the data back into the bitmap.
        int total_size = m_BitmapData.Stride * m_BitmapData.Height;
        Marshal.Copy(ImageBytes, 0, m_BitmapData.Scan0, total_size);

        // Unlock the bitmap.
        m_Bitmap.UnlockBits(m_BitmapData);

        // Release resources.
        ImageBytes = null;
        m_BitmapData = null;
    }
}

The class’s ImageBytes array provides direct access to the bitmap’s pixel data. RowSizeBytes tells you how many bytes are in a row. Note that this may include some padding bytes to align the data in memory, so some of the bytes at the end of each row may not actually be part of a pixel’s data.

The LockBitmap method calls the bitmap’s LockBits method to lock the object’s data. This prevents the data from moving while the program is manipulating it and is important before a program starts modifying the bitmap data. The program allocates an array big enough to hold the pixel data and then calls Marshal.Copy to copy the data from the bitmap into this array.

Subroutine UnlockBitmap calls Marshal.Copy to copy the data from the pixel array back into the Bitmap object. It then calls the object’s UnlockBits method.

Note: This program takes about 3.5 times as long to invert its image using GetPixel and SetPixel as it does using LockBits. I was surprised to find, however, that a comparable Visual Basic program was about 20 times faster using LockBits! Although C# is not generally faster than Visual Basic, it was surprising that it was this much slower in this example.

As far as I can tell, the reason for the difference is in loop that inverts the byte values, specifically this statement:

bm24.ImageBytes[i] = (byte)(BYTE_255 - bm24.ImageBytes[i]);

In C#, operations that involve byte values return an integer so this code needs to use (byte) to cast the result back into a byte. In Visual Basic, subtracting a byte value from another byte value results in a new byte value, so no conversion is needed. I think the big difference in time is caused by C# promoting the byte values to integers and then the code converting the result back into a byte.

If you discover a reasonable solution to this problem (without using unsafe methods to manipulate the byte array–that’s another issue), please let me know.


Download Example   Follow me on Twitter   RSS feed




This entry was posted in graphics, image processing and tagged , , , , , , , , , , , , , . Bookmark the permalink.

5 Responses to Use the Bitmap24 class to manipulate image pixels very quickly in C#

  1. Read More On this page

    BLOG.CSHARPHELPER.COM: Manipulate image pixels very quickly using LockBits wrapped in a class in C#

  2. Rajesh says:

    I have question that how do we change the color using your method live if a pixel has black color, How? then How do I convert that pixel to red color?

  3. Rod Stephens says:

    The class used by this example isn’t really set up to look at pixels easily. Take a look at this example:

    Manipulate 32-bit image pixels using a class with simple pixel get and set methods in C#

    It uses an enhanced version of this class modified to work with 32-bit color and to make getting and setting individual pixel values easier. You should be able to look at a pixel’s red, green, and blue components to see if it is black and if so change the components to make the pixel red.

  4. Pingback: Add GetPixel and SetPixel to the Bitmap24 class in C#

  5. Pingback: Use the Bitmap32 class to manipulate image pixels in C#

Leave a Reply

Your email address will not be published. Required fields are marked *