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

时间:2014-09-08 12:03:52

标签: c# .net graphics system.drawing

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

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

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

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

由于我对该主题知之甚少,如果图像文件的文件头可以包含与实际文件内容不同的数据,我就不知道

问题:

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

谢谢!

3 个答案:

答案 0 :(得分:21)

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

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

JPEG / JFIF格式

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

我使用了来自Sweetscape在线模板存储库的010 EditorJPEG Template。 010编辑器提供30天免费试用。

您专门寻找的是 SOF n 标识符和错误 JPEG中的数据。

enter image description here

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

JPEG / JFIF格式是许多不同实现/格式的巨大组合。显然,在很久以前出现奇数 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的问题,但是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!

可能的解决方案

  • 使用外部库加载图像文件。 (我留给你的练习,但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中加载这些文件吗? 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#项目,问题将消失。