Fast work with Bitmaps in C#

57,339

Solution 1

You can do it a couple of different ways. You can use unsafe to get direct access to the data, or you can use marshaling to copy the data back and forth. The unsafe code is faster, but marshaling doesn't require unsafe code. Here's a performance comparison I did a while back.

Here's a complete sample using lockbits:

/*Note unsafe keyword*/
public unsafe Image ThresholdUA(float thresh)
{
    Bitmap b = new Bitmap(_image);//note this has several overloads, including a path to an image

    BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);

    byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);

    /*This time we convert the IntPtr to a ptr*/
    byte* scan0 = (byte*)bData.Scan0.ToPointer();

    for (int i = 0; i < bData.Height; ++i)
    {
        for (int j = 0; j < bData.Width; ++j)
        {
            byte* data = scan0 + i * bData.Stride + j * bitsPerPixel / 8;

            //data is a pointer to the first byte of the 3-byte color data
            //data[0] = blueComponent;
            //data[1] = greenComponent;
            //data[2] = redComponent;
        }
    }

    b.UnlockBits(bData);

    return b;
}

Here's the same thing, but with marshaling:

/*No unsafe keyword!*/
public Image ThresholdMA(float thresh)
{
    Bitmap b = new Bitmap(_image);

    BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);

    /* GetBitsPerPixel just does a switch on the PixelFormat and returns the number */
    byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);

    /*the size of the image in bytes */
    int size = bData.Stride * bData.Height;

    /*Allocate buffer for image*/
    byte[] data = new byte[size];

    /*This overload copies data of /size/ into /data/ from location specified (/Scan0/)*/
    System.Runtime.InteropServices.Marshal.Copy(bData.Scan0, data, 0, size);

    for (int i = 0; i < size; i += bitsPerPixel / 8 )
    {
        double magnitude = 1/3d*(data[i] +data[i + 1] +data[i + 2]);

        //data[i] is the first of 3 bytes of color

    }

    /* This override copies the data back into the location specified */
    System.Runtime.InteropServices.Marshal.Copy(data, 0, bData.Scan0, data.Length);

    b.UnlockBits(bData);

    return b;
}

Solution 2

You can use Bitmap.LockBits method. Also if you want to use parallel task execution, you can use the Parallel class in System.Threading.Tasks namespace. Following links have some samples and explanations.

Solution 3

If you're on C# 8.0 I'll suggest to use the new Span<T> for higher efficiency.

Here's a rough implementation

public unsafe class FastBitmap : IDisposable
{
    private Bitmap _bmp;
    private ImageLockMode _lockmode;
    private int _pixelLength;

    private Rectangle _rect;
    private BitmapData _data;
    private byte* _bufferPtr;

    public int Width { get => _bmp.Width; }
    public int Height { get => _bmp.Height; }
    public PixelFormat PixelFormat { get => _bmp.PixelFormat; }

    public FastBitmap(Bitmap bmp, ImageLockMode lockMode)
    {
        _bmp = bmp;
        _lockmode = lockMode;

        _pixelLength = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
        _rect = new Rectangle(0, 0, Width, Height);
        _data = bmp.LockBits(_rect, lockMode, PixelFormat);
        _bufferPtr = (byte*)_data.Scan0.ToPointer();
    }

    public Span<byte> this[int x, int y]
    {
        get
        {
            var pixel = _bufferPtr + y * _data.Stride + x * _pixelLength;
            return new Span<byte>(pixel, _pixelLength);
        }
        set
        {
            value.CopyTo(this[x, y]);
        }
    }

    public void Dispose()
    {
        _bmp.UnlockBits(_data);
    }
}

Solution 4

You want LockBits. You can then extract the bytes you want from the BitmapData object it gives you.

Solution 5

