【问题标题】:How are Bitmaps stored in memory in .NET?位图如何存储在 .NET 的内存中?
【发布时间】:2012-11-09 19:54:16
【问题描述】:

在 .NET 中,您通常使用 Bitmap 类来存储图像。要快速访问它,您需要调用lock() and unlock() 将位图的内容复制到内存中。那么,是不是说位图在加锁之前没有存储在一个打包的内存数组中呢?

究竟需要锁定什么?即为什么平台不能简单地返回一个指向位图第一个像素的指针并让您直接访问像素? (除了“不安全”的内存访问考虑)

一些可能的原因:

  • 位图以原始压缩形式存储以节省内存(PNG、JPEG 等)
  • 位图以 24-bpp 格式存储,因此访问速度比 32-bpp 图像慢
  • 位图未存储在压缩内存数组中,并且是碎片化的,因此无法快速读取/写入
  • 位图以未公开的方式存储,平台不希望您访问实际的位图数据内存 - 强制您使用 lock() 在内存中创建它的副本

【问题讨论】:

    标签: .net image performance architecture bitmap


    【解决方案1】:

    位图以惰性方式读取。实际使用的算法很大程度上取决于图像格式,.bmp 文件很容易,.jpeg 文件就不那么容易了。在底层,GDI+ 创建一个内存映射文件,在必要时利用操作系统的按需分页虚拟内存映射功能将文件数据映射到 RAM。正是这个 MMF 臭名昭著地在文件上创建了一个锁,并导致您在尝试将图像保存回具有相同名称的文件时遇到异常。

    Bitmap.LockBits() 创建一个单独的缓冲区,将像素数据从 MMF 映射到内存中具有请求的像素格式的区域。现在,您拥有了定义明确的格式的数据,该格式独立于文件中的格式。调用 UnlockBits() 将修改后的数据写回(如果有)。文件中的数据如何与修改后的像素重新组合并没有具体说明,可能很大程度上取决于编解码器。

    当您在其他方法中使用位图时,会发生这种完全相同的锁定,这是看不见的。就像使用 Graphics.DrawImage() 绘制图像一样。当你使用 Bitmap.GetPixel() 时臭名昭著。正如您现在可以猜到的那样,在锁定调用中转换像素数据会产生相当多的开销,原因是 GetPixel() 非常慢,因为您对位图中的每个像素而不是一次执行此操作,因为DrawImage 和 LockBits() 可以。

    还应该清楚,开销在很大程度上取决于图像文件格式、即时解码像素数据的费用、解码器完成的缓存量、图像文件的像素格式文件与您在 LockBits() 中要求的像素格式。要做出可预测的性能猜测,这里发生的事情太多了,您必须对其进行分析。

    【讨论】:

    • 难以置信的信息,汉斯!但是,有一个问题:如果位图必须在某个时间点渲染到屏幕上,将位图“锁定”一次到内存中,然后在内存上执行所有计算并使用内存进行渲染是否有意义? (考虑到我正在编写自己的位图渲染器?)
    • 是的,将位图的像素格式转换为与视频适配器兼容的格式是有意义的。所以像素数据可以直接爆破到显存中,无需转换。在任何 32bppPArgb 的现代适配器上,它比任何其他适配器快 10 倍。
    • 32bpp ARGB 是否是最快的格式,即使在使用 C++ 编写的低级应用程序时也是如此? “ccxvii”表示 BGRA 是视频/显卡最快的原生格式 - gamedev.stackexchange.com/a/44153/15277
    • 不,它是 32bppPArgb。预乘 alpha 很重要,因此在发送到视频适配器时不必应用 alpha 效果。 BGRA 不相关,它不是 Bitmap 类支持的像素格式。否则只是一个字节序的词,小与大。位图已将它们按正确的顺序排列。
    【解决方案2】:

    位图以未公开的方式存储,平台不希望您访问实际的位图数据内存 - 强制您使用 lock() 在内存中创建它的副本

    这个,我想。位图存储在 GDI 中,每次对 GetPixel() 的调用都会在 gdiplus.dll 中变成对 GdipBitmapGetPixel() 的调用。

    Bitmap.LockBits(),在 GDI 中调用 Bitmap::Lockbits,将立即返回 GDI 为给定图像保存的所有像素,作为托管的 BitmapData 对象。

    Bitmap::Lockbits 的文档说:

    锁定该位图的一个矩形部分并提供一个临时缓冲区,您可以使用该缓冲区以指定格式读取或写入像素数据。当您调用 Bitmap::UnlockBits 时,您写入缓冲区的任何像素数据都会复制到 Bitmap 对象。

    在 GDI 文档中,我找不到任何其他方式来访问“原始”位图,所以我认为这是因为 GDI 的设计方式(可能是因为 GDI 将位图存储在(视频?)内存?),所以如果您想对“原始”位图数据进行操作,必须调用LockBits

    【讨论】:

      【解决方案3】:

      为什么平台不能简单地返回一个指向位图第一个像素的指针,让你直接访问像素?

      只是一个想法:所有图像数据(“像素”)都可以存储在视频卡的内存(而不是 CPU 的内存)上。因此,无法从 CPU/应用程序直接访问图像数据。因此,应该有一种特殊的方式来通过请求从 CPU 获取对图像数据的访问。喜欢LockBits/UnockBits

      【讨论】:

        猜你喜欢
        • 2013-03-17
        • 2012-06-01
        • 1970-01-01
        • 2020-02-03
        • 1970-01-01
        • 2023-03-16
        • 1970-01-01
        • 2012-06-02
        • 1970-01-01
        相关资源
        最近更新 更多