【问题标题】:OutOfMemoryException: Out of memory - System.Drawing.Graphics.FromImageOutOfMemoryException:内存不足 - System.Drawing.Graphics.FromImage
【发布时间】:2014-09-08 12:03:51
【问题描述】:

使用 System.Drawing.Graphics.FromImage(在 Windows 2012 服务器上使用最新版本的 .NET 软件)时出现内存不足异常,仅在极少数特定图像文件上。大多数情况下,代码都能正常工作。

上述问题的典型答案表明某些资源没有被释放。

请在回答前考虑以下几点:-

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

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

问题:

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

谢谢!

【问题讨论】:

  • 这些 JPEG 文件是如何创建的?看起来它们的格式不正确....
  • 您从哪种应用程序中收到此错误?贴一些代码。你是在像 system.drawing.Image 这样实现 IDispose 的对象上调用 dispose 吗?
  • 找到问题的根源,也就是那些坏文件的根源。我见过 jpeg 严重混乱,例如显示所有错误的缩略图,除了保存到 tif 并返回到 jpeg 之外,没有任何方法可以纠正它。
  • 感谢以上3个cmets,我的回复如下:- jpg文件是否“坏”无关紧要,因为所有应用程序(如paintshop、photoshop)都可以打开它,调整它的大小 - 没有任何问题。是的,我确实处理了 system.drawing.image 对象。该应用程序是一个网站,用户可以在其中上传图像。如果普遍可用的应用程序可以打开图像,我不能对用户说 - 对不起你的图像不好;所以我必须想办法解决这个问题。

标签: c# .net graphics system.drawing


【解决方案1】:

虽然我不是 JPEG 文件格式的专家,但我对该主题进行了一些研究,我发现以下内容可以帮助您解决问题。

请注意,由于缺少示例文件来检查并说明它与 .Net/GDI+ JPEG/JFIF 解码器所期望的有什么不同,因此此答案将假设而不是具体查明问题的根源。

JPEG/JFIF 格式

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

我使用了 Sweetscape 在线模板库中的 010 EditorJPEG Template。 010 Editor 提供 30 天免费试用。

您要特别寻找的是 SOFn 标识符和 bad JPEG 中的数据。

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

JPEG/JFIF 格式是许多不同实现/格式的巨大组合。显然,在 odd JPEG 格式出现之前很久很久以前就已经存在的任何库中,您不会找到该格式的所有变体。 GDI+ 库有哪些。

在您的情况下,我怀疑您在 JPEG 文件中遇到了 commonly asked about 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 的问题,而是 Microsoft 未提供源代码的 GDI+ (gdiplus.dll) 的问题。这也意味着无法使用 .Net 包装器控制图像的加载方式,也无法检查它为什么会失败。 (虽然我仍然怀疑你的 JPEG 是用 CMYK 保存的)

不幸的是,当您在 GDI+ 领域前进时,您会发现更多这些奇怪的异常/错误。由于该库几乎已被弃用,取而代之的是 Windows Presentation Framework (WPF) 和 Windows Imaging 组件。 (WIC)

我自己的测试

由于您从未提供有关该主题的图片或任何其他详细信息,因此我试图重现您的问题。这本身就是一项任务,Image.FromFile (GdipLoadImageFromFile) 在许多不同的文件格式上都会失败。至少它不关心文件扩展名是什么,幸好 Photoshop 做到了。

因此,根据您的信息,我终于设法复制了一个 .jpg 文件,该文件在 Photoshop 中加载良好,显示 DPI 为 96,位深为 32。当然,如果我对 JPEG 格式有更多了解,我可能会得到马上解决。

在 010 编辑器中显示此文件(我必须在 Photoshop 中设置为 CMYK 颜色空间)给了我以下 SOFn 数据:Y (154) 像素高和 X (640) 像素宽,使用 4 个组件,每个组件的精度为 8 位,因此每个像素为 32 位。

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

可能的解决方案

  • 使用外部库加载图像文件。 (我留给你一个练习,但 ImageMagick A.K.A Magick.NET 似乎是一个不错的选择)
  • 使用可以将图像从一种格式转换为另一种格式的命令行工具(当您遇到此异常时调用)。或者从 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);
        }
    }
    

    基于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+ 越远,您的生活就会越好。

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

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

问: Windows 和
答: 文件的 DPI 和位深度不匹配,这只是证明 Windows JPEG 加载不同从其他应用程序 JPEG 加载例程。只有使用 GDI 或 GDI+ 的应用程序才能看到与 Windows 在显示图像详细信息时相同的信息。
如果您使用的是 Windows 7+,那么它不会使用 GDI+ 来显示信息或图像。它正在使用 WPF 或 WIC 来做到这一点,这在某种程度上是最新的。

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

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

【讨论】:

  • 很好的答案,但您应该强制原始发布者提供测试图像。
【解决方案2】:

我对这个错误有同样的问题 - 似乎图形/位图/图像库对某些格式错误的图像抛出异常。正如 Cadde 所表明的那样,进一步缩小范围是很困难的。

继 Cadde 给出的出色答案(使用外部库作为练习留给读者)之后,我使用 MagickNet 将我的代码更改为以下代码,您可以获得 here,或者简单地使用 NuGet:@ 987654322@.

代码尝试从图像创建 Graphics 对象,如果失败,则使用 ImageMagick 再次加载图像,转换为位图,并尝试从那里加载。

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());            
}

【讨论】:

  • 在我看来,将 OutOfMemoryException 作为控制流捕获听起来是一种不好的做法。而是使用 ImageMagick 加载图像,确定其色彩空间,必要时将 CYMK 转换为 sRGB,然后正常进行。您需要 ImageMagick 首先检查它会失去效率,但作为预期流程的一部分,您不会导致进程耗尽内存。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2014-10-15
    • 1970-01-01
    • 2011-05-21
    • 1970-01-01
    • 2021-12-20
    • 2021-10-29
    • 1970-01-01
    • 2020-03-12
    • 2015-08-11
    相关资源
    最近更新 更多