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

Posted

技术标签:

【中文标题】如何在 .NET 中使用大位图?【英文标题】:How do I use large bitmaps in .NET? 【发布时间】:2010-10-08 20:47:22 【问题描述】:

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

当尝试加载大型位图(9000 x 9000 像素或更大,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();

后一个代码旨在与 GDI 一起使用。 在研究这一点时,我发现这实际上是一个内存问题,其中 .NET 尝试在单个连续的内存块中分配两倍于所需的内存。

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

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

是否有流式传输它们,或者非内存加载它们?

【问题讨论】:

【参考方案1】:

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

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

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

(2) 可以通过为每种文件格式编写自定义加载器来改进(很多)性能。这需要您阅读要使用的文件格式的文件头和结构。一旦你掌握了它,它就不会那么难了,但它并不像调用方法那么简单。

例如,您可以在 Google 上搜索 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 中的图像处理功能并不那么热门。中等答案是尝试查找是否有人编写了可以帮助您的图像库,而长答案是卷起袖子,自己编写应用程序的核心。

因为你在现实生活中认识我,所以你知道在哪里可以找到我;)

【讨论】:

【参考方案2】:

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

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

【讨论】:

Paint.net 是 2007 年底的闭源程序 (blog.getpaint.net/2007/12/04/…)【参考方案3】:

有一件事让我震惊。您是否在绘制整个图像而不仅仅是可见部分?您绘制的图像部分不应超过您在应用程序中显示的部分,请使用 x、y、width 和 heigth 参数来限制绘制区域。

【讨论】:

【参考方案4】:

怎么样:

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

【讨论】:

我认为这不适用于基于限制的 32 位系统【参考方案5】:

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

【讨论】:

【参考方案6】:

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

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

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

【讨论】:

【参考方案7】:

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

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

【讨论】:

以上是关于如何在 .NET 中使用大位图?的主要内容,如果未能解决你的问题,请参考以下文章

如何将字节数组转换为位图实例 (.NET)?

在 .NET 2.0 中将位图转换为一个多页 TIFF 图像

如何在 .NET 中使用 ColorMatrix 更改亮度、颜色、饱和度、色调

如何优雅地使用Redis之位图操作

如何优雅地使用Redis之位图操作

如何从 Android 中的大位图加载图块?