如何计算 System.Drawing.Bitmap 的基础缓冲区的适当大小?



我正在使用System.Drawing.Bitmap,需要访问单个像素,但GetPixel()SetPixel()对我来说太慢了。

使用此答案中描述的技术,我正在分配一个byte[],将其固定在内存中,将我的原始Bitmap复制到其中,然后取消固定它。然后我使用byte[],然后固定它,用缓冲区构造一个新Bitmap,然后保存它。

我计算缓冲区的大小(以字节为单位)为width * height * BytesPerPixel.我正在使用PixelFormat.Format32bppArgb,所以BytesPerPixel == 4.

这一切都工作正常,除了我显然分配了一个太小的缓冲区并在复制位图时导致访问冲突。我必须添加额外的空间(很多)以避免访问违规,一切似乎都正常。

如何计算_buffer所需的合适尺寸?

编辑:我刚刚发现原始图像是PixelFormat.Format24bppRgb。但是,这是每个像素 3 个字节,我认为我的缓冲区应该足够大。

这是一个完整的程序来演示问题。ExtraSpace控制我必须分配的额外字节...:

using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace FastImageTest
{
class FastImage
{
private const int BytesPerPixel = 4;
private readonly byte[] _buffer;
private readonly int _width;
private readonly int _height;
private const int ExtraSpace = 0;// 1024 * 1024 * 256;
public FastImage(Bitmap from)
{
_width = from.Width;
_height = from.Height;
_buffer = new byte[_width * _height * BytesPerPixel + ExtraSpace];
GCHandle handle = GCHandle.Alloc(_buffer);
try
{
var address = Marshal.UnsafeAddrOfPinnedArrayElement(_buffer, 0);
var bitsPerPixel = 32;
var stride = bitsPerPixel * _width;
var pixelFormat = PixelFormat.Format32bppArgb;
//***** Access violation occurs on next line
using (var fastBitmap = new Bitmap(_width, _height, stride, pixelFormat, address))
{
using (var fastBitmapGraphics = Graphics.FromImage(fastBitmap))
fastBitmapGraphics.DrawImageUnscaled(from, 0, 0);
}
}
finally
{
handle.Free();
}
}
}
class Program
{
static void Main(string[] args)
{
var original = new Bitmap(@"d:pic.jpg");
var fast = new FastImage(original);
}
}
}

请记住,Windows 位图对象要求步幅与 DWORD(4 字节)对齐。 宽度不是 4 倍的 24 位图像的步幅将不同于 4 * 宽度。

另请注意,步幅以字节为单位,而不是位,因此您的计算在任何情况下都是错误的。您告诉Bitmap类它有32 * width步幅,这是应有的 8 倍,因此 GDI+ 可能会在图像的 1/8 时耗尽有效内存地址(如果你幸运的话......如果你不是,它会将随机数据写入重要的地方)。

计算应如下所示:

int stride = 4 * ((_width * bytesPerPixel + 3) / 4);

注意bytesPerPixel而不是bitsPerPixel。确保这是根据位图格式正确计算的,而不仅仅是设置为 4 的 const。

最新更新