使用System.Drawing.Graphics.FromImage(在Windows 2012服务器上使用最新版本的.NET软件)时出现Out of Memory异常,仅限于极少数特定图像文件。大多数情况下代码工作正常。
上述问题的典型答案表明某些资源尚未发布。
在回答之前请考虑以下事项:
由于我对该主题知之甚少,如果图像文件的文件头可以包含与实际文件内容不同的数据,我就不知道。
问题:
如何解决此问题?或者,我如何告诉System.Drawing.Graphics忽略文件头数据,只看实际的图像文件内容? (因为所有图形程序,如photoshop似乎都可以)。
谢谢!
答案 0 :(得分:21)
虽然我不是JPEG文件格式的大师,但我对这个主题做了一些研究,我发现这可以帮助你解决问题。
请注意,由于缺少要检查的示例文件并告诉它与.Net / GDI + JPEG / JFIF解码器所期望的不同,这个答案将假设而不是专门查明问题的根源。
首先,您可能希望对JPEG / JFIF格式本身有所了解。毕竟,您刚刚遇到.Net / GDI +无法加载/解析的文件。由于我没有您遇到问题的文件我会建议您在选择的十六进制编辑器中加载它...它能够根据模板/代码/解析器突出显示该文件。
我使用了来自Sweetscape在线模板存储库的010 Editor和JPEG Template。 010编辑器提供30天免费试用。
您专门寻找的是 SOF n 标识符和错误 JPEG中的数据。
在 SOF n 数据中,我可以看到我的图像是Y(154)像素高和X(640)像素宽,每个组件的精度为8位使用3个组件,每像素24位。
JPEG / JFIF格式是许多不同实现/格式的巨大组合。显然,在很久以前出现奇数 JPEG格式之前,你不会在任何库中找到格式的每个变体。 GDI +库有哪些。
在您的情况下,我怀疑您已经遇到JPEG文件的commonly asked about CMYK颜色配置文件。
你说你使用了 System.Drawing.Graphics.FromImage ,所以我假设你的代码看起来像下列之一:
Graphics.FromImage(Image.FromFile("nope.jpg"));
Graphics.FromImage(Image.FromFile("nope.jpg", true));
Graphics.FromImage(Image.FromStream(nopeJpegStream));
从这些调用中,当本机gdiplus.dll调用时,您可能会收到OutOfMemoryException ...
...返回代码3或5(内存不足或缓冲区不足)
我从referencesource.microsoft.com收集了这些。来源.Net 在任何情况下,这很可能不是.Net的问题,但是GDI +(gdiplus.dll)的问题是微软没有提供源代码的。这也意味着没有办法控制图像如何使用.Net包装器加载,并且无法检查它为什么失败。 (虽然我仍然怀疑你的JPEG是用CMYK保存的)
不幸的是,当您在GDI +土地上移动时,您会发现许多这些奇怪的异常/错误。由于该库几乎全部被弃用,而支持Windows Presentation Framework(WPF)和Windows Imaging Component。 (WIC)
由于您从未提供过有关主题的图片或任何其他详细信息,因此我尝试重现您的问题。这本身就是一项任务,Image.FromFile(GdipLoadImageFromFile)将在许多不同的文件格式上失败。至少它并不关心文件扩展名是什么,谢天谢地,Photoshop确实如此。
因此,根据您的信息,我终于设法重现了一个在Photoshop中加载得很好的.jpg文件,显示DPI为96,位深度为32.当然,如果我对JPEG格式有更多了解,我可能会得到解决方案。
在010编辑器中显示此文件(我必须在Photoshop中设置为CMYK色彩空间)给了我以下 SOF n 数据:Y(154)像素高和X(640)像素宽,使用 4 组件,每个组件的精度为8位,使其成为每像素32位。
我怀疑你会在你的"坏"文件。
是的,Image.FromFile现在抛出一个OutOfMemoryException!
使用Windows Presentation Framework程序集...
public static Image ImageFromFileWpf(string filename) {
/* Load the image into an encoder using the Presentation Framework.
* This is done by adding a frame (which in laymans terms is a layer) to a class derived BitmapEncoder.
* Only TIFF, Gif and JPEG XR supports multiple frames.
* Since we are going to convert our image to a GDI+ resource we won't support this as GDI+ doesn't (really) support it either.
* If you want/need support for layers/animated Gif files, create a similar method to this one that takes a BitmapFrame as an argument and then...
* 1. Instanciate the appropriate BitmapDecoder.
* 2. Iterate over the BitmapDecoders frames, feeding them to the new method.
* 3. Store the returned images in a collection of images.
*
* Finally, i opted to use a PngBitmapEncoder here which supports image transparency.
*/
var bitmapEncoder = new PngBitmapEncoder();
bitmapEncoder.Frames.Add(BitmapFrame.Create(new Uri(filename)));
// Use a memorystream as a handover from one file format to another.
using (var memoryStream = new MemoryStream()) {
bitmapEncoder.Save(memoryStream);
/* We MUST create a copy of our image from stream, MSDN specifically states that the stream must remain
* open throughout the lifetime of the image.
* We cannot instanciate the Image class, so we instanciate a Bitmap from our temporary image instead.
* Bitmaps are derived from Image anyways, so this is perfectly fine.
*/
var tempImage = Image.FromStream(memoryStream);
return new Bitmap(tempImage);
}
}
基于this answer ...
...我认为这是一个很好的选择,因为它让你在.Net框架内
请记住,当方法返回时,您会专门获取PNG图像。如果你打电话给Image.Save(string)
,你将保存PNG文件,无论你将其保存为什么扩展名。
有一个重载Image.Save(string, ImageFormat)
将使用预期的文件格式保存文件。但是,使用ImageFormat.Jpeg
的重载会导致生成的文件在多个级别上的质量下降。
使用第三次重载可以在某种程度上解决这个问题:
foreach (var encoder in ImageCodecInfo.GetImageEncoders()) {
if (encoder.MimeType == "image/jpeg")
image.Save(filename, encoder, new EncoderParameters { Param = new [] { new EncoderParameter(Encoder.Quality, 100L) }});
}
至少,这将保存一个JPEG"几乎"没有压缩。 GDI +仍然没有做好 但是,无论你多么扭曲和扭转它。 GDI +将不如一个合适的图像库,它再次很可能是ImageMagick。你可以从GDI +获得更远的距离,你将会越好。
问:我可以在.Net中加载这些文件吗?
A:是的,因为GDI +不支持JPEG文件中的CMYK色彩空间,所以有点摆弄并且没有使用GDI +来初始加载文件。
即使如此,GDI +也缺乏对许多内容的支持,这就是为什么我会推荐一个外部图像库而不是GDI +。
问:在Windows和<在此处插入照片应用>之间的文件的DPI和位深度不匹配>
A:这只是Windows JPEG加载与其他应用程序JPEG加载例程不同的证明。只有使用GDI或GDI +的应用程序才会看到Windows在显示图像详细信息时所执行的相同信息
如果您使用的是Windows 7+,那么它不会使用GDI +来显示信息或图像。它正在使用WPF或WIC这样做,这些更新一些。
问:如果我使用图形程序打开jpg文件并且只是重新保存而不更改任何内容,则Windows资源管理器中的文件属性现在匹配/读取正确(72 dpi和24位深度)
A:如果您使用的是Adobe Photoshop,则使用"另存为网络"那么JPEG图像将不会以CMYK格式保存。使用"另存为..."相反,你会发现颜色空间(和位深度)保持不变。
然而,在Photoshop中加载文件时,我无法重现您在DPI和位深度方面的差异。它们在Windows和Photoshop中都被报告为相同。
答案 1 :(得分:4)
我遇到了与此错误相同的问题 - 似乎图形/位图/图像库会因某些格式错误的图像而引发异常。正如Cadde所显示的那样,缩小范围比这更难了。
继Cadde(使用外部库作为练习向读者留下)做出的重大回答后,我使用MagickNet将代码更改为以下内容,您可以获取here,或者只需使用NuGet :PM> Install-Package Magick.NET-Q16-x86
。
代码尝试从图像创建Graphics对象,如果失败,则使用ImageMagick再次加载图像,转换为Bitmap,并尝试从那里加载。
Image bitmap = Bitmap.FromFile(filename, false);
Graphics graphics = null;
try
{
graphics = Graphics.FromImage(bitmap);
}
catch (OutOfMemoryException oome)
{
// Well, this looks like a buggy image.
// Try using alternate method
ImageMagick.MagickImage image = new ImageMagick.MagickImage(filename);
image.Resize(image.Width, image.Height);
image.Quality = 90;
image.CompressionMethod = ImageMagick.CompressionMethod.JPEG;
graphics = Graphics.FromImage(image.ToBitmap());
}
答案 2 :(得分:1)
我遇到了同样的问题。我的jpg文件是从Photoshop生成的。一个简单的解决方案是使用Winodws Paint打开jpg文件,并保存为新的jpg文件。将新的jpg文件导入C#项目,问题将消失。