我正在尝试编写轻量级图像查看应用程序。但是,.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();
后一个代码旨在与GDI一起使用。 在研究这个问题的同时,我发现这实际上是一个内存问题,.NET试图在单个内存块中分配两倍的内存。
http://bytes.com/groups/net-c/279493-drawing-large-bitmaps
我从其他应用程序(Internet Explorer,MS Paint等)知道可以打开大图像,而且很快。我的问题是, 如何在.NET中使用大型位图?
无论如何要流式传输它们,还是非内存加载它们?
答案 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参数来限制绘制区域。