我能更快地复制这个数组吗?

6

这是我在C#中将Bitmap复制到Byte[]绝对最快方式吗?

如果有更快的方法,请务必告诉我!

const int WIDTH = /* width */;
const int HEIGHT = /* height */;


Bitmap bitmap = new Bitmap(WIDTH, HEIGHT, PixelFormat.Format32bppRgb);
Byte[] bytes = new byte[WIDTH * HEIGHT * 4];

BitmapToByteArray(bitmap, bytes);


private unsafe void BitmapToByteArray(Bitmap bitmap, Byte[] bytes)
{
    BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, WIDTH, HEIGHT), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);

    fixed(byte* pBytes = &bytes[0])
    {
        MoveMemory(pBytes, bitmapData.Scan0.ToPointer(), WIDTH * HEIGHT * 4);
    }

    bitmap.UnlockBits(bitmapData);
}

[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
private static unsafe extern void MoveMemory(void* dest, void* src, int size);

6
不要使用 WIDTH * HEIGHT * 4 来计算位图的大小。请使用 bitmapData.Stride * HEIGHT。即使行有填充,这也可以正常工作。 - R. Martinho Fernandes
我知道,这只是一个例子。不过还是谢谢 :) - tbridge
1
你知道其实有一个Bitmap构造函数可以让你提供自己的位图缓冲区吗?这可以是一个固定的托管数组。 - user180326
@jdv-Jan de Vaan -- 是的,但我是在反过来做。我有一个位图,需要一个字节数组。 - tbridge
4个回答

7

在这里,最好使用Marshal.Copy(),这至少可以减少查找DLL导出的(一次性)成本。但是,它们都使用C运行时memcpy()函数。速度完全受到RAM总线带宽的限制,只有购买更昂贵的机器才能加速。

请注意,分析是棘手的,第一次访问位图数据会导致页面故障,以将像素数据加载到内存中。这需要多长时间严重依赖于硬盘正在做什么以及文件系统缓存的状态。


1
在我的测试中,P/Invoke的RtlMoveMemory版本仍然比Marshal.Copy()快很多。我同意对于这种情况进行分析是有难度的,但我不认为有充分的证据来支持使用Marshal.Copy()。无论如何,感谢您的回答,但由于某些原因,我无法点赞。 - tbridge

4
我理解您想要对位图进行像素操作。因此,逻辑上,您希望以数组的形式访问像素以进行操作。
您面临的问题基本上是有缺陷的,其他人可能没有注意到这一点-也许这是他们可以学习的东西?
基本上,您已经深入涉足位图指针的操作,我将给您一个我辛苦赚来的“秘密”。
您不需要将位图复制到数组中。只需使用它即可。
在这种情况下,不安全指针是您的好朋友。当您点击“bitmapData.Scan0.ToPointer()”时,您错过了技巧。
只要您有第一个像素的指针、步幅和知道每个像素的字节数,那么您就是赢家。
我专门定义了一个具有32位每像素的位图(UInt32访问的内存效率),并获取了第一个像素的指针。然后,我将指针视为数组,并可以读取和写入像素数据作为UInt32值。
代码胜过言语,请看看。
我敬礼您能走到这一步!
以下是未经测试的代码,但很多内容都是从我的源代码复制而来。
public delegate void BitmapWork(UInt32* ptr, int stride);

    /// <summary>
    /// you don't want to forget to unlock a bitmap do you?  I've made that mistake too many times...
    /// </summary>
    unsafe private void UnlockBitmapAndDoWork(Bitmap bmp, BitmapWork work)
    {
        var s = new Rectangle (0, 0, bmp.Width, bmp.Height); 
        var locker = bmp.LockBits(new Rectangle(0, 0, 320, 200), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

        var ptr = (UInt32*)locker.Scan0.ToPointer();

        // so many times I've forgotten the stride is expressed in bytes, but I'm accessing with UInt32's.  So, divide by 4.
        var stride = locker.Stride / 4;

        work(ptr, stride);
        bmp.UnlockBits(locker);
    }

    //using System.Drawing.Imaging;
    unsafe private void randomPixels()
    {
        Random r = new Random(DateTime.Now.Millisecond);

        // 32 bits per pixel.  You might need to concern youself with the Alpha part depending on your use of the bitmap
        Bitmap bmp = new Bitmap(300, 200, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        UnlockBitmapAndDoWork(bmp, (ptr, stride) => 
        {
            var calcLength = 300 * 200; // so we only have one loop, not two.  It's quicker!
            for (int i = 0; i < calcLength; i++)
            {
                // You can use the pointer like an array.  But there's no bounds checking.
                ptr[i] = (UInt32)r.Next();
            }
        });            
    }

1

我也会看一下System.Buffer.BlockCopy。这个函数也非常快,对于你在这种情况下从托管到托管的复制来说可能是有竞争力的。不幸的是,我无法提供一些性能测试。


1
即使我正在从托管到托管进行复制,Buffer.BlockCopy(Array, int, Array, int, int) 在这里也不太适用。对于Bitmap数据,我只有一个指向内部数据开头的IntPtr。感谢您的建议 :) - tbridge

-3

我认为这个更快(我实际上用过它):

// Bitmap bytes have to be created via a direct memory copy of the bitmap
private byte[] BmpToBytes_MemStream (Bitmap bmp)
{
    MemoryStream ms = new MemoryStream();
    // Save to memory using the Jpeg format
    bmp.Save(ms, ImageFormat.Jpeg);

    // read to end
    byte[] bmpBytes = ms.GetBuffer();
    bmp.Dispose();
    ms.Close();

    return bmpBytes;
}

原始来源


说实话,你会用一个包含JPEG的字节数组做什么?再次解码JPEG以获取实际像素吗?哦,你知道JPEG是一种有损格式吗? - R. Martinho Fernandes
1
你的函数:0.00064毫秒 > 原始值:0.0000251毫秒 -- 这意味着这个函数慢了大约25倍。 - tbridge
这是最安全的选择,但也是最慢的。 - Asti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接