从内存中显示RGBA图像

我有一个包含RGBA编码图像的C#字节数组。 在WPF中显示此图像的最佳方法是什么?

一种选择是从字节数组创建一个BitmapSource并将其附加到Image控件。 但是,创建BitmapSource需要一个用于RGBA32的PixelFormat,这在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); 

我绝对不想在我的字节数组中交换像素。

在深入研究WIC的细节之前,您可以考虑将字节交换封装在一个简单的自定义BitmapSource中,如下所示。 它采用RGBA字节数组并在重写的CopyPixels方法中交换像素字节以生成PBGRA缓冲区。

您可以通过提供RGBA缓冲区和位图宽度来简单地创建此自定义BitmapSource的实例

 var buffer = new byte[] { 25, 166, 0, 255, 90, 0, 120, 255 }; var bitmapSource = new RgbaBitmapSource(buffer, 2); 

这是实施:

 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 DownloadProgress; public override event EventHandler DownloadCompleted; public override event EventHandler DownloadFailed; public override event EventHandler 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; } } } 

正如评论中指出的那样,您可以通过实施不安全的复制操作来提高性能,这可能如下所示:

 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; } } } } 

你可以:

一个。 分配一个新的缓冲区并将子像素复制到其中,同时交换通道( 使用不安全的代码 )并使用缓冲区创建一个BitmapSource。 ( 使用数组作为源或使用IntPtr作为源 。)

要么

湾 按原样显示图像(使用错误的通道顺序)并使用HLSL着色器在渲染期间交换子像素。

如果不进行交换,我无法看到你将如何到达那里。 这个例子完成了这项工作,但不是直接改变字节缓冲区,而是将每个32位字的R和B值交换为位图的后备缓冲区。 由于Windows是little-endian,因此比特移位看起来非常直观,因为取消引用的RGBA字读作ABGR。 但是,就整体表现而言,这可能是你最好的选择。

我刚刚对您在post中使用的相同2px示例进行了硬编码。 我没有尝试过扩展它。 我只是想提出一种不同的方法来交换字节。 我希望这有帮助。

  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;