OutOfMemoryException:内存不足 - System.Drawing.Graphics.FromImage [英] OutOfMemoryException: Out of memory - System.Drawing.Graphics.FromImage

查看:297
本文介绍了OutOfMemoryException:内存不足 - System.Drawing.Graphics.FromImage的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当使用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似乎都这么做)。

    谢谢!

    解决方案

    div>

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



    请注意,由于缺少示例文件来检查并说明与.Net / GDI + JPEG / JFIF解码器所期望的不同之处,此答案将假设而不是特别指出问题的根源。



    JPEG / JFIF格式



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



    我使用 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 ... < GdipLoadImageFromFile

    $ b <流变体)或

  • GdipImageForceValidation



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

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



不幸的是,当你在GDI + land中移动时,你将会发现许多更多奇怪的异常/错误。由于该库几乎不赞成使用Windows Presentation Framework(WPF)和Windows Imaging Component。 (WIC)

我自己的测试



由于您从未提供图片或任何其他细节试图重现你的问题。这本身就是一项任务,Image.FromFile(GdipLoadImageFromFile)将以许多不同的文件格式失败。至少它不在乎文件扩展名是什么,这是值得庆幸的Photoshop。



因此,根据您的信息,我最终设法重现了一个加载罚款的.jpg文件在Photoshop中,将DPI显示为96,位深度显示为32.当然,如果我对JPEG格式有更多的了解,我可能会马上得到解决方案。



在010 Editor中显示这个文件(我必须在Photoshop中设置为CMYK颜色空间)给了我下面的 SOF n 数据:Y(154)像素高和X 640)像素宽,每个组件使用8位精度,使用 4 组件,使其成为每像素32位。

我怀疑你会看到

是的,Image.FromFile现在抛出一个OutOfMemoryException异常!

可能的解决方案




  • 使用外部库加载图像文件。 (我给你的锻炼,但是 ImageMagick AKA Magick.NET 似乎是一个不错的选择)

  • 使用一个命令行工具(在得到这个异常时调用)可以转换从一种格式到另一种格式的图像。或者从JPEG到JPEG,因为它可能在这种情况下。 (再一次,ImageMagick的convert命令行工具似乎是一个不错的选择)

  • 使用Windows Presentation Framework程序集...

      public static Image ImageFromFileWpf(string filename){
    / *使用Presentation Framework将图像加载到编码器中。
    *这是通过向派生的BitmapEncoder中添加一个框架(它是一个图层)。
    *只有TIFF,GIF和JPEG XR支持多个帧。
    *由于我们要将图像转换为GDI +资源,因此GDI +不支持它。
    *如果你想要/需要对图层/动画Gif文件的支持,可以创建一个类似的方法来将一个BitmapFrame作为参数,然后...
    * 1.实例化适当的BitmapDecoder。
    * 2.迭代BitmapDecoders帧,将它们提供给新方法。
    * 3.将返回的图像存储在一组图像中。
    *
    *最后,我选择使用支持图像透明度的PngBitmapEncoder。
    * /
    var bitmapEncoder = new PngBitmapEncoder();
    bitmapEncoder.Frames.Add(BitmapFrame.Create(new Uri(filename)));

    //使用内存流作为从一种文件格式到另一种文件格式的切换。
    using(var memoryStream = new MemoryStream()){
    bitmapEncoder.Save(memoryStream);
    / *我们必须从流中创建图像的副本,MSDN明确指出,在图像的整个生命周期内,流必须保持
    *打开状态。
    *我们不能实例化Image类,所以我们需要从临时图像中创建一个Bitmap。
    *位图是从图像派生的,所以这非常好。
    * /
    var tempImage = Image.FromStream(memoryStream);
    返回新的位图(tempImage);


    根据这个答案 ...




...我会说这是一个很好的选择,因为它可以让你保持在.Net框架中。

请记住,当方法返回时,你可以专门获取一个PNG图像。如果您调用 Image.Save(string),您将 保存PNG文件,无论您将其保存为什么扩展名。



有一个过载 Image.Save(string,ImageFormat),它将使用预期的文件格式保存文件。但是,在 ImageFormat.Jpeg 中使用该重载会导致文件在多个级别上的质量下降。



  foreach(ImageCodecInfo.GetImageEncoders()中的var编码器){ 
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和其他音符。



Q:我可以在.Net中加载这些文件吗?

A:是的,有一点小窍门,因为GDI +不支持JPEG文件中的CMYK色彩空间,所以GDI +用于初始加载文件。

即使如此,GDI +也缺乏对许多事物的支持,这就是为什么我会推荐一个外部图像库



在Windows和<插入照片应用程序在这里>的$ DPI不匹配和文件的位深度
A:这只是证明Windows JPEG加载不同于其他应用程序的JPEG加载例程。只有使用GDI或GDI +的应用程序才会看到与Windows在显示图像详细信息时所用的信息相同的信息。

如果你使用Windows 7+,那么它不使用GDI +来显示信息或图像。它使用WPF或WIC来做到这一点,它们更新一些。



问:如果我使用图形程序打开jpg文件,只需重新保存而不更改任何内容,现在Windows资源管理器中的文件属性匹配/读取正确(72 dpi和24位深度)
b $ b A:如果你使用的是Adobe Photoshop,并使用Save for web,那么JPEG图像将不会保存为CMYK格式。使用另存为...,您会发现色彩空间(和位深度)保持不变。



但是,我无法再现在Photoshop中加载我的文件时,您在DPI和位深度方面的差异。他们在Windows和Photoshop中的报告都是一样的。


I get Out of Memory exception when using System.Drawing.Graphics.FromImage (using latest versions of .NET software on Windows 2012 server), ONLY on a very few specific image files. Most of the time the code works fine.

Typical answers to above issue indicate that certain resources are not being released.

Please consider the following before answering:-

  • This specific image is 34KB in size, is a .JPG image. Server is idle and has over 32GB RAM.
  • If I look at properties of this jpg file, using windows explorer, by right-clicking on file, Windows says: 96 dpi and 32 bit depth.
  • BUT, if I open this jpg file using any graphics program (e.g. photoshop), the file properties show as: 72 dpi and 24 bit depth.
  • So, there is a mis-match between what I think file header properties say and what the file actually contains.
  • Further, if I open the jpg file using a graphics program and just re-save without changing anything, the file properties in windows explorer now match/read correct (72 dpi and 24 bit depth); and the file is processed by System.Drawing.Graphics correctly, without throwing exception.

Due to my limited knowledge of the subject, I don't know if the file header of an image file can contain different data from actual file contents.

Questions:

How can I fix this problem? Or how can I tell System.Drawing.Graphics to ignore file header data and just look at actual image file contents? (as all graphics programs such as photoshop appear to do).

Thanks!

解决方案

While I'm not a guru on the JPEG file format i did some research on the subject and here's what i found that could help you with your problem/questions.

Note that this answer will assume rather than specifically pinpoint the source of your problem due to the lack of an example file to inspect and tell what differs it from what the .Net/GDI+ JPEG/JFIF decoder expects.

The JPEG/JFIF format

Starting off, you might want to have some insight into the JPEG/JFIF format itself. After all, you have just encountered a file that .Net/GDI+ cannot load/parse. Since i don't have the file you experience issues with i would suggest you load it up in a hex editor of choice... that has the capability to highlight the file based on a template/code/parser.

I used 010 Editor and the JPEG Template from Sweetscape's online template repository. 010 Editor comes with a 30-day free trial.

What you are specifically looking for is the SOFn identifier and data in your bad JPEG.

In the SOFn data i can see that my image is Y (154) pixels high and X (640) pixels wide with a precision of 8 bits per component using 3 components, making it 24 bits per pixel.

The JPEG/JFIF format is a huge mix of many different implementations/formats. Obviously, you won't find every variant of the format in any library that has been around since long long ago before the odd JPEG formats appeared. Which the GDI+ library has.

In your case, i suspect you have run into the commonly asked about CMYK color profile on your JPEG files.

The .Net implementation

You said you used System.Drawing.Graphics.FromImage so i will assume your code looks like one of the following:

Graphics.FromImage(Image.FromFile("nope.jpg"));
Graphics.FromImage(Image.FromFile("nope.jpg", true));
Graphics.FromImage(Image.FromStream(nopeJpegStream));

From those calls, you may get an OutOfMemoryException when the native gdiplus.dll calls...

  • GdipGetImageGraphicsContext
  • GdipLoadImageFromFile
  • GdipLoadImageFromFileICM (or their respective *Stream variants) or
  • GdipImageForceValidation

... returns code 3 or 5 (Out of memory or Insufficient buffer respectively)

Which i gathered from referencesource.microsoft.com looking through the .Net sources there.
In any case, this most likely isn't an issue with .Net but an issue with GDI+ (gdiplus.dll) which Microsoft doesn't provide source code for. Which also means that there is no way of controlling how the image loads using the .Net wrappers and there's no way to check WHY it fails. (though i still suspect your JPEG is saved with CMYK)

Unfortunately, you are going to find many many more of these strange exceptions/errors as you move along in GDI+ land. As the library is all but deprecated in favor of the Windows Presentation Framework (WPF) and the Windows Imaging Component. (WIC)

My own testing

Since you never provided an image or any additional details on the subject i attempted to reproduce your issue. Which was a task in of itself, Image.FromFile (GdipLoadImageFromFile) will fail on many different file formats. At least it doesn't care what the file extension is, which thankfully Photoshop does.

So with your information, i finally managed to reproduce a .jpg file that loads fine in Photoshop, shows DPI as 96 and bit depth as 32. Of course, if i knew more about the JPEG format i probably could have gotten to the solution right away.

Showing this file (which i had to set to CMYK color space in Photoshop) in 010 Editor gave me the following SOFn data: Y (154) pixels high and X (640) pixels wide with a precision of 8 bits per component using 4 components, making it 32 bits per pixel.

I suspect you would see the same on your "bad" file.
And yes, Image.FromFile now throws an OutOfMemoryException!

Possible solutions

  • Use an external library for loading image files. (An exercise i leave to you but ImageMagick A.K.A Magick.NET seems like a good bet)
  • Make use of a command line tool (invoked when you get this exception) that can convert an image from one format to another. Or from JPEG to JPEG as it may be in this case. (Once again, ImageMagick's "convert" command line tool seems like a good bet)
  • Use the Windows Presentation Framework assemblies...

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

    Based on this answer...

... Which i would say is a good option as it keeps you within the .Net framework.
Please keep in mind that when the method returns, you do specifically get a PNG image back. If you call Image.Save(string) on it you WILL save a PNG file, no matter what extension you save it as.

There is an overload Image.Save(string, ImageFormat) that will save the file using the intended file format. However, using that overload with ImageFormat.Jpeg will cause a loss in quality in the resulting file on more than one level.

That can be somewhat remedied by using the third overload:

foreach (var encoder in ImageCodecInfo.GetImageEncoders()) {
    if (encoder.MimeType == "image/jpeg")
        image.Save(filename, encoder, new EncoderParameters { Param = new [] { new EncoderParameter(Encoder.Quality, 100L) }});
}

Which, at least, will save a JPEG with "almost" no compression. GDI+ still doesn't do a good job at it.
However, no matter how much you twist and turn it. GDI+ will not be as good as a proper image library, which once again would most likely be ImageMagick. The further away you can get from GDI+, the better off you will be.

Conclusion / TL:DR and other notes.

Q: Can i load these files in .Net?
A: Yes, with a bit of fiddling and not using GDI+ for the initial loading of the file as GDI+ doesn't support the CMYK color space in JPEG files.
And even so, GDI+ lacks support for many things which is why i would recommend an external image library over GDI+.

Q: Mismatch in DPI and bit depth for file between Windows and <insert photo app here>
A: This is just proof that Windows JPEG loading differs from other applications JPEG loading routines. Only applications that use GDI or GDI+ would see the same information that Windows does when showing image details.
If you are using Windows 7+ then it isn't using GDI+ to show the information nor the image. It is using WPF or WIC to do so which are somewhat more up to date.

Q: If I open the jpg file using a graphics program and just re-save without changing anything, the file properties in windows explorer now match/read correct (72 dpi and 24 bit depth)
A: If you are using Adobe Photoshop and you use "Save for web" then the JPEG image will not be saved in CMYK format. Use "Save As..." instead and you will find that the color space (and bit depth) stays the same.

However, i wasn't able to reproduce your discrepancy in DPI and bit depth when loading my file in Photoshop. They are reported as the same in both Windows and Photoshop.

这篇关于OutOfMemoryException:内存不足 - System.Drawing.Graphics.FromImage的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