Introduction
It seems that reading and writing pixel/color information from/to bitmaps is the most unstable thing in .NET.
Windows Forms do it in a very specific manner. WPF do it differently. Silverlight do/(did, depending on how you think about it) differently again and finally, Universal Apps, which are mostly based on WPF and Silverlight principles, do it differently again.
If you want to know, I wanted to write the Simple Life Simulation application that I already presented in one of my previous articles (Managed Bitmaps 2) as a Universal App.
I looked for the WriteableBitmap
class, as that's the heart of that application. I already knew it existed in Universal apps and it actually has the same name as the classes used in Silverlight and WPF. So it was supposed to be easy. Then, I read this in the documentation in MSDN:
- "To access the pixel content from C# or Microsoft Visual Basic, you can use the AsStream extension method to access the underlying buffer as a stream."
At that moment I was thinking "What the hell have they done this time?!?"
Then I read the next paragraph:
- "To access the pixel content from C++, you can query for the IBufferByteAccess type (defined in Robuffer.h) and directly access its Buffer property."
In fact, that's what I was expecting to read from the start. But, is this solution C++ only? Why?
Why can't I use C# to do the same?
The Answer
Now I can tell you that I was overreacting. But the documentation is so minimal that I thought that I would only be able to use the IBufferByteAccess
interface by using C++. I simply don't know why it is not exposed directly, but C# can access byte buffers/Bgra buffers directly. It needs to use unsafe
code, but it is perfectly doable, and that's what I was expecting from the beginning.
To do that, all we need to do is to import the IBufferByteAccess
in C# and, of course, use unsafe
code to access the buffer.
The Interface
In C#, you need to import the interface like this:
[ComImport]
[Guid("905a0fef-bc53-11df-8c49-001e4fc686da")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal unsafe interface IBufferByteAccess
{
byte* Buffer { get; }
}
The most important thing here is not the interface itself, but all the attributes used on the interface. If you wonder, I actually got that from the C++ header.
.NET does the work of dispatching the interface to COM thanks to those attributes. So, don't think you can avoid them.
The Pixel Data
Then, considering the WriteableBitmap
is always in BGRA format (where each pixel is 8 bits, that is, one byte, in that specific order, and I discovered that from the comment in the MSDN sample) I wrote this struct to represent a pixel/color:
[StructLayout(LayoutKind.Sequential)]
public struct Bgra
{
public byte B;
public byte G;
public byte R;
public byte A;
}
In fact I think I could change the return type of the Buffer
property from byte *
to Bgra *
without issues, but I preferred to keep the interface intact (as it is in C++) and then do the cast myself.
Another thing that annoyed me is that the BGRA format is called Bgra32
in WPF PixelFormats
(meaning the entire BGRA uses 32 bits) while it is called Bgra8
in BitmapPixelFormat
, meaning that each channel is 8 bits. In the end, they refer to the exactly same format, but the 8 in one of them is related to each channel, while the 32 in the other is related to the sum of all the 4 channels. Quite confusing, don't you think?
Using it
Finally, we get to the point of using it.
So, considering we have a writeableBitmap
variable, of type WriteableBitmap
, we need to the following to get the real buffer in a format we can manipulate:
unsafe
{
var pixelBuffer = writeableBitmap.PixelBuffer;
var bufferByteAccess = (IBufferByteAccess)pixelBuffer;
var pixels = (Bgra *)bufferByteAccess.Buffer;
Then, it is a matter of chosing the pixel(s) that we want to manipulate. For example, to access an individual pixel, we use the formula (y*bitmapWidth) + x. That is: pixels[(y*bitmap.PixelWidth) + x]
.
But if we want to access all the pixels (either as read or write) we can do this:
for(int i=0; i<width*height; i++)
{
}
Conclusion
I know, this is quite short for an article, but I simply wanted to show that the most straight forward solution, which is the one probably used in WPF and also the one used in C++, can be used in C#.
Please, if you don't have that kind of restriction, don't use a WriteableBitmap
as a Stream
. It may be safer (in some scenarios), but makes everything slower, much less prepared for multi-threading (in case of ray-tracing, for example) and also harder to understand.
Simple Life Simulation
Now you can find the Simple Life Simulation application on the store from this link:
.