Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saving PNG images to a memory stream causes errors

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);
        }
    }
}
like image 851
Sylvain Avatar asked Mar 07 '16 19:03

Sylvain


1 Answers

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
}
like image 94
AmmarArnt Avatar answered Oct 13 '22 03:10

AmmarArnt