7

I have a C# byte array containing an RGBA encoded image. What is the best way to show this image in WPF?

One option would be to create a BitmapSource form the byte array and attach it to an Image control. However, creating a BitmapSource requires a PixelFormat for RGBA32, which doesn't seem to be available in Windows.

byte[] buffer = new byte[] { 25, 166, 0, 255, 90, 0, 120, 255 };
BitmapSource.Create(2, 1, 96d, 96d, PixelFormats.Bgra32, null, buffer, 4 * 2);

I definitely don't want to swap pixels in my byte array.

molf
  • 73,644
  • 13
  • 135
  • 118
Jippe
  • 714
  • 6
  • 14
  • `PixelFormats.Pbgra32` still swaps the red and blue pixels, so that doesn't work. – Jippe Jan 29 '14 at 12:30
  • Sorry, i didn't read carefully enough. Did you read [this question](http://social.msdn.microsoft.com/Forums/en-US/5c6ad447-2027-4685-9ab2-62d418695a07/why-rgba32-in-pixelformats-is-not-supported)? – Clemens Jan 29 '14 at 12:40
  • 1
    I did, and there it seems the format with the friendly name `GUID_WICPixelFormat32bppRGBA` is natively supported. However, how can I make use of that? – Jippe Jan 29 '14 at 12:55
  • @Jippe why don't you want to swap colors and use Bgra32? You can do it with few lines of code, it would take less than typing your question. – Andrey Feb 03 '14 at 16:47
  • 1
    WIC has that format, it just isn't exposed by WPF. Technically you can initialize a PixelFormat by writing the private fields of the struct with Reflection. Quite painful to do however, it isn't a very simple struct. The SecurityCriticalDataForSet<> wrapper for the guid is a pita. – Hans Passant Feb 03 '14 at 19:01
  • @Andrey I don't want to perform an unnecessary loop through the byte array just to swap some bits. – Jippe Feb 04 '14 at 19:18
  • 1
    @Jippe, someone is going to perform a bit swap, whether it is you, WIC or your video card does not make a huge difference – Mitch Feb 04 '14 at 23:17
  • can we use `Bitmap` and make use of `Format32bppPArgb` and convert bitmap to BitmapSource? – Mujahid Daud Khan Feb 05 '14 at 10:00
  • PArgb is ARGB with the RGB pre-multiplied by A/MAX(A). – Danny Varod Feb 05 '14 at 17:47
  • Can you provide a sample byte array and expected image to shown for my test app? – Ranjith Venkatesh Feb 06 '14 at 15:25
  • @Mitch, I'm in the luxurious position to control the code of the library that creates the buffer. So in order to avoid swapping bits, I can just expose an BGRA buffer besides the already existing RGBA buffer. However, this now means I'm polluting our fine library with Windows peculiarities. – Jippe Feb 11 '14 at 07:55
  • @Jippe, what are you working with that uses RGBA? Most little endian machines use `BGRA`, with most big-endian machines using `ARGB`... this is one of those few places where Windows, Linux, Mac OS, DirectX, OpenGL actually agree – Mitch Feb 11 '14 at 13:12
  • @Mitch, we use RGB on OS X. You can specify whether the alpha channel is first or last. However, Windows understands neither. – Jippe Feb 13 '14 at 16:07

3 Answers3

9

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;
            }
        }
    }
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Using an unsafe code section to copy the pixels into the destination buffer would be much more efficient and there are already examples of that online. – Danny Varod Feb 04 '14 at 22:44
  • Better, in a more generic version you shouldn't hard code the number of channels or assume that each sub-pixel is a single Byte. – Danny Varod Feb 06 '14 at 15:54
  • @DannyVarod Sure, but that would create a lot more code here. My intention was not to provide a full-featured solution, but just the direction to go. And the question is exactly about 32bpp RGBA. – Clemens Feb 06 '14 at 17:02
  • True, but then why bother giving an example of copying pixels at all, there are plenty of C# examples available for that. E.g. http://www.codeproject.com/Articles/617613/Fast-Pixel-Operations-in-NET-With-and-Without-unsa http://stackoverflow.com/questions/9319199/how-to-copy-bitmap-pixels-to-other-bitmap-preserving-alpha-transparency-in-c http://www.codeproject.com/Tips/240428/Work-with-bitmap-faster-with-Csharp http://www.codeproject.com/Articles/1989/Image-Processing-for-Dummies-with-C-and-GDI-Part-1 – Danny Varod Feb 06 '14 at 18:33
  • 2
    @DannyVarod You don't get it. The example is about how to encapsulate the whole thing in a specialized BitmapSource. And please stop complaining about other people's answers. Improve your own one instead. – Clemens Feb 07 '14 at 07:54
  • 1
    @Clemens +1 on comment for "Please proceed, Governor" (: – drankin2112 Feb 07 '14 at 18:21
  • This code does not work in .Net 4.6.1. Using the above class causes a Not Implemented Exception with no clue as to what is missing. But, wrapping the above class with WriteableBitmap does work. – stricq Jul 30 '16 at 18:59
2

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;
drankin2112
  • 4,715
  • 1
  • 15
  • 20
1

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.

Danny Varod
  • 17,324
  • 5
  • 69
  • 111