【问题标题】:Can I copy this array any faster?我可以更快地复制这个数组吗?
【发布时间】:2011-07-07 00:15:02
【问题描述】:

这是绝对最快的我可以可能Bitmap复制到C#中的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);

【问题讨论】:

  • 不要使用WIDTH * HEIGHT * 4来计算位图的大小。使用bitmapData.Stride * HEIGHT。即使行有填充,它也可以工作。
  • 我知道,这只是一个例子。不过谢谢:)
  • 你知道实际上有一个位图构造函数可以让你为位图提供自己的缓冲区吗?这可能是一个固定的托管数组。
  • @jdv-Jan de Vaan -- 是的,但我打算走另一条路。我有一个位图,需要一个字节数组。

标签: c# optimization bitmap copy bytearray


【解决方案1】:

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

请注意,分析很棘手,第一次访问位图数据会导致页面错误以将像素数据放入内存。这需要多长时间很大程度上取决于您的硬盘正在做什么以及文件系统缓存的状态。

【讨论】:

  • 在我的测试中,P/Invoke RtlMoveMemory 版本的性能仍然比 Marshal.Copy() 好很多倍。我同意对这样的事情进行分析很棘手,但我不会说有令人信服的证据可以切换到使用Marshal.Copy()。无论如何感谢您的回答,会 +1 但由于某种原因它不会让我:\
【解决方案2】:

我正在阅读您正在寻找对位图进行像素操作的内容。 因此,从逻辑上讲,您希望将像素作为数组访问以执行此操作。

你所面临的问题存在根本性的缺陷,而其他人并没有意识到这一点——也许这也是他们可以解决的问题?

基本上,到目前为止,您已经弄乱了位图中的指针,我将告诉您我来之不易的“秘密”之一。

您不需要将位图复制到数组中。照原样使用它。

在这种情况下,不安全的指针是你的朋友。当您点击“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();
            }
        });            
    }

【讨论】:

    【解决方案3】:

    我也会看看System.Buffer.BlockCopy
    此功能也非常快,并且在此设置中可能具有竞争力,因为您正在从托管复制到托管。很遗憾,我无法提供一些性能测试。

    【讨论】:

    • Buffer.BlockCopy(Array, int, Array, int, int) 在这里无法正常工作,即使我正在复制管理。对于Bitmap 数据,我所拥有的只是内部数据开头的IntPtr。谢谢你的建议:)
    【解决方案4】:

    我觉得这样更快(我实际使用过):

    // 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;
    }
    

    Original Source

    【讨论】:

    • 说真的,您将如何处理带有 JPEG 的字节数组?再次解码JPEG以获得实际像素?哦,您知道 JPEG 是一种有损格式吗?
    • 你的函数:0.00064 ms > 原文:0.0000251 ms -- 意思是这个函数大约慢了 25 倍
    • 这是最安全的选择,但也是最慢的。
    猜你喜欢
    • 1970-01-01
    • 2017-11-06
    • 1970-01-01
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    • 2011-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多