Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LockBits() of Bitmap as different format in C#?

I recently stumbled across this answer to a question that was about converting Bitmap format to 24-bit RGB in C#: Faster copying of images to change their PixelFormat, where the selected answer states the following:

Another way is to lock the bits without the alpha channel and then copy the memory to a new bitmap

and the following code is provided as an example (simplified for brevity):

public static Bitmap RemoveAlphaChannel(Bitmap bitmap) {
    Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
    Bitmap bitmapDest = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format24bppRgb);
    BitmapData data = bitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
    BitmapData dataDest = bitmapDest.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
    int bitmapSize = data.Stride * data.Height;
    Buffer.MemoryCopy(dataDest.Scan0, data.Scan0, bitmapSize, bitmapSize);
    bitmap.UnlockBits(data);
    bitmapDest.UnlockBits(dataDest);
    return bitmapDest;
}

Let's convert say a 32-bit ARGB image to 24-bit RGB using this function. I have the following questions:

  1. How does locking a 32-bit Bitmap as 24-bit work, and why is it allowed?
  2. How are the RGB components of every pixel in the original bitmap contiguous now? Where did the Alpha component go? What happened to the stride length?
  3. Does locking the bits of the Bitmap as a different format cause a copy of the bitmap to be created under the hood?
like image 894
MathuSum Mut Avatar asked Mar 11 '17 12:03

MathuSum Mut


1 Answers

This is simply a convenience provided by GDI+. It matters, it is often much more convenient and faster to access the pixels with a format that works well with the particular algorithm you want to apply.

The 32bppPArgb format is best to get the bitmap rendered to the screen as fast as possible, it is compatible with the video frame buffer format so no conversion is required. Typical render speed is x10 faster than any other pixel format. But PArgb is very awkward to manipulate in code. The R, G and B channel values are corrected by the alpha value, you'd have to divide it out again to recover the original RGB values. Asking for an Argb format solves that in one fell swoop.

Likewise, the 24bppRgb format is awkward, you have to use a byte* to access the pixel channels. That requires 3 memory accesses per pixel, slows down the code a great deal. Asking for 32bppArgb permits using an int*, much faster, and allows you to ignore stride.

Nothing particularly complicated about these conversions. But they are not for free, GDI+ has to do the work to allocate temporary storage and convert the pixel values back and forth. I profiled it on my pokey laptop, using a 1000 x 1000 bitmap and ImageLockMode.ReadWrite:

32bppArgb => 32bppArgb  : 0.002 msec
32bppPArgb => 32bppArgb : 5.6 msec
32bppArgb => 24bppRgb   : 5.6 msec
32bppPArgb => 24bppRgb  : 5.6 msec

I measured perf on your RemoveAlphaChannel() method, using memcpy() to do the copy on the same 32bppArgb 1000 x 1000 bitmap. I got 2.8 msec for the required single ImageLockMode.ReadOnly pixel conversion and 2.8 msec for the copy. About twice as fast as doing it the GDI+ provided way with Graphics.DrawImage() which took 9.3 msec.

like image 53
Hans Passant Avatar answered Sep 24 '22 11:09

Hans Passant