wpf 编辑 WriteableBitmap 的原始像素数据?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20181132/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Edit raw pixel data of WriteableBitmap?
提问by Oztaco - Reinstate Monica C.
Is it possible to directly read/write to a WriteableBitmap's pixel data? I'm currently using WriteableBitmapEx's SetPixel()but it's slow and I want to access the pixels directly without any overhead.
是否可以直接读取/写入 WriteableBitmap 的像素数据?我目前正在使用 WriteableBitmapEx,SetPixel()但它很慢,我想直接访问像素而没有任何开销。
I haven't used HTML5's canvas in a while, but if I recall correctly you could get its image data as a single array of numbers and that's kind of what I'm looking for
我已经有一段时间没有使用 HTML5 的画布了,但是如果我没记错的话,您可以将其图像数据作为单个数字数组获取,这就是我正在寻找的
Thanks in advance
提前致谢
回答by Mitch
To answer your question, you can more directly access a writable bitmap's data by using the Lock, write, Unlockpattern, as demonstrated below, but it is typically not necessary unless you are basing your drawing upon the contents of the image. More typically, you can just create a new buffer and make it a bitmap, rather than the other way around.
要回答您的问题,您可以使用Lock, write,Unlock模式更直接地访问可写位图的数据,如下所示,但通常没有必要,除非您的绘图基于图像的内容。更典型的是,您可以只创建一个新缓冲区并使其成为位图,而不是相反。
That being said, there are many extensibility points in WPF to perform innovative drawing without resorting to pixel manipulation. For most controls, the existing WPF primitives(Border, Line, Rectangle, Image, etc...) are more than sufficient - don't be concerned about using many of them, they are rather cheap to use. For complex controls, you can use the DrawingContextto draw D3D primitives. For image effects, you can implement GPU assisted shadersusing the Effectclassor use the built in effects (Blur and Shadow).
话虽如此,WPF 中有许多可扩展点,可以在不诉诸像素操作的情况下执行创新绘图。对于大多数控件,现有的WPF 基元(边框、线条、矩形、图像等)已经绰绰有余了 - 不要担心使用其中的很多,它们使用起来相当便宜。对于复杂的控件,您可以使用DrawingContext来绘制 D3D 图元。对于图像效果,您可以使用该类或使用内置效果(模糊和阴影)实现 GPU 辅助着色器。Effect
But, if your situation requires direct pixel access, pick a pixel formatand start writing. I suggest BGRA32 because it is easy to understand and is probably the most common one to be discussed.
但是,如果您的情况需要直接访问像素,请选择像素格式并开始编写。我建议使用 BGRA32,因为它易于理解并且可能是最常讨论的一种。
BGRA32 means the pixel data is stored in memory as 4 bytes representing the blue, green, red, and alpha channels of an image, in that order. It is convenient because each pixel ends up on a 4 byte boundary, lending it to storage in an 32 bit integer. When dealing with a 32 bit integer, keep in mind the order will be reversed on most platforms (check BitConverter.IsLittleEndianto determine proper byte order at runtime if you need to support multiple platforms, x86 and x86_64 are both little endian)
BGRA32 表示像素数据以 4 个字节的形式存储在内存中,代表图像的蓝色、绿色、红色和 Alpha 通道,按此顺序。这很方便,因为每个像素都以 4 字节边界结束,以 32 位整数形式存储。处理 32 位整数时,请记住在大多数平台上顺序会颠倒(BitConverter.IsLittleEndian如果您需要支持多个平台,请在运行时检查以确定正确的字节顺序,x86 和 x86_64 都是小端)
The image data is stored in horizontal strips which are one stridewide which compose a single row the width of an image. The stridewidth is always greater than or equal to the pixel width of the image multiplied by the number of bytes per pixel in the format selected. Certain situations can cause the stride to be longer than the width * bytesPerPixelwhich are specific to certain architechtures, so you must use the stride width to calculate the start of a row, rather than multiplying the width. Since we are using a 4 byte wide pixel format, our stride does happen to be width * 4, but you should not rely upon it.
图像数据存储在一个stride宽度的水平条带中,这些条带构成一个图像宽度的单行。的stride宽度总是大于或等于所述图像乘以在选择的格式每像素的字节数的像素宽度。某些情况可能会导致步幅比width * bytesPerPixel某些特定架构所特有的更长,因此您必须使用步幅宽度来计算行的开头,而不是乘以宽度。由于我们使用的是 4 字节宽的像素格式,因此我们的步幅恰好是width * 4,但您不应该依赖它。
As mentioned, the only case I would suggest using a WritableBitmap is if you are accessing an existing image, so that is the example below:
如前所述,我建议使用 WritableBitmap 的唯一情况是如果您正在访问现有图像,那就是下面的示例:
Before / After:
之前/之后:


