加载位图时出现内存不足错误

5

我正在处理大尺寸的图片(例如,16000x9440像素)并将其切割成其他内容。当创建新的位图实例时,我遇到了“内存不足”的异常:

using (FileStream fileStream = new FileStream(mapFileResized, FileMode.Open))
{
    byte[] data = new byte[fileStream.Length];
    fileStream.Read(data, 0, data.Length);
    using (MemoryStream memoryStream = new MemoryStream(data))
    {
        using (Bitmap src = new Bitmap(memoryStream)) // <-- exception
        {
            tile = new Bitmap(tileWidth, tileHeight, PixelFormat.Format24bppRgb);
            tile.SetResolution(src.HorizontalResolution, src.VerticalResolution);
            tile.MakeTransparent();
            using (Graphics grRect = Graphics.FromImage(tile))
            {
                grRect.CompositingQuality = CompositingQuality.HighQuality;
                grRect.SmoothingMode = SmoothingMode.HighQuality;
                grRect.DrawImage(
                        src,
                        new RectangleF(0, 0, tileWidth, tileHeight),
                        rTile,
                        GraphicsUnit.Pixel
                );
            }
        }
    }
}

当我使用小尺寸的图片(例如8000x4720像素)时,一切正常。

我该如何处理大尺寸的图片呢?

PS瓦片位图在finally块中被处理。

此致,Alex。

3个回答

6

您正在使用约1GB的内存,很容易就会耗尽内存。

假设您使用的是32bpp文件格式,分辨率为16000x9440像素,则文件大小约为:

16000 * 9440 * (32/8) = ~576MB

byte[] data = new byte[fileStream.Length];
fileStream.Read(data, 0, data.Length);
using (MemoryStream memoryStream = new MemoryStream(data))
{
  [... snip ...]
}

您需要将整个文件加载到内存流中,这将需要576MB的空间。
[... snip ...]
    using (Bitmap src = new Bitmap(memoryStream)) // <-- exception
    {
        [... snip ...]
    }
[... snip ...]

你需要将整个流内容加载到位图中,这至少需要另外576MB的内存(取决于每个像素所需的位图内存量,应该至少为4,可能更多)。此时,您已经在内存中两次拥有了该图像,对于如此大的图像来说,这会严重影响内存。
您可以通过摆脱内存流并直接从文件流中加载位图来减少内存占用。
另一个解决方案是仅加载位图的一部分,并按需加载其他部分(类似于谷歌地图),但我无法帮助您解决该问题,可能需要手动读取位图。

除了解释问题是什么之外,提供替代方案也可以获得+1。 - Aistina
谢谢建议,我删除了memoryStream声明并直接从fileStream加载位图:using (Bitmap src = new Bitmap(fileStream))。但是它没有帮助=(我有一个Windows 7 x64和超过5GB的空闲VRAM。部分加载程序的解决方案听起来不错,也许有帮助。 - Alex M
1
这并不能解释它,一个.NET程序很容易分配超过1GB的内存。 - Hans Passant
通过使用以下结构,解决了一些问题:using (Image src = Image.FromStream(fileStream, false, false)); 但是下一步是使用32000x18880像素的图像,它无法工作=) - Alex M

3

虽然这不是你问题的完整答案,但是你最好使用像ImageMagick.NET这样的库。


根据架构页面(http://www.imagemagick.org/script/architecture.php#tera-pixel),ImageMagick可以“读取、处理或写入兆像素、吉像素或太像素图像尺寸”。虽然我还没有尝试过。 - Maurits Rijk

2
“MemoryStream”是使用存储数据的字节数组实现的。如果读取的数据量超过数组容量,就会分配一个大小为原来两倍的新数组,并将字节从一个数组复制到另一个数组。
由于您显然知道需要多少数据,因此可以预先分配正确的大小,从而避免调整大小。
但是,一旦达到某个大小,您将耗尽内存。 .NET对单个对象施加2 GB的限制(即使是在64位系统上),因此“MemoryStream”中的内部数组永远无法扩展到超过该限制。如果您的图像大于此限制,则会收到"内存不足"异常。

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