是否可以直接读取/写入 WriteableBitmap 的像素数据?我目前正在使用 WriteableBitmapEx 的 SetPixel()
,但速度很慢,我想直接访问像素而不需要任何开销。
我已经有一段时间没有使用 HTML5 的 Canvas 了,但是如果我没记错的话,您可以将其图像数据作为单个数字数组获取,而这正是我正在寻找的东西
提前致谢
最佳答案
要回答您的问题,您可以使用 Lock
, write, Unlock
模式更直接地访问可写位图的数据,如下所示,但通常不是除非您根据图像的内容进行绘图,否则这是必需的。更典型的是,您可以只创建一个新缓冲区并将其设为位图,而不是相反。
话虽这么说,WPF 中有许多扩展点可以在不借助像素操作的情况下执行创新绘图。对于大多数控件,现有的 WPF primitives (边框、线、矩形、图像等...)绰绰有余 - 不要担心使用它们中的许多,它们使用起来相当便宜。对于复杂的控件,您可以使用 DrawingContext绘制 D3D 图元。图片效果可以implement GPU assisted shaders使用 Effect
class或使用内置效果(模糊和阴影)。
但是,如果您的情况需要直接访问像素,请选择 pixel format并开始写作。我建议使用 BGRA32,因为它易于理解并且可能是最常讨论的一种。
BGRA32 表示像素数据以 4 个字节的形式存储在内存中,依次表示图像的蓝色、绿色、红色和 alpha channel 。这很方便,因为每个像素都在 4 字节边界上结束,借以以 32 位整数存储。处理 32 位整数时,请记住在大多数平台上顺序会颠倒(如果您需要支持多个平台,请检查 BitConverter.IsLittleEndian
以确定运行时正确的字节顺序,x86 和 x86_64 都是小端)
图像数据存储在水平 strip 中,水平 strip 宽度为一个 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();
但是,如果您不修改现有图像,则确实没有必要。例如,您可以使用所有安全代码绘制棋盘图案:
输出:
// 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
,将位图效果实现为 Effect
,并将所有内容保留在其中WPF模型。但是,如果这对您不起作用,我们还有许多其他选择。
您可以使用 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);
您可以使用 WPF DrawingVisual
发出 GDI 样式命令,然后呈现为位图,如 RenderTargetBitmap
页面上的示例所示。
您可以使用 InteropBitmap
使用 GDI,该 System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap
来自 HBITMAP
从 检索>Bitmap.GetHBitmap
方法。不过,请确保您没有泄露 HBITMAP
。
关于c# - 编辑 WriteableBitmap 的原始像素数据?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20181132/