我有一个包含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);
我绝对不想在我的字节数组中交换像素。
答案 0 :(得分:7)
在深入研究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<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; }
}
}
正如评论中所指出的,您可以通过实施不安全的复制操作来提高性能,这可能如下所示:
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;
}
}
}
}
答案 1 :(得分:2)
你可以:
一个。分配一个新缓冲区并将子像素复制到其中,同时交换通道(using unsafe code)并使用缓冲区创建一个BitmapSource。 (Using an array as a source或Using an IntPtr as a source。)
或
湾按原样显示图像(使用错误的通道顺序),并在渲染过程中使用HLSL shader交换子像素。
答案 2 :(得分:2)
我无法看到你如何在不进行交换的情况下到达那里。这个例子完成了这项工作,但不是直接改变字节缓冲区,而是将每个32位字的R和B值交换为位图的后备缓冲区。由于Windows是little-endian,因此比特移位看起来反直觉,因为取消引用的RGBA字读作ABGR。但是,就整体表现而言,这可能是你最好的选择。
我刚刚对您在帖子中使用的相同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;