Show RGBA image from memory
Before you delve into the details of WIC, you may consider to encapsulate byte swapping in a simple custom BitmapSource like shown below. It takes an RGBA byte array and swaps the pixel bytes in the overridden CopyPixels
method to produce a PBGRA buffer.
You can simply create an instance of this custom BitmapSource by providing the RGBA buffer and the bitmap width like
var buffer = new byte[] { 25, 166, 0, 255, 90, 0, 120, 255 };var bitmapSource = new RgbaBitmapSource(buffer, 2);
Here is the implementation:
public class RgbaBitmapSource : BitmapSource{ private byte[] rgbaBuffer; private int pixelWidth; private int pixelHeight; public RgbaBitmapSource(byte[] rgbaBuffer, int pixelWidth) { this.rgbaBuffer = rgbaBuffer; this.pixelWidth = pixelWidth; this.pixelHeight = rgbaBuffer.Length / (4 * pixelWidth); } public override void CopyPixels( Int32Rect sourceRect, Array pixels, int stride, int offset) { for (int y = sourceRect.Y; y < sourceRect.Y + sourceRect.Height; y++) { for (int x = sourceRect.X; x < sourceRect.X + sourceRect.Width; x++) { int i = stride * y + 4 * x; byte a = rgbaBuffer[i + 3]; byte r = (byte)(rgbaBuffer[i] * a / 256); // pre-multiplied R byte g = (byte)(rgbaBuffer[i + 1] * a / 256); // pre-multiplied G byte b = (byte)(rgbaBuffer[i + 2] * a / 256); // pre-multiplied B pixels.SetValue(b, i + offset); pixels.SetValue(g, i + offset + 1); pixels.SetValue(r, i + offset + 2); pixels.SetValue(a, i + offset + 3); } } } protected override Freezable CreateInstanceCore() { return new RgbaBitmapSource(rgbaBuffer, pixelWidth); } public override event EventHandler<DownloadProgressEventArgs> DownloadProgress; public override event EventHandler DownloadCompleted; public override event EventHandler<ExceptionEventArgs> DownloadFailed; public override event EventHandler<ExceptionEventArgs> DecodeFailed; public override double DpiX { get { return 96; } } public override double DpiY { get { return 96; } } public override PixelFormat Format { get { return PixelFormats.Pbgra32; } } public override int PixelWidth { get { return pixelWidth; } } public override int PixelHeight { get { return pixelHeight; } } public override double Width { get { return pixelWidth; } } public override double Height { get { return pixelHeight; } }}
As pointed out in a comment, you might improve performance by implementing an unsafe copy operation, which could look like this:
unsafe public override void CopyPixels( Int32Rect sourceRect, Array pixels, int stride, int offset){ fixed (byte* source = rgbaBuffer, destination = (byte[])pixels) { byte* dstPtr = destination + offset; for (int y = sourceRect.Y; y < sourceRect.Y + sourceRect.Height; y++) { for (int x = sourceRect.X; x < sourceRect.X + sourceRect.Width; x++) { byte* srcPtr = source + stride * y + 4 * x; byte a = *(srcPtr + 3); *(dstPtr++) = (byte)(*(srcPtr + 2) * a / 256); // pre-multiplied B *(dstPtr++) = (byte)(*(srcPtr + 1) * a / 256); // pre-multiplied G *(dstPtr++) = (byte)(*srcPtr * a / 256); // pre-multiplied R *(dstPtr++) = a; } } }}
I can't see how you're going to get there without doing the swap. This example does the job but instead of altering the byte buffer directly, it just swaps the R and B values for each 32-bit word as it reads them into the bitmap's backbuffer. Since Windows is little-endian, the bit-shifting looks counter intuitive because a dereferenced RGBA word reads as ABGR. But, this is probably your best bet in terms of overall performance.
I just hard-coded the same 2px example you used in your post. I haven't tried to scale it. I just wanted to present a different approach to swapping the bytes. I hope this helps.
byte[] buffer = new byte[] { 25, 166, 0, 255, 90, 0, 120, 255 }; WriteableBitmap bitmap = new WriteableBitmap(2, 1, 96, 96, PixelFormats.Bgra32, null); int numPixels = buffer.Length / sizeof(uint); bitmap.Lock(); unsafe { fixed (byte* pSrcData = &buffer[0]) { uint* pCurrent = (uint*)pSrcData; uint* pBitmapData = (uint*)bitmap.BackBuffer; for (int n = 0; n < numPixels; n++) { uint x = *(pCurrent++); // Swap R and B. Source is in format: 0xAABBGGRR *(pBitmapData + n) = (x & 0xFF000000) | (x & 0x00FF0000) >> 16 | (x & 0x0000FF00) | (x & 0x000000FF) << 16; } } } bitmap.AddDirtyRect(new Int32Rect(0, 0, 2, 1)); bitmap.Unlock(); image1.Source = bitmap;
You can either:
a. allocate a new buffer and copy the sub-pixels into it while swapping the channels (using unsafe code) and create a BitmapSource using the buffer. (Using an array as a source or Using an IntPtr as a source.)
or
b. Display the image as is (with the wrong channel order) and use an HLSL shader to swap the sub-pixels during rendering.