13

我正在尝试编写一个轻量级的图像查看应用程序。但是,.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 中使用大型位图?

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

4

7 回答 7

9

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

(1) 考虑一个像 Photoshop 这样的应用程序,您可以在其中处理在文件系统上消耗千兆位的巨大图像。在大多数系统(甚至 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 中的图像处理能力并不那么热门。中等答案是尝试查找是否有人编写了可以帮助您的图像库,而长答案是卷起袖子自己编写应用程序的核心。

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

于 2009-02-20T20:11:18.443 回答
4

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

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

于 2009-02-20T16:04:02.797 回答
1

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

于 2009-02-23T09:28:37.950 回答
0

关于什么:

Image image = new Bitmap(filename);
using (Graphics gfx = Graphics.FromImage(image))
{    
// Stuff
}
于 2009-02-20T15:19:52.077 回答
0

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

于 2009-02-20T15:20:40.170 回答
0

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

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

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

于 2009-02-20T15:21:35.257 回答
0

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

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

于 2009-02-20T15:21:42.430 回答