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 :-(
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;