Building on @notJim answer (and with help from https://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspx), I developed the following that makes my life a lot easier in that I end up with an array of arrays that allows me to jump to a pixel by its x and y coordinates. Of course, the x coordinate needs to be corrected for by the number of bytes per pixel, but that is an easy extension.

Dim bitmapData As Imaging.BitmapData = myBitmap.LockBits(New Rectangle(0, 0, myBitmap.Width, myBitmap.Height), Imaging.ImageLockMode.ReadOnly, myBitmap.PixelFormat)

Dim size As Integer = Math.Abs(bitmapData.Stride) * bitmapData.Height
Dim data(size - 1) As Byte

Marshal.Copy(bitmapData.Scan0, data, 0, size)

Dim pixelArray(myBitmap.Height)() As Byte

'we have to load all the opacity pixels into an array for later scanning by column
'the data comes in rows
For y = myBitmap.Height - 1 To 0 Step -1
    Dim rowArray(bitmapData.Stride) As Byte
    Array.Copy(data, y * bitmapData.Stride, rowArray, 0, bitmapData.Stride)
    'For x = myBitmap.Width - 1 To 0 Step -1
    '   Dim i = (y * bitmapData.Stride) + (x * 4)
    '   Dim B = data(i)
    '   Dim G = data(i + 1)
    '   Dim R = data(i + 2)
    '   Dim A = data(i + 3)
    'Next
    pixelArray(y) = rowArray
Next
Share:
57,339

Related videos on Youtube

AndreyAkinshin
Author by

AndreyAkinshin

Updated on April 21, 2022

Comments

  • AndreyAkinshin
    AndreyAkinshin about 2 years

    I need to access each pixel of a Bitmap, work with them, then save them to a Bitmap.

    Using Bitmap.GetPixel() and Bitmap.SetPixel(), my program runs slowly.

    How can I quickly convert Bitmap to byte[] and back?

    I need a byte[] with length = (4 * width * height), containing RGBA data of each pixel.

    • turgay
      turgay over 9 years
      Following link contains bitmap pixel access methods comparatively. csharpexamples.com/fast-image-processing-c
    • György Kőszeg
      György Kőszeg over 2 years
      The answers here recommend using LockBits, which needs different access for all possible pixel formats. Here is a library (disclaimer: written by me), which provides fast, unified managed access for all possible formats, including indexed ones.
  • MusiGenesis
    MusiGenesis over 14 years
    I think it's faster if you use the BitmapData object returned from LockBits inside an unsafe block with a pointer cast (NOTE: I actually have no idea if this is faster or not, but I'm trying to goad someone else into benchmarking it).
  • David Seiler
    David Seiler over 14 years
    It'd save you the copies into and out of the BitmapData, which is nice. Not sure how much savings that would grant in practice, though; memcpy() is really fast these days.
  • MusiGenesis
    MusiGenesis over 14 years
    Thanks, and I didn't even have to goad anybody. :)
  • Steve Niles
    Steve Niles over 11 years
    You should be aware when doing this, that the order of the color channels may be different depending on what PixelFormat you're using. For example, with 24bit Bitmaps, the first byte of the pixel is the blue channel, followed by green, then red, as opposed to the commonly expected Red-Green-Blue order.
  • freakinpenguin
    freakinpenguin almost 10 years
    Thanks for you effort!
  • GorillaApe
    GorillaApe over 9 years
    bData.Height seems a bit expensive. I got some performance boost skipping it
  • danodonovan
    danodonovan about 9 years
    @Parhs sure, skipping bData.Height will speed things up, because you're only taking the first row of your data. Try size = 1 that'll make things even faster!
  • wondra
    wondra almost 9 years
    You should also be aware that this may not get expected byte data because generally bData.Stride does not have to be equal to b.Width * bytePerPix.
  • Md Sifatul Islam
    Md Sifatul Islam over 7 years
    whats the purpose of using the variable magnitude?
  • Nyerguds
    Nyerguds about 7 years
    For >=8bpp images I usually get around the stride difference by copying per line, increasing the source offset by the stride with each line but copying to a new array with the actual width (well, width * BPP / 8 of course) * height as length. For images with <8bpp though, stuff gets... interesting. A good rule is to always include the stride with your image processing functions, preferably as ref parameter in case it gets changed for your output.
  • Felipe
    Felipe over 6 years
    Link is ded, can anyone summarize what it said?
  • davidtbernal
    davidtbernal over 6 years
    @Pipe fixed the link
  • John Alexiou
    John Alexiou over 6 years
    What is GetBitsPerPixel() here?
  • rotgers
    rotgers about 6 years
    @ja72 System.Drawing.Bitmap.GetPixelFormatSize(...);
  • royalTS
    royalTS over 5 years
    @Jargon Isn´t the order of the color channels depending on the endianness rather than the pixel format?
  • Dennis van Gils
    Dennis van Gils about 4 years
    @davidtbernal in your second example using marshalling, is there any way to get the x/y of current pixel (based on i I think?)
  • jklw10
    jklw10 almost 4 years
    how would i go about using this if i wanted to write to the bitmap ?
  • Shaamil Ahmed
    Shaamil Ahmed almost 4 years
    I've updated the code and should work like a charm. Now you access the pixels by saying new FastBitmap(.....)[100, 200] where 100 is x and 200 is y. You can set the value of the pixel by writing new FastBitmap(......)[100, 200] = new Span<byte>(array);
  • jklw10
    jklw10 almost 4 years
    how would you go about converting this back into a bitmap? could i just make _data public?
  • jklw10
    jklw10 almost 4 years
    gyazo.com/d5ab06cf35a3bb84a56d8330fa25517b i made my own cast but encountered this error when trying fastbmp[x,y] = new Span<byte>(BitConverter.GetBytes(color.ToArgb()));
  • jklw10
    jklw10 almost 4 years
    isn't var pixel = _bufferPtr + y * _data.Stride * x * _pixelLength; supposed to be var pixel = _bufferPtr + y * _data.Stride + x * _pixelLength