如何在.NET中使用大位图?

时间:2009-02-20 15:09:43

标签: c# bitmap gdi out-of-memory

我正在尝试编写轻量级图像查看应用程序。但是,.NET存在系统内存限制。

当尝试加载大位图( 9000 x 9000 px 或更大,24位)时,我得到一个System.OutOfMemoryException。这是在具有2GB RAM(其中1.3GB用完)的Windows 2000 PC上。尝试加载文件也需要花费大量时间。

以下代码生成此错误:

Image image = new Bitmap(filename);
using (Graphics gfx = this.CreateGraphics())
{
    gfx.DrawImage(image, new Point(0, 0));
}

与此代码一样:

Stream stream = (Stream)File.OpenRead(filename);
Image image = Image.FromStream(stream, false, false);
using (Graphics gfx = this.CreateGraphics())
{
    gfx.DrawImage(image, new Rectangle(0, 0, 100, 100), 4000, 4000, 100, 100, GraphicsUnit.Pixel);
}

此外,这样就足够了:

Bitmap bitmap = new Bitmap(filename);
IntPtr handle = bitmap.GetHbitmap();

后一个代码旨在与G​​DI一起使用。 在研究这个问题的同时,我发现这实际上是一个内存问题,.NET试图在单个内存块中分配两倍的内存。

http://bytes.com/groups/net-c/279493-drawing-large-bitmaps

我从其他应用程序(Internet Explorer,MS Paint等)知道可以打开大图像,而且很快。我的问题是, 如何在.NET中使用大型位图?

无论如何要流式传输它们,还是非内存加载它们?

7 个答案:

答案 0 :(得分:8)

这是一个两部分问题。第一个问题是如何在不耗尽内存的情况下加载大型图像(1),第二个问题是提高加载性能(2)。

(1)Concider像Photoshop一样的应用程序,你可以在文件系统上使用巨大的图像消耗gigabite。将整个图像保存在内存中并且仍然有足够的可用内存来执行操作(过滤器,图像处理等,甚至只是添加图层)在大多数系统上都是不可能的(即使是8gb x64系统)。

这就是为什么像这样的应用程序使用交换文件的概念。在内部,我假设photoshop使用专有的文件格式,适合他们的应用程序设计,并构建为支持交换的部分加载,使他们能够将文件的一部分加载到内存中进行处理。

(2)通过为每种文件格式编写自定义加载器,可以改进(非常多)Performande。这需要您阅读要使用的文件格式的文件头和结构。一旦你掌握了它,那就不是****,但这并不像做方法调用那样微不足道。

例如,您可以google for FastBitmap查看有关如何快速加载位图(BMP)文件的示例,其中包括解码位图标头。这涉及到pInvoke并且为了让你了解你所面临的问题,你需要定义位图结构,例如

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct BITMAPFILEHEADER
        {
            public Int16 bfType;
            public Int32 bfSize;
            public Int16 bfReserved1;
            public Int16 bfReserved2;
            public Int32 bfOffBits;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct BITMAPINFO
        {
            public BITMAPINFOHEADER bmiHeader;
            public RGBQUAD bmiColors;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct BITMAPINFOHEADER
        {
            public uint biSize;
            public int biWidth;
            public int biHeight;
            public ushort biPlanes;
            public ushort biBitCount;
            public BitmapCompression biCompression;
            public uint biSizeImage;
            public int biXPelsPerMeter;
            public int biYPelsPerMeter;
            public uint biClrUsed;
            public uint biClrImportant;
        }

可能会创建一个DIB(http://www.herdsoft.com/ti/davincie/imex3j8i.htm)和奇怪的事情,比如数据被“颠倒”存储在您需要考虑的位图中,或者当您打开它时您会看到镜像: - )

现在这只是针对位图。说你想做PNG然后你需要做类似的东西,但解码PNG标题,这是最简单的形式并不难,但如果你想得到完整的PNG规范支持,那么你是一个有趣的旅程: - )

PNG与位图不同,因为它使用基于块的格式,它具有“标题”,您可以放置​​以查找不同的数据。我在播放格式时使用的一些块的示例是

    string[] chunks =  
new string[] {"?PNG", "IHDR","PLTE","IDAT","IEND","tRNS",
"cHRM","gAMA","iCCP","sBIT","sRGB","tEXt","zTXt","iTXt",
"bKGD","hIST","pHYs","sPLT","tIME"};

您还必须了解PNG文件的Adler32校验和。因此,您想要做的每种文件格式都会增加一系列不同的挑战。

我真的希望我能在回复中提供更完整的源代码示例,但这是一个复杂的主题,说实话我自己没有实现交换,所以我不能给出太多可靠的建议。

简短的回答是BCL中的图像处理能力并不高。中等答案是尝试找出是否有人编写了可以帮助您的图像库,而长期的答案就是拉起袖子并自己编写应用程序的核心。

既然你在现实生活中认识我,你就知道在哪里找到我;)

答案 1 :(得分:3)

对于一个非常全面的答案,我会使用Reflector来查看Paint.NET的源代码(http://www.getpaint.net/);用C#编写的高级图形编辑程序。

(正如评论中所指出的,Paint.NET曾经是开源的,但现在是封闭的来源)。

答案 2 :(得分:0)

怎么样:

Image image = new Bitmap(filename);
using (Graphics gfx = Graphics.FromImage(image))
{    
// Stuff
}

答案 3 :(得分:0)

只是快速修复,我有同样的问题,我创建了第二个位图实例并在构造函数中传递了位图。

答案 4 :(得分:0)

你能创建一个相同尺寸和颜色深度的新的空白位图吗?如果是这种情况,您至少知道您的环境可以处理图像。然后问题出现在图像加载子系统中,当然,您指示的链接可能就是这种情况。

我想你可以编写自己的位图加载器,但这对于非平凡的格式来​​说是很多工作,所以我不会建议它。

也许有替换库可用于解决标准加载器的这些问题?

答案 5 :(得分:0)

所以你说它不是加载位图而是导致内存不足的渲染?

如果是这样,您可以使用Bitmap.LockBits来获取像素并自己编写基本的图像缩放器吗?

答案 6 :(得分:0)

有一件事让我印象深刻。您是在绘制整个图像而不仅仅是可见部分吗?您不应该绘制比在应用程序中显示的图像更大的部分,使用x,y,width和heigth参数来限制绘制区域。