编辑WriteableBitmap的原始像素数据?

是否可以直接读/写WriteableBitmap的像素数据? 我目前正在使用WriteableBitmapEx的SetPixel()但它很慢,我想直接访问像素而没有任何开销。

我有一段时间没有使用过HTML5的canvas,但是如果我没记错的话,你可以将它的图像数据作为单个数字数组获得,这就是我正在寻找的东西

提前致谢

要回答您的问题,您可以使用Lock ,写入, Unlock模式更直接地访问可写位图的数据,如下所示,但除非您基于图像内容绘图,否则通常不需要。 更典型的是,您可以创建一个新缓冲区并使其成为位图,而不是相反。

话虽如此,WPF中有许多可扩展点来执行创新绘图而无需借助像素操作。 对于大多数控件,现有的WPF基元 (Border,Line,Rectangle,Image等)绰绰有余 – 不要担心使用它们中的许多,它们使用起来相当便宜。 对于复杂控件,可以使用DrawingContext绘制D3D基元。 对于图像效果,您可以使用Effect类 实现GPU辅助着色器或使用内置效果(模糊和阴影)。

但是,如果您的情况需要直接像素访问,请选择像素格式并开始写入。 我建议BGRA32,因为它很容易理解,可能是最常见的讨论。

BGRA32表示像素数据以4个字节的forms存储在存储器中,表示图像的蓝色,绿色,红色和alpha通道。 这很方便,因为每个像素最终都在4字节边界上,使其以32位整数存储。 处理32位整数时,请记住在大多数平台上顺序都是相反的(如果需要支持多个平台,请检查BitConverter.IsLittleEndian以确定运行时的正确字节顺序,x86和x86_64都是小端)

图像数据存储在水平条带中,水平条带是一个stride幅宽,其构成图像宽度的单行。 stride宽度始终大于或等于图像的像素宽度乘以所选格式的每像素的字节数。 某些情况可能导致步幅长于某些架构特定的width * bytesPerPixel ,因此您必须使用步幅宽度来计算行的开始,而不是乘以宽度。 由于我们使用的是4字节宽的像素格式,我们的步幅恰好是width * 4 ,但您不应该依赖它。

如上所述,我建议使用WritableBitmap的唯一情况是,如果您正在访问现有图像,那么这是以下示例:

之前/之后:

图像贯穿以下算法

 // must be compiled with /UNSAFE // get an image to draw on and convert it to our chosen format BitmapSource srcImage = JpegBitmapDecoder.Create(File.Open("img13.jpg", FileMode.Open), BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames[0]; if (srcImage.Format != PixelFormats.Bgra32) srcImage = new FormatConvertedBitmap(srcImage, PixelFormats.Bgra32, null, 0); // get a writable bitmap of that image var wbitmap = new WriteableBitmap(srcImage); int width = wbitmap.PixelWidth; int height = wbitmap.PixelHeight; int stride = wbitmap.BackBufferStride; int bytesPerPixel = (wbitmap.Format.BitsPerPixel + 7) / 8; wbitmap.Lock(); byte* pImgData = (byte*)wbitmap.BackBuffer; // set alpha to transparent for any pixel with red < 0x88 and invert others int cRowStart = 0; int cColStart = 0; for (int row = 0; row < height; row++) { cColStart = cRowStart; for (int col = 0; col < width; col++) { byte* bPixel = pImgData + cColStart; UInt32* iPixel = (UInt32*)bPixel; if (bPixel[2 /* bgRa */] < 0x44) { // set to 50% transparent bPixel[3 /* bgrA */] = 0x7f; } else { // invert but maintain alpha *iPixel = *iPixel ^ 0x00ffffff; } cColStart += bytesPerPixel; } cRowStart += stride; } wbitmap.Unlock(); // if you are going across threads, you will need to additionally freeze the source wbitmap.Freeze(); 

但是,如果您不修改现有图像,则没有必要。 例如,您可以使用所有安全代码绘制棋盘图案:

输出:

25%缩放的棋盘图案

 // draw rectangles int width = 640, height = 480, bytesperpixel = 4; int stride = width * bytesperpixel; byte[] imgdata = new byte[width * height * bytesperpixel]; int rectDim = 40; UInt32 darkcolorPixel = 0xffaaaaaa; UInt32 lightColorPixel = 0xffeeeeee; UInt32[] intPixelData = new UInt32[width * height]; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { intPixelData[row * width + col] = ((col / rectDim) % 2) != ((row / rectDim) % 2) ? lightColorPixel : darkcolorPixel; } } Buffer.BlockCopy(intPixelData, 0, imgdata, 0, imgdata.Length); // compose the BitmapImage var bsCheckerboard = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, imgdata, stride); 

如果你直接写入字节数组,你甚至不需要Int32中间体。

输出:

从下面生产的梯度

 // draw using byte array int width = 640, height = 480, bytesperpixel = 4; int stride = width * bytesperpixel; byte[] imgdata = new byte[width * height * bytesperpixel]; // draw a gradient from red to green from top to bottom (R00 -> ff; Gff -> 00) // draw a gradient of alpha from left to right // Blue constant at 00 for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { // BGRA imgdata[row * stride + col * 4 + 0] = 0; imgdata[row * stride + col * 4 + 1] = Convert.ToByte((1 - (col / (float)width)) * 0xff); imgdata[row * stride + col * 4 + 2] = Convert.ToByte((col / (float)width) * 0xff); imgdata[row * stride + col * 4 + 3] = Convert.ToByte((row / (float)height) * 0xff); } } var gradient = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, imgdata, stride); 

编辑:显然,您正在尝试使用WPF来制作某种图像编辑器。 我仍然会将WPF基元用于形状和源位图,然后将RenderTransform的转换,缩放,旋转,效果的位图效果实现,并将所有内容保留在WPF模型中。 但是,如果这对您不起作用,我们还有很多其他选择。

您可以使用WPF基元渲染到RenderTargetBitmap ,它具有与WritableBitmap一起使用的选定PixelFormat ,如下所示:

 Canvas cvRoot = new Canvas(); // position primitives on canvas var rtb = new RenderTargetBitmap(width, height, dpix, dpiy, PixelFormats.Bgra32); var wb = new WritableBitmap(rtb); 

您可以使用WPF DrawingVisual发出GDI样式命令,然后渲染到位图,如RenderTargetBitmap页面上的示例所示。

您可以使用使用从Bitmap.GetHBitmap方法检索的HBITMAP System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap创建的InteropBitmap来使用GDI。 但请确保您没有泄漏HBITMAP

您可以通过调用Lock()方法并在之后使用BackBuffer属性来访问原始像素数据。 完成后,不要忘记调用AddDirtyRectUnlock 。 举个简单的例子,您可以看一下: http : //cscore.codeplex.com/SourceControl/latest#CSCore.Visualization/WPF/Utils/PixelManipulationBitmap.cs

经过长时间的头痛,我发现这篇文章解释了一种不使用位算术的方法,并允许我将其视为一个数组:

 unsafe { IntPtr pBackBuffer = bitmap.BackBuffer; byte* pBuff = (byte*)pBackBuffer.ToPointer(); pBuff[4 * x + (y * bitmap.BackBufferStride)] = 255; pBuff[4 * x + (y * bitmap.BackBufferStride) + 1] = 255; pBuff[4 * x + (y * bitmap.BackBufferStride) + 2] = 255; pBuff[4 * x + (y * bitmap.BackBufferStride) + 3] = 255; }