10

I have a WebAPI endpoint (hosted in IIS) that reads images from a database (byte array) and returns them in the PNG format. This code is simple:

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

I have three web servers and on one of the them the above code causes this error: A generic error occurred in GDI+. at at System.Drawing.Image.Save().

More detail on the servers

The servers are running different OS versions:

  • Server 1, working fine: Windows Server 2012 Standard
  • Server 2, working fine: Windows Server 2008 R2 Standard
  • Server 3, not working: Windows Server 2012 R2 Datacenter (Core)

JPEG is a workaround

I have changed the above code to return a JPEG instead of a PNG and that fixes the problem.

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

I have to send PNG images so I need to find the root cause.

Using WPF classes instead of Windows Form classes

I converted the code to use WPF classes (System.Windows.Media) instead of Windows Form classes. The result is interesting: I get an error creating PNGs as well. This time the error is The component registration is invalid. (Exception from HRESULT: 0x88982F8A) at System.Windows.Media.Imaging.BitmapEncoder.SaveFrame()

Is there something missing on the server?

Could it be that my server is missing a low level component that is necessary to save PNGs?

Thanks


@Chris Pratt asked to see the code that creates the images from the byte array. There are several layers between the WebAPI code and the SqlDataReader invocation but here is the code that converts the byte array read from the database.

/// <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);
        }
    }
}
Sylvain
  • 19,099
  • 23
  • 96
  • 145
  • Is server #3 using a different Windows theme than #1? – cbr Mar 07 '16 at 19:38
  • Server 3 is a "Core" installation so there is no desktop. That is, there is no Windows Explorer shell, with its Start menu, Taskbar, and the other features you may be used to seeing. All you have is a command prompt. (https://msdn.microsoft.com/en-us/library/dd184075.aspx) – Sylvain Mar 07 '16 at 19:41
  • You might have more luck, in general, posting on Server Fault, since it very well might be something about the OS. For our purposes here, please post the code for `ImageHelper.ReadFromDatabase`. I'm not sure why the code might behave differently from one server to the next, but we might be able to tease out some small thing that might make your code more broadly compatible. – Chris Pratt Mar 07 '16 at 19:51
  • According to that link "Core" doesn't even support .Net at all. – juharr Mar 07 '16 at 19:51
  • 2
    OP says JPEGs work fine. Logic would dictate that if you can work with any image format, you should be able to work with them all, unless specific helper libraries involved in working with that specific format are not installed. – Chris Pratt Mar 07 '16 at 19:53
  • @juharr : that link is for server 2008 core. My intent was to provide a link to what Server Core is in general; not to that specific version. – Sylvain Mar 07 '16 at 19:58
  • @ChrisPratt see updated question with the code you requested. – Sylvain Mar 07 '16 at 20:04
  • try to set the "imageStream" Position to 0 before returning the Bitmap (before the using) - imageStream.Position = 0; – Cadburry Mar 07 '16 at 20:12
  • Have you seen this? http://stackoverflow.com/a/16340060/2141972 -- permissions issue to HKEY_CLASSES_ROOT\CLSID{FAE3D380-FEA4-4623-8C75-C6B61110B681} – J.H. Mar 07 '16 at 20:33
  • @J.H. tests in progress...thanks – Sylvain Mar 07 '16 at 20:51
  • You're welcome. Let us know if it works. – J.H. Mar 07 '16 at 21:22
  • Bad news: after restarting the IIS service the problem no longer occurs. I had recycled the App Pool several times during my investigations but I had never restarted the service. The problem will probably reappear sooner or later. But in the meantime I have no way to test other solutions. I'll update this post when I can continue the investigation. – Sylvain Mar 08 '16 at 15:29
  • Can you switch to http://magick.codeplex.com/ ? – Akash Kava Jan 21 '17 at 17:11
  • @Sylvain I'm having the same problem (the WPF error "component registration is invalid") in 2012R2 (no Core). Did you figure out the solution? Thanks! – Diego Jancic Nov 30 '18 at 18:28

2 Answers2

1

The System.Drawing namespace has a lot of known downsides for processing images. Please refer to this article with a nice and thorough explanation.

When dealing with this namespace, you have to be very careful where you dispose memory streams, bitmaps etc. etc. Your issue sounds like it has something to do with these disposable objects.

That said, a good alternative is to use the ImageSharp library, which among else, nicely handles reading an image from a byte array and saving it as png.

You could use the Image.Load method:

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

and save it as png to a MemoryStream for further use:

using (var memStream = new MemoryStream())
{
   image.SaveAsPng(memStream);
   // Do something with the memStream, for example prepare http response
}
AmmarArnt
  • 114
  • 4
0

1) The .NET framework >= 3.0 should have a built-in encoder. Check there is an encoder for the right image mime type.

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

(you will need a lookup for encoders)

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

Then you can try saving with your favorite encoder or get the bytes

  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) Sometimes i had problems saving indexed images. Have a look to yourImage.PixelFormat and try converting image to a different one if needed.