Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot render image to HttpContext.Response.OutputStream

Basically I am trying to render a simple image in an ASP.NET handler:

public void ProcessRequest (HttpContext context)
{
    Bitmap image = new Bitmap(16, 16);
    Graphics graph = Graphics.FromImage(image);

    graph.FillEllipse(Brushes.Green, 0, 0, 16, 16);

    context.Response.ContentType = "image/png";
    image.Save(context.Response.OutputStream, ImageFormat.Png);
}

But I get the following exception:

System.Runtime.InteropServices.ExternalException: A generic error
occurred in GDI+.
    at System.Drawing.Image.Save(Stream stream, ImageCodecInfo encoder,
    EncoderParameters encoderParams)

The solution is to use this instead of having image write to OutputStream:

MemoryStream temp = new MemoryStream();
image.Save(temp, ImageFormat.Png);
byte[] buffer = temp.GetBuffer();
context.Response.OutputStream.Write(buffer, 0, buffer.Length);

So I'm just curious as to why the first variant is problematic?

Edit: The HRESULT is 80004005 which is just "generic".

like image 734
Serguei Avatar asked Feb 24 '09 17:02

Serguei


3 Answers

The writer indeed needs to seek to write in the stream properly.

But in your last source code, make sure that you do use either MemoryStream.ToArray() to get the proper data or, if you do not want to copy the data, use MemoryStream.GetBuffer() with MemoryStream.Length and not the length of the returned array.

GetBuffer will return the internal buffer used by the MemoryStream, and its length generally greater than the length of the data that has been written to the stream.

This will avoid you to send garbage at the end of the stream, and not mess up some strict image decoder that would not tolerate trailing garbage. (And transfer less data...)

like image 112
Jérôme Laban Avatar answered Oct 11 '22 14:10

Jérôme Laban


Image.Save(MemoryStream stream) does require a MemoryStream object that can be seeked upon. The context.Response.OutputStream is forward-only and doesn't support seeking, so you need an intermediate stream. However, you don't need the byte array buffer. You can write directly from the temporary memory stream into the context.Response.OutputStream:

/// <summary>
/// Sends a given image to the client browser as a PNG encoded image.
/// </summary>
/// <param name="image">The image object to send.</param>
private void SendImage(Image image)
{
    // Get the PNG image codec
    ImageCodecInfo codec = GetCodec("image/png");

    // Configure to encode at high quality
    using (EncoderParameters ep = new EncoderParameters())
    {
        ep.Param[0] = new EncoderParameter(Encoder.Quality, 100L);

        // Encode the image
        using (MemoryStream ms = new MemoryStream())
        {
            image.Save(ms, codec, ep);

            // Send the encoded image to the browser
            HttpContext.Current.Response.Clear();
            HttpContext.Current.Response.ContentType = "image/png";
            ms.WriteTo(HttpContext.Current.Response.OutputStream);
        }
    }
}

A fully functional code sample is available here:

Auto-Generate Anti-Aliased Text Images with ASP.NET

like image 28
Jason Carter Avatar answered Oct 11 '22 13:10

Jason Carter


I believe the problem is that the Response.OutputStream does not support seeking. In order to save a PNG (or JPEG), the image object needs to be able to write the output non-sequentially. If I remember correctly, it would have worked if you saved the image as a BMP since that image format can be written without seeking the stream.

like image 30
David Avatar answered Oct 11 '22 13:10

David