I am working with cameras (in this case monochrome) in VB.NET. The API of the cameras is in C++ and the manufacturer provides wrappers. As is typical in these cases, the image is returned as a Byte
array. In my particular case it's 8 bits per pixel so there's no fancy bitpacking to undo.
I was flabbergasted that .NET has so little support for this type of thing. Searching online I found various topics, but it doesn't help that in many cases people have a byte array corresponding to image data (that is, read an image file into a byte array and then have .NET interpret the array as a file, with header and everything).
In this case I need to convert an array of raw pixel values into, particularly, something I can display on-screen.
There doesn't seem to be any built-in way to do this, because the 8bpp format built into .NET is indexed (needs a palette) but it doesn't seem that setting the palette is supported. Again, this really surprised me. This is the most promising topic I found.
So the only way I found to do this is to create a Bitmap
and then copy the pixel values pixel by pixel. In my case an 8 MPixel image took several seconds on the fastest i7 when using SetPixel
.
The alternative I found to using SetPixel
is LockBits
, which then gives you access to the (unmanaged) array of bytes that constitutes the image. It seems wasteful, because I have to manipulate the array in .NET and then copy it back to unmananaged space. I'm already copying the original image data once (so the original can be reused or discarded by the rest of what's going on) so although significantly faster than using SetPixel
this still seems wasteful.
Here is the VB code I have:
Public Function GetBitmap(ByRef TheBitmap As System.Drawing.Bitmap) As Integer
Dim ImageData() As Byte
Dim TheWidth, TheHeight As Integer
'I've omitted the code here (too specific for this question) which allocates
'ImageData and then uses Array.Copy to store a copy of the pixel values
'in it. It also assigns the proper values to TheWidth and TheHeight.
TheBitmap = New System.Drawing.Bitmap(TheWidth, TheHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb)
Dim TheData As System.Drawing.Imaging.BitmapData = TheBitmap.LockBits(
New System.Drawing.Rectangle(0, 0, TheWidth, TheHeight), Drawing.Imaging.ImageLockMode.ReadWrite, TheBitmap.PixelFormat)
Dim Image24(Math.Abs(TheData.Stride) * TheHeight) As Byte
'Then we have two choices: to do it in parallel or not. I tried both; the sequential version is commented out below.
System.Threading.Tasks.Parallel.For(0, ImageData.Length, Sub(i)
Image24(i * 3 + 0) = ImageData(i)
Image24(i * 3 + 1) = ImageData(i)
Image24(i * 3 + 2) = ImageData(i)
End Sub)
'Dim i As Integer
'For i = 0 To ImageData.Length - 1
' Image24(i * 3 + 0) = ImageData(i)
' Image24(i * 3 + 1) = ImageData(i)
' Image24(i * 3 + 2) = ImageData(i)
'Next
System.Runtime.InteropServices.Marshal.Copy(Image24, 0, TheData.Scan0, Image24.Length)
TheBitmap.UnlockBits(TheData)
'The above based on
'http://msdn.microsoft.com/en-us/library/system.drawing.imaging.bitmapdata.aspx
Return 1
End Function
For an 8 MPixel image the sequential version consumes about 220 milliseconds from creating TheBitmap
up to (and including) the call to TheBitmap.UnlockBits()
. The parallel version is much more inconsistent but ranges between 60 and 80 milliseconds. Considering I'm acquiring from four cameras simultaneously and streaming to disk with plenty of time to spare, this is pretty disappointing. The API comes with sample code in C++ which can display from all four cameras simultaneously at full frame rate (17 fps). It never deals with Bitmap
, of course, since GDI accesses arrays of raw pixel values.
In summary, I have to travel across the managed/unmanaged barrier simply because there is no direct way to take an array of pixel values and "stuff" it into a Bitmap
instance. In this case I'm also copying the pixel values into an 24bpp Bitmap
because I cannot "set" the palette in an indexed image so 8bpp is not a viable format for display.
Is there no better way?
Create a valid bitmap header, and preappend it to the byte array. You'll only need to manually create 56-128 bytes of data.
Second, create a stream from a byte array.
Next, create an image or bitmap class by using Image.FromStream()/Bitmap.FromStream()
I wouldn't imagine that there is any sort of raw imaging features in .Net. There is so much needed information for an image, it would be an extremely lengthy parameter list for a method to accept raw bytes and create an imagine (is it a bitmap, jpeg, gif, png, etc, and all the additional data each of those requires to create a valid image) it wouldn't make sense.