Search code examples
c#bitmappixelformat

C#: How to load a RAW image (format: rgb565) into a Bitmap?


My Goal:
Display an image, which comes along in rgb565 raw format, in my Windows Forms program. (off topic: the data stems from an OV7670 camera module)

My approach:
First, I create an empty Bitmap. Next, I insert the image data (raw format: rgb565) into the payload section of the empty Bitmap. Finally, I display the modified Bitmap within a PictureBox.

My problem:
Everything works fine, but the testimage is displayed with diagonal stripes instead of vertical stripes (see links below).

Original rgb565 raw image: Original rgb565 raw image
Screenshot of PictureBox (with diagonal stripes): Screenshot of PictureBox

I did manage to display the image by extracting R,G,B and using SetPixel(), but that's too slow for me. That's why I would like to get the code underneath to display the image in the correct way.

My Testimage can be found on dropbox here:
Testimage: Testimage


MemoryStream memoryStream = new MemoryStream(1000000);

    // Read raw image into byte array
    string imgpath = "rgb565_LSB-first_313x240.raw";
    FileStream fs = new FileStream(imgpath, FileMode.Open);
    fs.CopyTo(memoryStream);
    Byte[] buffer = memoryStream.ToArray();

    // Create empty Bitmep and inject byte arrays data into bitmap's data area
    Bitmap bmp = new Bitmap(313, 240, PixelFormat.Format16bppRgb565);

    // Lock the bitmap's bits.  
    Rectangle rect = new Rectangle(0, 0, 313, 240);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite,
                                      PixelFormat.Format16bppRgb565);
    IntPtr ptrToFirstPixel = bmpData.Scan0;

    // Inject the rgb565 data (stored in the buffer array)
    Marshal.Copy(buffer, 0, ptrToFirstPixel, buffer.Length);
    bmp.UnlockBits(bmpData);

    // Diplay Bitmap in my PictureBox
    pbImage.Image = bmp;

Expected result: vertical stripes :-)
Actual result: diagonal stripes :-(


Solution

  • After 10 hours poking around in the haystack, I could finally track down the reason, which was definitly not a banality (at least not to me).

    Here it comes: Bitmap specification requires, to pad row size to a multiple of 4 Bytes! https://upload.wikimedia.org/wikipedia/commons/c/c4/BMPfileFormat.png

    Since my colorbar-testimage had 313 pixel linewidth and because every pixel was encoded rgb565, I got 626 bytes per line.

    But 626 is not a multiple of 4. That's why I should have added another 2 "padding bytes" to the end of each line. And that was the reason of my diagonal stripes.

    After adding these 2 padding bytes (0x00 0x00), I ended up with an Bitmap Image where the header tells you: this image has a width of 313 pixels, but the real image data consists of 314 pixels per line - it's a bit weird, but that's defined by the spec.

    As soon as I modified my Bitmap to comply with this requirement of the specification, the diagonal stripes disappeared and the expected vertical striped turned out of the dark.

    Since 99% of all sample code in the internet assumes multiple of 4 linewidth for their images (e.g. imageformats of 320x240 or 680x480), they all do not face my problem - but most of them will, if you feed them rgb565 images with odd number of line-pixels as I had to do.

    A few additional lines (marked with "// ***") were sufficient to add the "padding trick". (The code below is just for explaining purposes, in productive code you might want to add some optimizations)

    MemoryStream memoryStream = new MemoryStream(1000000);
    
    // Read raw image into byte array
    string imgpath = "rgb565_LSB-first_313x240.raw";
    FileStream fs = new FileStream(imgpath, FileMode.Open);
    fs.CopyTo(memoryStream);
    Byte[] buffer = memoryStream.ToArray();
    
    // Create empty Bitmep and inject byte arrays data into bitmap's data area
    Bitmap bmp = new Bitmap(313, 240, PixelFormat.Format16bppRgb565);
    // Lock the bitmap's bits.  
    Rectangle rect = new Rectangle(0, 0, 313, 240);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format16bppRgb565);
    IntPtr ptrToFirstPixel = bmpData.Scan0;
    
    
    // *** Attention:  Bitmap specification requires, to pad row size to a multiple of 4 Bytes 
    // *** See:        https://upload.wikimedia.org/wikipedia/commons/c/c4/BMPfileFormat.png
    // *** Solution:   Copy buffer[] to buffer2[] and pay attention to padding (!!) at the end of each row
    Byte[] buffer2 = new Byte[240 * bmpData.Stride];
    for (int y = 0; y < 240; y++)
    {
        Buffer.BlockCopy(buffer, y * 313 * 2, buffer2, y * bmpData.Stride, 313 * 2);
    }
    
    Marshal.Copy(buffer2, 0, ptrToFirstPixel, buffer2.Length);  // *** Use padded buffer2 instead of buffer1
    bmp.UnlockBits(bmpData);
    // Diplay Bitmap in my PictureBox
    pbImage.Image = bmp;