Universal Apps 中的 WriteableBitmap





5.00/5 (4投票s)
关于如何在 C# 中使用 Universal Apps 的 WriteableBitmap 的小技巧
引言
在 .NET 中,从位图读取和写入像素/颜色信息似乎是最不稳定的事情。
Windows Forms 以一种非常特殊的方式进行操作。 WPF 的操作方式不同。 Silverlight 的操作方式也不同(取决于您如何看待),最后,Universal Apps,它们主要基于 WPF 和 Silverlight 的原则,又以不同的方式进行操作。
如果您想知道,我想将我之前的一篇文章(Managed Bitmaps 2)中已经介绍的 Simple Life Simulation 应用程序写成一个 Universal App。
我寻找了 WriteableBitmap
类,因为这是该应用程序的核心。 我已经知道它存在于 Universal apps 中,并且它实际上与 Silverlight 和 WPF 中使用的类同名。 所以它应该很容易。 然后,我在 MSDN 的文档中读到了这个
- "要从 C# 或 Microsoft Visual Basic 访问像素内容,您可以使用 AsStream 扩展方法将底层缓冲区作为流访问。"
那一刻我在想“他们这次又做了什么?!?”
然后我读了下一段
- "要从 C++ 访问像素内容,您可以查询 IBufferByteAccess 类型(在 Robuffer.h 中定义)并直接访问其 Buffer 属性。"
事实上,这就是我一开始期望读到的。 但是,这个解决方案仅限于 C++ 吗? 为什么?
为什么我不能使用 C# 来做同样的事情?
答案
现在我可以告诉您我反应过度了。 但文档非常简略,我以为我只能通过使用 C++ 来使用 IBufferByteAccess
接口。 我只是不知道为什么它没有被直接公开,但 C# 可以 直接访问字节缓冲区/Bgra 缓冲区。 它需要使用 unsafe
代码,但完全可行,这正是我一开始所期望的。
要做到这一点,我们所需要做的就是导入 C# 中的 IBufferByteAccess
,当然,使用 unsafe
代码来访问缓冲区。
接口
在 C# 中,您需要像这样导入接口
[ComImport]
[Guid("905a0fef-bc53-11df-8c49-001e4fc686da")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal unsafe interface IBufferByteAccess
{
byte* Buffer { get; }
}
这里最重要的事情不是接口本身,而是接口上使用的所有属性。 如果您想知道,我实际上是从 C++ 头文件中得到的。
.NET 借助这些属性完成了将接口分派给 COM 的工作。 因此,不要认为您可以避免它们。
像素数据
然后,考虑到 WriteableBitmap
始终为 BGRA 格式(其中每个像素为 8 位,即一个字节,以该特定顺序排列,并且我从 MSDN 示例中的注释中发现了这一点),我编写了这个结构来表示一个像素/颜色
[StructLayout(LayoutKind.Sequential)]
public struct Bgra
{
public byte B;
public byte G;
public byte R;
public byte A;
}
事实上,我认为我可以将 Buffer
属性的返回类型从 byte *
更改为 Bgra *
而没有问题,但我更喜欢保持接口不变(因为它在 C++ 中),然后自己进行转换。
另一个让我恼火的事情是,BGRA 格式在 WPF 的 PixelFormats
中被称为 Bgra32
(这意味着整个 BGRA 使用 32 位),而在 BitmapPixelFormat
中被称为 Bgra8
,这意味着每个通道都是 8 位。 最终,它们指的是完全相同的格式,但其中一个中的 8 与每个通道相关,而另一个中的 32 与所有 4 个通道的总和相关。 你不觉得很困惑吗?
使用方法
最后,我们开始使用它。
所以,考虑到我们有一个 writeableBitmap
变量,类型为 WriteableBitmap
,我们需要执行以下操作才能以我们可以操作的格式获取实际缓冲区
unsafe
{
var pixelBuffer = writeableBitmap.PixelBuffer;
var bufferByteAccess = (IBufferByteAccess)pixelBuffer;
var pixels = (Bgra *)bufferByteAccess.Buffer;
// remaining of the code here
然后,就是选择我们要操作的像素的问题。 例如,要访问单个像素,我们使用公式 (y*bitmapWidth) + x。 也就是说:pixels[(y*bitmap.PixelWidth) + x]
。
但是,如果我们想访问所有像素(无论是读取还是写入),我们可以这样做
for(int i=0; i<width*height; i++)
{
// Do some pixel read/manipulation here by using pixels[i]
}
结论
我知道,对于一篇文章来说这有点短,但我只是想表明,最直接的解决方案,即 WPF 中可能使用的解决方案,以及 C++ 中使用的解决方案,都可以在 C# 中使用。
请注意,如果您没有这种限制,请不要将 WriteableBitmap
用作 Stream
。 在某些情况下,它可能更安全,但会使一切变慢,更不适合多线程(例如,在光线追踪的情况下),并且也更难理解。
简单生命模拟
现在,您可以通过此链接在商店中找到 Simple Life Simulation 应用程序