【问题标题】:How do I calculate the proper size for the underlying buffer of System.Drawing.Bitmap?如何计算 System.Drawing.Bitmap 底层缓冲区的正确大小?
【发布时间】:2017-03-17 10:41:43
【问题描述】:

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

使用the technique described in this answer,我正在分配一个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);
        }
    }
}

【问题讨论】:

  • @PeterDuniho 感谢您提供的信息,我会检查这些内容。我不认为我的步幅计算有误,那是BytesPerPixel,而不是Bits。并感谢链接到 MVCE。这是一个完整的例子,我只是不知道没有原始图像就不一定可以重现。
  • var stride = bitsPerPixel * _width 其中var bitsPerPixel = 32。错了。
  • 啊,我现在明白了!对不起..
  • @PeterDuniho 你说得对,步幅是我的问题。我在评论中非常确定我使用的是BytesPerPixel。如果您想将其发布为答案,我会接受。

标签: c# .net bitmap


【解决方案1】:

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

还要注意,步幅是字节,而不是位,所以无论如何你的计算都是错误的。您告诉Bitmap 类,它的步幅为32 * width,是应有的8 倍,因此GDI+ 可能早在图像的1/8 处就会耗尽有效内存地址(如果你幸运的话……如果你不幸运,它会将随机数据写入某个重要的地方)。

下面是计算的样子:

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

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

【讨论】:

  • 计算中不需要四边形,它们相互抵消。谢谢!
  • @Steve:你错了。四位绝对是必需的,并且由于 C#(以及几乎所有其他主流编程语言)中整数除法的工作方式,它们不会相互抵消。自己尝试一些例子。如果_width 是1,bytesPerPixel 是3,那么4 * ((_width * bytesPerPixel + 3) / 4) 的值是多少? _width * bytesPerPixel + 3 的值是多少?我向你保证,两个表达式的结果是不一样的,只有第一个是正确的。
猜你喜欢
  • 2011-01-25
  • 2020-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多