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

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

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

在回答之前,请考虑以下内容:

此特定图像大小为34KB,是.JPG图像。服务器处于空闲状态,并具有超过32GB的RAM。
如果我看一下
在Windows资源管理器中,通过右键单击该jpg文件,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 +无法加载/解析的文件。由于我没有文件,因此遇到问题,我建议您将其加载到所选的十六进制编辑器中,该编辑器可以根据模板/代码/解析器突出显示文件。

我使用了Sweetscape在线模板存储库中的010 Editor和JPEG Template。
010 Editor附带30天的免费试用期。

您特别需要的是不良JPEG中的SOFn标识符和数据。

在SOFn数据中,我可以看到我的图像的高度为Y(154)像素,宽度为X(640)像素,每个分量使用3个分量的精度为8位,使其每个像素为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问题,而是Microsoft不提供源代码的GDI +(gdiplus.dll)问题。这也意味着无法使用.Net包装器控制图像的加载方式,也无法检查为什么会失败。 (尽管我仍然怀疑您的JPEG是用CMYK保存的)

不幸的是,随着您在GDI +领域中的发展,您会发现许多其他奇怪的异常/错误。由于该库几乎已弃用,因此支持Windows Presentation Framework(WPF)和Windows Imaging Component。 (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位深度)
答:如果您使用的是Adobe Photoshop,并且使用“另存为Web”,则JPEG图像将不会以CMYK格式保存。使用“另存为...”代替,您会发现颜色空间(和位深度)保持不变。

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

C#无法将类型为“ System.Double”的对象转换为类型为“ System.Single” - c#

在判断此问题已得到回答之前,请阅读说明。我在下面有这个简单的代码:Dictionary<string, object> d = new Dictionary<string, object>(); d.Add("key" , 30d); System.Diagnostics.Debug.WriteLine($…

错误:无法将类型为“ System.Int32”的对象转换为类型为“ System.String”的对象 - c#

我已经完成了完美的编码注册页面,登录代码,现在UpdateCustomer页面有错误-背景信息:我正在使用Microsoft Access作为数据源 LabelState.Text = (string)Session["sState"]; LabelPostalCode.Text = (string)Session["sPost…

System.console()从Eclipse返回null,但可以通过命令提示符进行操作 - java

当我从Eclipse Helios使用System.console时,它总是返回null。但是,当我直接从命令行使用它时(即从命令提示符手动编译并执行Java源代码),我确实获得了一个Console对象。要知道为什么会这样,所以我检查了this链接。据此,当我从Eclipse运行Java代码时,后台作业调度程序必须正在启动我的JVM。这是什么意思?从命令行启…

使用System.Drawing.Font限制字体大小? - c#

我正在使用内置的“字体”对话框来选择字体大小和样式。字体大小范围是从8到72。我需要限制用户不要选择超过20的大小。是否可以将字体大小从22禁用或完全不显示22?我没有在Font类上看到任何属性来执行此操作?感谢您的任何建议。 c#参考方案 我没有在Font类上看到任何属性来执行此操作?这是因为它是FontDialog类的属性,而不是Font类的属性。例如:…

System.timer不会引发事件,并且控制台会立即终止 - c#

我需要一个每分钟执行一次的计时器,但是我很难让计时器完全用我以前使用的代码运行。所以我想我在做一些根本上与代码无关的错误,但是即使在Visual Studio Community 2017中刚刚创建的Console项目中,它也不会执行_timer_elapsed方法。控制台立即终止,没有错误,就好像它已经执行了所有代码using System; using …