Fast work with Bitmaps in C#

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.

  • http://csharpexamples.com/fast-image-processing-c/
  • http://msdn.microsoft.com/en-us/library/dd460713%28v=vs.110%29.aspx
  • http://msdn.microsoft.com/tr-tr/library/system.drawing.imaging.bitmapdata%28v=vs.110%29.aspx

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.