// 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();
However, it really isn't necessary if you are not modifying an existing image. For example, you can draw a checkerboard pattern using all safe code:
但是,如果您不修改现有图像,则实际上没有必要。例如,您可以使用所有安全代码绘制棋盘格图案:
Output:
输出:


// 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);
And you don't really even need an Int32 intermediate, if you write to the byte array directly.
如果您直接写入字节数组,您甚至不需要 Int32 中间体。
Output:
输出:


// 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);
Edit:apparently, you are trying to use WPF to make some sort of image editor. I would still be using WPF primitives for shapes and source bitmaps, and then implement translations, scaling, rotation as RenderTransform's, bitmap effects as Effect's and keep everything within the WPF model. But, if that does not work for you, we have many other options.
编辑:显然,您正在尝试使用 WPF 来制作某种图像编辑器。我仍然会为形状和源位图使用 WPF 基元,然后实现平移、缩放、旋转为RenderTransforms、位图效果为Effects 并将所有内容保留在 WPF 模型中。但是,如果这对您不起作用,我们还有许多其他选择。
You could use WPF primitives to render to a RenderTargetBitmapwhich has a chosen PixelFormatto use with WritableBitmapas below:
您可以使用 WPF 基元渲染到RenderTargetBitmap一个选择PixelFormat使用的对象,WritableBitmap如下所示:
Canvas cvRoot = new Canvas();
// position primitives on canvas
var rtb = new RenderTargetBitmap(width, height, dpix, dpiy, PixelFormats.Bgra32);
var wb = new WritableBitmap(rtb);
You could use a WPF DrawingVisualto issue GDI style commands then render to a bitmap as demonstrated on the sample on the RenderTargetBitmappage.
您可以使用 WPFDrawingVisual发出 GDI 样式命令,然后呈现为位图,如RenderTargetBitmap页面上的示例所示。
You could use GDI using an InteropBitmapcreated using System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmapfrom an HBITMAPretrieved from a Bitmap.GetHBitmapmethod. Make sure you don't leak the HBITMAP, though.
你可以使用GDI使用InteropBitmap使用创建System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap从一个HBITMAP从检索Bitmap.GetHBitmap方法。不过,请确保您不会泄漏HBITMAP.
回答by Oztaco - Reinstate Monica C.
After a nice long headache, I found this articlethat explains a way to do it without using bit arithmetic, and allows me to treat it as an array instead:
经过长时间的头痛,我发现这篇文章解释了一种不使用位算术的方法,并允许我将其视为数组:
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;
}
回答by Florian
You can access the raw pixel data by calling the Lock()method and using the BackBufferproperty afterwards. When you're finished, don't forget to call AddDirtyRectand Unlock.
For a simple example, you can take a look at this: http://cscore.codeplex.com/SourceControl/latest#CSCore.Visualization/WPF/Utils/PixelManipulationBitmap.cs
您可以通过调用该Lock()方法并在BackBuffer之后使用该属性来访问原始像素数据。完成后,不要忘记调用AddDirtyRect和Unlock。举个简单的例子,你可以看看这个:http: //cscore.codeplex.com/SourceControl/latest#CSCore.Visualization/WPF/Utils/PixelManipulationBitmap.cs

