OutOfMemoryException:内存不足 – System.Drawing.Graphics.FromImage

使用System.Drawing.Graphics.FromImage(在Windows 2012服务器上使用最新版本的.NET软件)时,我会出现Out of Memoryexception, 仅限于极少数特定的图像文件 。 大多数情况下代码工作正常。

上述问题的典型答案表明某些资源尚未发布。

在回答之前请考虑以下事项: –

  • 此特定图像大小为34KB,是.JPG图像。 服务器空闲,内存超过32GB。
  • 如果我查看这个jpg文件的属性,使用Windows资源管理器,通过右键单击文件,Windows说: 96 dpi和32位深度
  • 但是,如果我使用任何图形程序(例如photoshop)打开这个jpg文件,文件属性显示为: 72 dpi和24 bit深度
  • 因此, 我认为文件头属性所说的与文件实际包含的内容之间存在不匹配
  • 此外,如果我使用图形程序打开jpg文件并且只是重新保存而不更改任何内容 ,则Windows资源管理器中的文件属性现在匹配/读取正确(72 dpi和24位深度); 并且文件由System.Drawing.Graphics正确处理,不会抛出exception

由于我对该主题的了解有限,我不知道图像文件的文件头是否可以包含与实际文件内容不同的数据

问题:

我该如何解决这个问题? 或者,我如何告诉System.Drawing.Graphics忽略文件头数据,只看实际的图像文件内容? (因为像Photoshop这样的所有图形程序似乎都有)。

谢谢!

虽然我不是JPEG文件格式的大师,但我对这个主题进行了一些研究,这是我发现的可以帮助你解决问题的问题。

请注意,由于缺少要检查的示例文件并告诉它与.Net / GDI + JPEG / JFIF解码器所期望的不同,本答案将假定而不是专门查明问题的根源。

JPEG / JFIF格式

首先,您可能想要了解JPEG / JFIF格式本身。 毕竟,您刚刚遇到.Net / GDI +无法加载/解析的文件。 由于我没有您遇到问题的文件,我建议您在选择的hex编辑器中加载它…它能够基于模板/代码/解析器突出显示该文件。

我使用了010 Editor和Sweetscape在线模板库中的JPEG模板 。 010编辑器提供30天免费试用。

你特别寻找的是你的 JPEG中的SOF n标识符和数据。

在此处输入图像描述

SOF n数据中,我可以看到我的图像是Y(154)像素高和X(640)像素宽,使用3个分量,每个分量的精度为8位,使其成为每像素24位。

JPEG / JFIF格式是许多不同实现/格式的巨大组合。 显然,你不会在奇怪的 JPEG格式出现之前很久以前就找到任何库中的格式的每个变体。 GDI +库有哪些。

在您的情况下,我怀疑您已经遇到了JPEG文件中常见的有关 CMYK颜色配置文件的问题。

.Net实现

你说你使用过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 …

  • GdipGetImageGraphicsContext
  • GdipLoadImageFromFile
  • GdipLoadImageFromFileICM(或它们各自的* Stream变体)或
  • GdipImageForceValidation

…返回代码3或5(内存不足或缓冲区不足)

我从referencesource.microsoft.com收集了那里的.Net来源。
在任何情况下,这很可能不是.Net的问题,但是GDI +(gdiplus.dll)的问题,微软不提供源代码。 这也意味着没有办法控制图像如何使用.Net包装器加载,并且无法检查它为什么失败。 (虽然我仍然怀疑你的JPEG是用CMYK保存的)

不幸的是,当您在GDI +土地上移动时,您会发现许多这些奇怪的exception/错误。 由于该库几乎全部弃用,因此支持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)像素宽,每个组件的精度为8位使用4组件,使其每像素32位。

我怀疑你会在你的“坏”文件中看到相同的内容。
是的,Image.FromFile现在抛出一个OutOfMemoryException!

可能的解决方案

  • 使用外部库加载图像文件。 (我给你留下的练习,但ImageMagick AKA Magick.NET似乎是一个不错的选择)
  • 使用命令行工具(在获得此exception时调用)可以将图像从一种格式转换为另一种格式。 或者从JPEG到JPEG,在这种情况下可能是这样。 (再次,ImageMagick的“转换”命令行工具似乎是一个不错的选择)
  • 使用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); } } 

    根据这个答案 ……

…我会说这是一个很好的选择,因为它让你在.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 +获得更远的距离,你将会越好。

结论/ TL:DR和其他说明。

问:我可以在.Net中加载这些文件吗?
答:可以 ,因为GDI +不支持JPEG文件中的CMYK颜色空间,所以有点摆弄并且没有使用GDI +来初始加载文件。
即便如此,GDI +也缺乏对许多东西的支持,这就是为什么我会推荐一个外部图像库而不是GDI +。

问: Windows和<在此处插入照片应用程序>之间的文件的DPI和位深度不匹配
答:这只是Windows JPEG加载与其他应用程序JPEG加载例程不同的certificate。 只有使用GDI或GDI +的应用程序才会看到Windows在显示图像详细信息时所执行的相同信息。
如果您使用的是Windows 7+,那么它不会使用GDI +来显示信息或图像。 它正在使用WPF或WIC这样做,这些更新一些。

问:如果我使用图形程序打开jpg文件并且只是重新保存而不更改任何内容,则Windows资源管理器中的文件属性现在匹配/读取正确(72 dpi和24位深度)
答:如果您使用的是Adobe Photoshop,并且使用“保存为网络”,则JPEG图像将不会以CMYK格式保存。 改为使用“另存为…”,您会发现颜色空间(和位深度)保持不变。

但是,在Photoshop中加载文件时,我无法重现您在DPI和位深度方面的差异。 它们在Windows和Photoshop中都被报告为相同。

我遇到了与此错误相同的问题 – 似乎图形/位图/图像库会因某些格式错误的图像而引发exception。 正如Cadde所显示的那样,将其缩小范围很难,这很困难。

继Cadde(使用外部库作为练习向读者留下)做出的重大回答之后,我使用MagickNet将我的代码更改为以下内容,您可以在这里使用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()); } 

我有同样的问题。 我的jpg文件是从Photoshop生成的。 一个简单的解决方案是使用Winodws Paint打开jpg文件,并保存为新的jpg文件。 将新的jpg文件导入C#项目,问题将消失。