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

13

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

当尝试加载大型位图(9000 x 9000 px或更大,24位)时,会出现System.OutOfMemoryException错误。这是在一台拥有2GB RAM的Windows 2000 PC上发生的(其中1.3GB已被使用)。尝试加载文件也需要很长时间。

以下代码会生成此错误:

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();

这段代码原本是为GDI设计的。 在研究中,我发现这实际上是一个内存问题,即.NET尝试在单个连续的内存块中分配比所需空间多两倍的内存。

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

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

是否有任何方式可以流式传输它们或非内存加载它们?

7个回答

9

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

(1) 考虑像Photoshop这样的应用程序,您可以使用文件系统上消耗几个GB的巨大图像。在大多数系统上,保持整个图像在内存中并仍然有足够的空闲内存来执行操作(过滤器、图像处理等,甚至只是添加图层)将是不可能的(即使是8GB x64系统)。

这就是为什么此类应用程序使用交换文件的概念。在内部,我假设Photoshop使用专有文件格式,适合其应用程序设计和构建以支持从交换中部分加载的功能,使它们能够将文件的部分加载到内存中进行处理。

(2) 可以通过为每个文件格式编写自定义加载程序来改善性能(相当多)。这需要您阅读要使用的文件格式的文件头和结构。一旦您掌握了它,它并不是那么困难,但它不像调用方法那样简单。

例如,您可以搜索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中的图像处理功能并不强大。中等答案是尝试查找是否有人编写了可帮助您的图像库,长答案是挽起袖子自己编写应用程序的核心部分。
既然您认识我,您知道在哪里可以找到我;)

4

为了得到一个真正全面的答案,我建议使用Reflector来查看Paint.NET(一个用C#编写的高级图形编辑程序)的源代码(http://www.getpaint.net/)。

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


Paint.net是一款闭源程序,发布于2007年底(http://blog.getpaint.net/2007/12/04/freeware-authors-beware-of-%E2%80%9Cbackspaceware%E2%80%9D/)。 - zihotki

1
有一件事情让我想到了。你是在绘制整个图像而不仅仅是可见部分吗?你不应该绘制比应用程序中显示的更大的图像部分,应该使用x、y、width和height参数来限制绘制区域。

0

这个怎么样:

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

基于限制条件,我认为这在32位系统上不会起作用。 - WiiMaxx

0

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


0

你能否创建一个新的、空白的位图,其尺寸和颜色深度与原图相同?如果可以,那么至少你知道你的环境可以处理这个图像。那么问题就在于图像加载子系统,正如你的链接所指出的那样。

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

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


0

所以你的意思是说,不是位图的加载导致内存溢出,而是渲染导致的?

如果是这样,你可以使用Bitmap.LockBits来获取像素并编写基本的图像调整大小程序吗?


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