Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I copy this array any faster?

Is this the absolute fastest I could possibly copy a Bitmap to a Byte[] in C#?

If there is a speedier way I am dying to know!

const int WIDTH = /* width */;
const int HEIGHT = /* height */;


Bitmap bitmap = new Bitmap(WIDTH, HEIGHT, PixelFormat.Format32bppRgb);
Byte[] bytes = new byte[WIDTH * HEIGHT * 4];

BitmapToByteArray(bitmap, bytes);


private unsafe void BitmapToByteArray(Bitmap bitmap, Byte[] bytes)
{
    BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, WIDTH, HEIGHT), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);

    fixed(byte* pBytes = &bytes[0])
    {
        MoveMemory(pBytes, bitmapData.Scan0.ToPointer(), WIDTH * HEIGHT * 4);
    }

    bitmap.UnlockBits(bitmapData);
}

[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
private static unsafe extern void MoveMemory(void* dest, void* src, int size);
like image 431
tbridge Avatar asked Mar 02 '11 04:03

tbridge


2 Answers

Well, using Marshal.Copy() would be wiser here, that at least cuts out on the (one time) cost of looking up the DLL export. But that's it, they both use the C runtime memcpy() function. Speed is entirely throttled by the RAM bus bandwidth, only buying a more expensive machine can speed it up.

Beware that profiling is tricky, accessing the bitmap data the first time causes page faults to get the pixel data into memory. How long that takes is critically dependent on what your hard disk is doing and the state of the file system cache.

like image 53
Hans Passant Avatar answered Sep 28 '22 14:09

Hans Passant


I'm reading into this that you're looking to do pixel manipulation of a bitmap. So logically, you're wanting to access the pixels as an array in order to do this.

The problem your facing is fundamentally flawed, and the other guys didn't pick up on this - and maybe this is something they can take away too?

Basically, you've gotten so far to be messing with pointers from bitmaps, I'm going to give you one of my hard earned "secrets".

YOU DON'T NEED TO COPY THE BITMAP INTO AN ARRAY. Just use it as it is.

Unsafe pointers are your friend in this case. When you hit "bitmapData.Scan0.ToPointer()" you missed the trick.

So long as you have a pointer to the first pixel, and the Stride, and you're aware of the number of bytes per pixel, then you're on to a winner.

I define a bitmap specifically with 32 bits per pixel (memory efficient for UInt32 access) and I get a pointer to first pixel. I then treat the pointer as an array, and can both read and write pixel data as UInt32 values.

Code speaks louder than words. have a look.

I salute you for making it this far!

This is untested code, but much is copied from my source.

public delegate void BitmapWork(UInt32* ptr, int stride);

    /// <summary>
    /// you don't want to forget to unlock a bitmap do you?  I've made that mistake too many times...
    /// </summary>
    unsafe private void UnlockBitmapAndDoWork(Bitmap bmp, BitmapWork work)
    {
        var s = new Rectangle (0, 0, bmp.Width, bmp.Height); 
        var locker = bmp.LockBits(new Rectangle(0, 0, 320, 200), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

        var ptr = (UInt32*)locker.Scan0.ToPointer();

        // so many times I've forgotten the stride is expressed in bytes, but I'm accessing with UInt32's.  So, divide by 4.
        var stride = locker.Stride / 4;

        work(ptr, stride);
        bmp.UnlockBits(locker);
    }

    //using System.Drawing.Imaging;
    unsafe private void randomPixels()
    {
        Random r = new Random(DateTime.Now.Millisecond);

        // 32 bits per pixel.  You might need to concern youself with the Alpha part depending on your use of the bitmap
        Bitmap bmp = new Bitmap(300, 200, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        UnlockBitmapAndDoWork(bmp, (ptr, stride) => 
        {
            var calcLength = 300 * 200; // so we only have one loop, not two.  It's quicker!
            for (int i = 0; i < calcLength; i++)
            {
                // You can use the pointer like an array.  But there's no bounds checking.
                ptr[i] = (UInt32)r.Next();
            }
        });            
    }
like image 30
Simon Miller Avatar answered Sep 28 '22 14:09

Simon Miller