Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I load the raw data of a 48bpp image into a Bitmap?

Tags:

c#

.net

image

I asked a similar question to do with a 24bpp image here but I am using the techniques described there and have a second issue, this time to do with 48bpp...

I have a byte[] containing a 16bit color depth image. So there are two bytes for red, two for green and two for blue, in sequence, from the top left of the image to the bottom right. I am loading this into a Bitmap like so (taking into account the Stride etc):

byte[] data = ReadRawData();

// Swap from rgb to bgr
for (int i = 5; i < data.Length; i+=6)
{
   var r1 = data[i - 5];
   var r2 = data[i - 4];

   var b1 = data[i - 1];
   var b2 = data[i];

   data[i - 5] = b1;
   data[i - 4] = b2;

   data[i - 1] = r1;
   data[i] = r2;
}

// Load into a bitmap (I know the width and height, and the format
using (var b = new Bitmap(157, 196, PixelFormat.Format48bppRgb))
{
  var bmpData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
    ImageLockMode.ReadWrite, b.PixelFormat);

  // Here we loop through each line and make sure it is padded to
  // the stride length
  var bytesPerLine = b.Width * 6;
  for (int i = 0; i < b.Height; i++)
  {
    IntPtr offset = bmpData.Scan0 + i * bmpData.Stride;
    Marshal.Copy(data, i * bytesPerLine, offset, bytesPerLine);
    int padding = bmpData.Stride - bytesPerLine;

    if (padding > 0)
    {
      var pad = new byte[padding];
      Marshal.Copy(pad, 0, offset + bytesPerLine, padding);
    }
  }
  b.UnlockBits(bmpData);
  // Done so save
  b.Save("c:\\out.tiff", ImageFormat.Tiff);
}

and it produces this image:

wrong image

The image is not correct, though, it should look like this:

correct image

So, my question is, well, what have I done wrong?


Update

If I switched the r1,r2 and b1,b2 (and the green ones) in case of an endianess issue , but then it draws the image like this:

endianness switch

(so still not right - but does that give us a clue as to what is going on perhaps?)

I have put a file up on github here that is the raw data so you can save that down to your disk and then this little method will open it up:

private static byte[] ReadRawData()
{
    byte[] data;
    using (var ms = new MemoryStream())
    {
        using (var f = File.OpenRead("c:\\data16.bin"))
        {
            byte[] buffer = new byte[2048];
            int read;
            while ((read = f.Read(buffer, 0, buffer.Length)) > 0)
            {
                   ms.Write(buffer, 0, read);
            }
        }
        data = ms.ToArray();
    }
    return data;
}

If I need to use WPF libraries instead, that is fine with me.


Update 2

So, as suggested in the comments I came up with a bit of code to generate a byte array that I could reason about.

What I did, therefore, was output a byte array that is 196 * 200 * 6 in length such that I will have an image that is 196 pixels wide by 200 high with 6 bytes per pixel (the size is handy as it is big enough to see and one that doesn't mean I have to bother with the Stride thing). I then decided that I would split the picture up into 4 vertical stripes, the bytes of which for each stripe are:

  • 0x00, 0x00 ,0x00, 0x00, 0xFF, 0xFF
  • 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF
  • 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00
  • 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

This should mean I can see the difference between the colours right? Well, what I actually got was this, so what am I doing wrong?:

After second attempt

Here is my code that produced the above image:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;

namespace ImagePlayingApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            const int width = 196;
            const int height = 200;
            const int columnWidth = width / 4;
            const int bytesPerPixel = 6;

            var data = new byte[width * height * bytesPerPixel];
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width * bytesPerPixel; x += bytesPerPixel)
                {
                    int i = y * width * bytesPerPixel + x;

                    // Blue and Green components
                    // always 0x00 since we are just
                    // interested in red
                    data[i] = 0x00;
                    data[i + 1] = 0x00;
                    data[i + 2] = 0x00;
                    data[i + 3] = 0x00;

                    if (x < columnWidth * bytesPerPixel)
                    { 
                        // Left most column, full red
                        data[i + 4] = 0xFF;
                        data[i + 5] = 0xFF;
                    }
                    else if (x < columnWidth * bytesPerPixel * 2)
                    {
                        // Next left, half red
                        data[i + 4] = 0x00;
                        data[i + 5] = 0xFF;
                    }
                    else if (x < columnWidth * bytesPerPixel * 3)
                    {
                        // Next left, other half red
                        data[i + 4] = 0xFF;
                        data[i + 5] = 0x00;
                    }
                    else
                    {
                        // Final column, no red
                        data[i + 4] = 0x00;
                        data[i + 5] = 0x00;
                    }
                }
            }

            using (var b = new Bitmap(width, 
                                      height, 
                                      PixelFormat.Format48bppRgb))
            {
                var bmpData = b.LockBits(
                    new Rectangle(0, 0, b.Width, b.Height), 
                    ImageLockMode.ReadWrite, 
                    b.PixelFormat);
                Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
                b.UnlockBits(bmpData);

                b.Save(@"c:\users\public\stripes2.tiff", ImageFormat.Tiff);
            }
        }
    }
}

So, does any one know how I can get the byte array described here into a Bitmap that is correct? As I said, if there is anything in WPF that would help, that would be fine or maybe I need to transform it into, I don't know 64bppargb format or something?

like image 765
kmp Avatar asked Mar 08 '13 19:03

kmp


1 Answers

So I have figured out how to do it, but it is not really perfect...

If I add references for System.Xaml, PresentationCore and WindowsBase and use the WPF image classes as an intermediate step I can get to a Bitmap, like so (using the data.bin file linked in the question and the ReadRawData method):

// Declare what we know
const int width = 157;
const int height = 196;
const double dpi = 50;
var format = PixelFormats.Rgb48;
int stride = (width * format.BitsPerPixel + 7) / 8;

// Get the data (see the question for this method)
var data = ReadRawData();

// Now put the data into a WiteableBitmap
var wb = new WriteableBitmap(width, height, dpi, dpi, format, null);
wb.WritePixels(new Int32Rect(0, 0, width, height), data, stride, 0);

// Encode as a TIFF
var enc = new TiffBitmapEncoder { Compression = TiffCompressOption.None };
enc.Frames.Add(BitmapFrame.Create(wb));

// And convert to a bitmap
using (var ms = new MemoryStream())
{
    enc.Save(ms);

    using (var bmp = new Bitmap(ms))
    {
        // Blergh, I feel dirty!
        bmp.Save("c:\\works.tiff", ImageFormat.Tiff);
    }
}

et voila!

works.tiff

I think what I will do in my project is ditch Windows.Drawing entirely given this problem and the whole pain with the stride and fact that RGB is actually BGR (basically I'm sick of it).

like image 117
kmp Avatar answered Nov 10 '22 18:11

kmp