Click here to Skip to main content
Click here to Skip to main content

Work with bitmaps faster in C#

By , 15 Aug 2011
 

When you are working with bitmaps in C#, you can use the GetPixel(x, y) and SetPixel(x, y, color) functions to get/set the pixel value. But they are very slow.

Here is the alternative way to work with bitmaps faster.

LockBitmap

With the LockBitmap class, we can lock/unlock bitmap data.

public class LockBitmap
{
    Bitmap source = null;
    IntPtr Iptr = IntPtr.Zero;
    BitmapData bitmapData = null;
 
    public byte[] Pixels { get; set; }
    public int Depth { get; private set; }
    public int Width { get; private set; }
    public int Height { get; private set; }
 
    public LockBitmap(Bitmap source)
    {
        this.source = source;
    }
 
    /// <summary>
    /// Lock bitmap data
    /// </summary>
    public void LockBits()
    {
        try
        {
            // Get width and height of bitmap
            Width = source.Width;
            Height = source.Height;
 
            // get total locked pixels count
            int PixelCount = Width * Height;
 
            // Create rectangle to lock
            Rectangle rect = new Rectangle(0, 0, Width, Height);
 
            // get source bitmap pixel format size
            Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);
 
            // Check if bpp (Bits Per Pixel) is 8, 24, or 32
            if (Depth != 8 && Depth != 24 && Depth != 32)
            {
                throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
            }
 
            // Lock bitmap and return bitmap data
            bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite, 
                                         source.PixelFormat);
 
            // create byte array to copy pixel values
            int step = Depth / 8;
            Pixels = new byte[PixelCount * step];
            Iptr = bitmapData.Scan0;
 
            // Copy data from pointer to array
            Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
 
