将 PNG 图像保存到内存流会导致错误

Posted

技术标签:

【中文标题】将 PNG 图像保存到内存流会导致错误【英文标题】:Saving PNG images to a memory stream causes errors 【发布时间】:2016-03-07 19:35:35 【问题描述】:

我有一个 WebAPI 端点(托管在 IIS 中),它从数据库(字节数组)中读取图像并以 PNG 格式返回它们。这段代码很简单:

  Image img = ImageHelper.ReadFromDatabase(…);
  using (MemoryStream ms = new MemoryStream())
  
      img.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
      HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
      response.Content = new ByteArrayContent(ms.ToArray());
      response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
      response.ConfigureResponseCachability(parseConceptVersion, TimeSpan.FromHours(1));
      return response;
  

我有三台网络服务器,在其中一台服务器上,上面的代码会导致此错误:A generic error occurred in GDI+.at System.Drawing.Image.Save()

有关服务器的更多详细信息

服务器运行不同的操作系统版本:

服务器 1,工作正常:Windows Server 2012 Standard 服务器 2,工作正常:Windows Server 2008 R2 Standard 服务器 3,不工作:Windows Server 2012 R2 数据中心(核心)

JPEG 是一种解决方法

我已将上述代码更改为返回 JPEG 而不是 PNG,从而解决了问题。

  img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);

我必须发送 PNG 图像,所以我需要找到根本原因。

使用 WPF 类而不是 Windows 窗体类

我将代码转换为使用 WPF 类 (System.Windows.Media) 而不是 Windows 窗体类。结果很有趣:我在创建 PNG 时也遇到了错误。这次错误是The component registration is invalid. (Exception from HRESULT: 0x88982F8A)System.Windows.Media.Imaging.BitmapEncoder.SaveFrame()

服务器上是否缺少某些东西?

可能是我的服务器缺少保存 PNG 所需的低级组件吗?

谢谢


@Chris Pratt 要求查看从字节数组创建图像的代码。 WebAPI 代码和 SqlDataReader 调用之间有几层,但这里是转换从数据库读取的字节数组的代码。

/// <summary>
/// Creates a bitmap from a byte array
/// </summary>
public static Bitmap CreateBitmapFromBytes(byte[] bytes, int width, int height)

    // Make sure bytes were specified.
    if (bytes == null ||
        bytes.Length == 0)
    
        return null;
    

    using (MemoryStream imageStream = new MemoryStream(bytes))
    
        Bitmap bitmap;

        try
        
            bitmap = (Bitmap)Bitmap.FromStream(imageStream);
        
        catch(ArgumentException)
        
            return GetEmptyBitmap(width, height);
        

        using (bitmap)
        
            return new Bitmap(bitmap, width, height);
        
    

【问题讨论】:

服务器 #3 使用的 Windows 主题是否与 #1 不同? Server 3 是“核心”安装,因此没有桌面。也就是说,没有 Windows 资源管理器外壳,它的“开始”菜单、任务栏和您可能习惯看到的其他功能。您所拥有的只是一个命令提示符。 (msdn.microsoft.com/en-us/library/dd184075.aspx) 一般来说,在 Server Fault 上发帖可能会更幸运,因为它很可能与操作系统有关。出于我们的目的,请张贴ImageHelper.ReadFromDatabase 的代码。我不确定为什么代码在不同服务器之间的行为可能会有所不同,但我们或许可以梳理出一些小细节,让您的代码具有更广泛的兼容性。 根据那个链接,“Core”甚至根本不支持.Net。 OP 说 JPEG 可以正常工作。逻辑会规定,如果您可以使用任何图像格式,那么您应该能够使用所有图像格式,除非未安装使用该特定格式所涉及的特定帮助程序库。 【参考方案1】:

System.Drawing 命名空间在处理图像方面有很多已知的缺点。请参考this article 并提供详尽的解释。

在处理此命名空间时,您必须非常小心处理内存流、位图等的位置。您的问题听起来与这些一次性对象有关。

也就是说,一个不错的选择是使用ImageSharp library,其中可以很好地处理从字节数组中读取图像并将其保存为 png。

你可以使用Image.Load method:

    var img = Image.Load(ImageHelper.ReadFromDatabaseAsByteArray());

和 save it as png 到 MemoryStream 以供进一步使用:

using (var memStream = new MemoryStream())

   image.SaveAsPng(memStream);
   // Do something with the memStream, for example prepare http response

【讨论】:

【参考方案2】:

1) .NET framework >= 3.0 应该有一个内置的编码器。检查是否有适合正确图像 mime 类型的编码器。

 public static ImageCodecInfo GetEncoderInfo(string mimeType)
        
             string lookupKey = mimeType.ToLower(); 
            ImageCodecInfo foundCodec = null; 
            if (Encoders.ContainsKey(lookupKey))
             
                foundCodec = Encoders[lookupKey];
             
            return foundCodec;
        

(您需要查找编码器)

  private static Dictionary<string, ImageCodecInfo> _encoders;

        /// <summary>
        ///     A quick lookup for getting image encoders
        /// </summary>
        private static Dictionary<string, ImageCodecInfo> Encoders
        
            get
            
                if (_encoders == null)
                
                    _encoders = new Dictionary<string, ImageCodecInfo>();
                
                if (_encoders.Count == 0)
                
                    foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
                    
                        _encoders.Add(codec.MimeType.ToLower(), codec);
                    
                
                return _encoders;
            
        

然后你可以尝试用你喜欢的编码器保存或获取字节

  public static void Save(string path, Image img, string mimeType)
        
            using (var ms = File.OpenWrite(path))
            
                ImageCodecInfo encoder = GetEncoderInfo(mimeType);
                img.Save(ms, encoder, null); 
            
        

2) 有时我在保存索引图像时遇到问题。查看 yourImage.PixelFormat 并在需要时尝试将图像转换为其他图像。

【讨论】:

以上是关于将 PNG 图像保存到内存流会导致错误的主要内容,如果未能解决你的问题,请参考以下文章

加载/保存图像错误

使用带有内存流的 c# 在控制台应用程序中保存图像时,GDI + 中发生一般错误

将图像保存到内存

Java压缩流GZIPStream导致的内存泄露

Java压缩流GZIPStream导致的内存泄露

保存/使用图像而不分配 300mb 内存