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

查看:51
本文介绍了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 一样).

谢谢!

解决方案

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

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

JPEG/JFIF 格式

首先,您可能想深入了解 JPEG/JFIF 格式本身.毕竟,您刚刚遇到了 .Net/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 的问题,而是 Microsoft 未提供源代码的 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 Editor 中显示此文件(我必须在 Photoshop 中将其设置为 CMYK 颜色空间)给了我以下 SOFn 数据:Y (154) 像素高和 X (640) 像素宽,使用 4 个组件,每个组件的精度为 8 位,使其为每像素 32 位.

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

可能的解决方案

  • 使用外部库加载图像文件.(我留给你一个练习,但 ImageMagick 又名 Magick.NET 似乎是一个不错的选择)
  • 利用可以将图像从一种格式转换为另一种格式的命令行工具(在遇到此异常时调用).或者从 JPEG 到 JPEG,就像在这种情况下一样.(再一次,ImageMagick 的转换"命令行工具似乎是一个不错的选择)
  • 使用 Windows Presentation Framework 程序集...

    public static Image ImageFromFileWpf(string filename) {/* 使用演示框架将图像加载到编码器中.* 这是通过向派生的 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)));//使用内存流作为从一种文件格式到另一种文件格式的切换.使用 (var memoryStream = new MemoryStream()) {bitmapEncoder.Save(memoryStream);/* 我们必须从流中创建图像的副本,MSDN 特别指出流必须保留* 在图像的整个生命周期内打开.* 我们不能实例化 Image 类,所以我们从我们的临时图像实例化一个位图.* 无论如何,位图都是从 Image 派生的,所以这完全没问题.*/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")图像.保存(文件名,编码器,新编码器参数{参数=新[]{新编码器参数(编码器.质量,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 中被报告为相同.

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天全站免登陆