    /// <summary>
    /// Unlock bitmap data
    /// </summary>
    public void UnlockBits()
    {
        try
        {
            // Copy data from byte array to pointer
            Marshal.Copy(Pixels, 0, Iptr, Pixels.Length);
 
            // Unlock bitmap data
            source.UnlockBits(bitmapData);
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
 
    /// <summary>
    /// Get the color of the specified pixel
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <returns></returns>
    public Color GetPixel(int x, int y)
    {
        Color clr = Color.Empty;
 
        // Get color components count
        int cCount = Depth / 8;
 
        // Get start index of the specified pixel
        int i = ((y * Width) + x) * cCount;
 
        if (i > Pixels.Length - cCount)
            throw new IndexOutOfRangeException();
 
        if (Depth == 32) // For 32 bpp get Red, Green, Blue and Alpha
        {
            byte b = Pixels[i];
            byte g = Pixels[i + 1];
            byte r = Pixels[i + 2];
            byte a = Pixels[i + 3]; // a
            clr = Color.FromArgb(a, r, g, b);
        }
        if (Depth == 24) // For 24 bpp get Red, Green and Blue
        {
            byte b = Pixels[i];
            byte g = Pixels[i + 1];
            byte r = Pixels[i + 2];
            clr = Color.FromArgb(r, g, b);
        }
        if (Depth == 8)
        // For 8 bpp get color value (Red, Green and Blue values are the same)
        {
            byte c = Pixels[i];
            clr = Color.FromArgb(c, c, c);
        }
        return clr;
    }
 
    /// <summary>
    /// Set the color of the specified pixel
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <param name="color"></param>
    public void SetPixel(int x, int y, Color color)
    {
        // Get color components count
        int cCount = Depth / 8;
 
        // Get start index of the specified pixel
        int i = ((y * Width) + x) * cCount;
 
        if (Depth == 32) // For 32 bpp set Red, Green, Blue and Alpha
        {
            Pixels[i] = color.B;
            Pixels[i + 1] = color.G;
            Pixels[i + 2] = color.R;
            Pixels[i + 3] = color.A;
        }
        if (Depth == 24) // For 24 bpp set Red, Green and Blue
        {
            Pixels[i] = color.B;
            Pixels[i + 1] = color.G;
            Pixels[i + 2] = color.R;
        }
        if (Depth == 8)
        // For 8 bpp set color value (Red, Green and Blue values are the same)
        {
            Pixels[i] = color.B;
        }
    }
}

Benchmark

To test LockBitmap's performance, we can use the Benchmark class.

public class Benchmark
{
    private static DateTime startDate = DateTime.MinValue;
    private static DateTime endDate = DateTime.MinValue;
 
    public static TimeSpan Span { get { return endDate.Subtract(startDate); } }
 
    public static void Start() { startDate = DateTime.Now; }
 
    public static void End() { endDate = DateTime.Now; }
 
    public static double GetSeconds()
    {
        if (endDate == DateTime.MinValue) return 0.0;
        else return Span.TotalSeconds;
    }
}

Usage

Now we can use the LockBitmap class to work with images very fast.

public void ChangeColor()
{
    Bitmap bmp = (Bitmap)Image.FromFile("d:\\source.png");
    Benchmark.Start();
    LockBitmap lockBitmap = new LockBitmap(bmp);
    lockBitmap.LockBits();
 
    Color compareClr = Color.FromArgb(255, 255, 255, 255);
    for (int y = 0; y < lockBitmap.Height; y++)
    {
        for (int x = 0; x < lockBitmap.Width; x++)
        {
            if (lockBitmap.GetPixel(x, y) == compareClr)
            {
                lockBitmap.SetPixel(x, y, Color.Red);
            }
        }
    }
    lockBitmap.UnlockBits();
    Benchmark.End();
    double seconds = Benchmark.GetSeconds();
    bmp.Save("d:\\result.png");
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Vano Maisuradze
Software Developer
Georgia Georgia
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionGet XY from 8bpp and then draw it in same bmp but in 24bpp formatmemberMikgau16 Apr '13 - 0:09 
Hello
first of all thank you for this usefull class.
 
I have small newbie question.
 
I have to do an image processing on a 8 bpp image. I use your lockbitmap class which works fine.
I store the xy coordinates of pixels that have been modified.
 
Then I transform this 8 bpp bitmap to an 24 bpp bitmap (this is done to be able to draw in red the modified pixel in previous step (in graphic object then i draw it in picturebox).
Finally, I use again lockbitmap class to set the pixel modified to red color to show user which pixwels have been modified.
Issue i get is that the pixels are not correctly localized.
Do you know where can come from my issue.
I guess maybe the screen resolutoin which is not same as bitmap?
If so do you know how to solve that?
Thank you
Mickael
AnswerRe: Get XY from 8bpp and then draw it in same bmp but in 24bpp formatmemberMikgau16 Apr '13 - 3:18 
Ok i found out an issue in the class
 
int i = (y * bitmapData.Stride) + (x * cCount);
 

Find below new class with fix
 
public class LockBitmap
    {
        Bitmap source = null;
        IntPtr Iptr = IntPtr.Zero;
        BitmapData bitmapData = null;
 
        public byte[] Pixels { get; set; }
        public int Depth { get; private set; }
        public int Width { get; private set; }
        public int Height { get; private set; }
 
        public LockBitmap(Bitmap source)
        {
            this.source = source;
        }
 
        /// <summary>
        /// Lock bitmap data
        /// </summary>
        public void LockBits()
        {
            try
            {
                // Get width and height of bitmap
                Width = source.Width;
                Height = source.Height;
 
                // Create rectangle to lock
                Rectangle rect = new Rectangle(0, 0, Width, Height);
 
                // get source bitmap pixel format size
                Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);
 
                // Check if bpp (Bits Per Pixel) is 8, 24, or 32
                if (Depth != 8 && Depth != 24 && Depth != 32)
                    throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
 
                // Lock bitmap and return bitmap data
                bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite, source.PixelFormat);
                // get total locked pixels count
                int PixelCount = bitmapData.Stride * Height;
                // create byte array to copy pixel values

                Pixels = new byte[PixelCount];
                Iptr = bitmapData.Scan0;
 
                // Copy data from pointer to array
                Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
 
        /// <summary>
        /// Unlock bitmap data
        /// </summary>
        public void UnlockBits()
        {
            try
            {
                // Copy data from byte array to pointer
                Marshal.Copy(Pixels, 0, Iptr, Pixels.Length);
 
                // Unlock bitmap data
                source.UnlockBits(bitmapData);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
 
        /// <summary>
        /// Get the color of the specified pixel
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public Color GetPixel(int x, int y)
        {
            Color clr = Color.Empty;
 
            // Get color components count
            int cCount = Depth / 8;
 
            // Get start index of the specified pixel
            int i = ((y * bitmapData.Stride) + x) * cCount;
 
            if (i > Pixels.Length - cCount)
                throw new IndexOutOfRangeException();
 
            if (Depth == 32) // For 32 bpp get Red, Green, Blue and Alpha
            {
                byte b = Pixels[i];
                byte g = Pixels[i + 1];
                byte r = Pixels[i + 2];
                byte a = Pixels[i + 3]; // a
                clr = Color.FromArgb(a, r, g, b);
            }
            if (Depth == 24) // For 24 bpp get Red, Green and Blue
            {
                byte b = Pixels[i];
                byte g = Pixels[i + 1];
                byte r = Pixels[i + 2];
                clr = Color.FromArgb(r, g, b);
            }
            if (Depth == 8)
            // For 8 bpp get color value (Red, Green and Blue values are the same)
            {
                byte c = Pixels[i];
                clr = Color.FromArgb(c, c, c);
            }
            return clr;
        }
 
        /// <summary>
        /// Set the color of the specified pixel
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="color"></param>
        public void SetPixel(int x, int y, Color color)
        {
            // Get color components count
            int cCount = Depth / 8;
 
            // Get start index of the specified pixel
            int i = (y * bitmapData.Stride) + (x * cCount);
 
            if (Depth == 32) // For 32 bpp set Red, Green, Blue and Alpha
            {
                Pixels[i] = color.B;
                Pixels[i + 1] = color.G;
                Pixels[i + 2] = color.R;
                Pixels[i + 3] = color.A;
            }
            if (Depth == 24) // For 24 bpp set Red, Green and Blue
            {
                Pixels[i] = color.B;
                Pixels[i + 1] = color.G;
                Pixels[i + 2] = color.R;
            }
            if (Depth == 8)
            // For 8 bpp set color value (Red, Green and Blue values are the same)
            {
                Pixels[i] = color.B;
            }
        }
    }

QuestionLockBitmap --> Bitmap ?memberssseeegggeee19 Jan '13 - 0:36 
How to convert from a LockBitmap to a Bitmap ?
CType and DirectCast not working, as well as retrieving source, nothing works.
I am looking to create a new bitmap, and write pixels of my choice into it. So I made a blank bitmap and made a lockbitmap out of it, appended the pixels to the LockBitmap and retrieve a System.Drawing.Bitmap. How can I do it ?
 
Please reply.
AnswerRe: LockBitmap --> Bitmap ?memberVano Maisuradze21 Feb '13 - 5:44 
Sorry I didn't noticed your comment.
 
When you are creating LockBitmap object, it takes bitmap object's reference as parameter. So you don't need to convert LockBitmap to Bitmap, because the Bitmap what you want to get from LockBitmap, is the Bitmap which you passed it as parameter.
 
For example:
Bitmap bmp = (Bitmap)Image.FromFile("d:\\source.png");
LockBitmap lockBitmap = new LockBitmap(bmp);
lockBitmap.LockBits();
// Do some work on bmp using lockBitmap object
lockBitmap.UnlockBits(); // Unlock the bmp for later use

GeneralMy vote of 5memberakaki kapanadze17 Nov '12 - 2:16 
Good Job! Thumbs Up | :thumbsup:
GeneralMy vote of 5memberAndre Belloti6 Nov '12 - 6:54 
Amazing performance improvement.
Thanks
GeneralMy vote of 5memberPGT29 Oct '12 - 4:59 
great code samples
Questioni get some errors what references did you use ?memberPGT26 Oct '12 - 9:20 
wel as the title says, i cannt get it to work
jipijee

AnswerRe: i get some errors what references did you use ?memberVano Maisuradze27 Oct '12 - 10:57 
You need to reference System and System.Drawing libraries.
Then add following namespaces:
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

QuestionActual timings, and another code performance enhancement [modified]memberRainer Gustin17 Aug '12 - 9:42 
Faster yes, but how much faster...
 
Using the alternatives code with the added modification of adding returns at the end of every depth test code block for SetPixel and GetPixel to further improve performance. Example:
        if (Depth == 32) // For 32 bpp get Red, Green, Blue and Alpha
        {
            byte b = Pixels[i];
            byte g = Pixels[i + 1];
            byte r = Pixels[i + 2];
            byte a = Pixels[i + 3]; // a
            return (Color.FromArgb(a, r, g, b));
        }    
    ...
        return (Color.Empty);
I did some timings on my Core i7-3770K @ 3.5 GHz.
My high resolution timer frequency was 3,428,085 ticks/second.
 
With the lock outside of the timer test. And performing 100,000 (partially loop unrolled) tests:
Bitmap.SetPixel=0.576ms is almost 16x slower than LockBitmap.SetPixel=0.036 ms.
Bitmap.GetPixel=0.444ms is almost 24x slower than LockBitmap.GetPixel=0.019 ms.
 
But we do need to consider the Lock time, which is significant.
1000 locks took, per lock, an average of 0.036ms (125 ticks) to complete.
Average because 956/1000 of those locks took 101 ticks to complete but 44/1000 had delays in getting the lock taking around 540 ticks.
 
So what is the breakeven point?
If your doing a lock and drawing less than 73 pixels, then you would be better off using Bitmap.SetPixel on average.
 
Also, check out this article if using 24 bit or 8bit: Fast Drawing of Non-32bpp Images with System.Drawing[^]

modified 17 Aug '12 - 16:06.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 15 Aug 2011
Article Copyright 2011 by Vano Maisuradze
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid