Dynamic image retrieval with .axd
A lot of my work these days deals with images in web controls; I had an earlier post completed about pulling down portions of an image through JavaScript to piece together a very wide image client-side, but then directions shifted and I never got around to posting it. For the current control we need to request many different images and I'm now using a more efficient method which I'd like to share here.
The 'old' way of doing it was to simply use <img src="page.aspx?id=6" mce_src="page.aspx?id=6">
which wrote the image to the Page's Response
context. The 'new' way looks almost identical on the client side <img src="getImage.axd?id=6" mce_src="getImage.axd?id=6">
and there is a only slightly more work required on the server. If you think about the overhead (i.e. many steps required) to parse even an empty aspx, I think it's obvious that axd is the way to go.
There are actually a few built-in axd extensions for ASP.NET 2.0 including WebResource
and Trace
; they simply redirect to some built-in handler that ships with the framework. The first can be used to retrieve static resources embedded in your control's assembly. However I want to retrieve a dynamically generated image so we'll need to roll our own.
I've already developed the classes that will generate data and a Bitmap object, so there are just two steps remaining.
Step 1: Update the web.config file with the re-direct for the .axd request
<httpHandlers>
<add verb="GET" path="getCampaign.axd"
type="GanttControl.ImageRequestHandler, GanttControl"/>
</httpHandlers>
Step 2: Wrap my existing classes into one that implements IHttpHandler
.
class ImageRequestHandler : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
string id = context.Request.QueryString["id"];
if (id == null || id.Length == 0)
{
throw new ArgumentException("An 'id' parameter must be
specified");
}
CampaignFactory campaignFactory = new CampaignFactory();
GanttBarFactory factory = new GanttBarFactory(
campaignFactory.CreateWithOneTrend());
Bitmap bitmap = factory.Create(200, 2);
context.Response.ContentType = "image/png";
bitmap.Save(context.Response.OutputStream, ImageFormat.Png);
}
}
It all looks good until you run it and see the following exception in your Output window.
A first chance exception of type 'System.NotSupportedException'
occurred in System.Web.dll
A first chance exception of type 'System.Runtime.InteropServices.
ExternalException' occurred in System.Drawing.dll
Groan. A quick test shows that the Bitmap
can be output succesfully as JPEG and GIF, so why is PNG dying? The exception message from System.Drawing
is as follows:
"A generic error occurred in GDI+."
Thank you, Platform team. Thank you sooooo much :) The stack trace doesn't give us any more clues.
at System.Drawing.Image.Save(Stream stream, ImageCodecInfo encoder,
EncoderParameters encoderParams)
at System.Drawing.Image.Save(Stream stream, ImageFormat format)
...
So there's something very strange going on with PNG files. But why? We're only writing to a stream and writing it to a file works perfectly.
Oh, right. Streams aren't the same as files.
A quick check in the debugger of HttpContext.Response.OutputStream shows that it does not support CanRead
and CanSeek
. Apparently the PNG exporter needs that where GIF and JPEG don't. It's strange, because I remember compressing with libPng and you just passed it data row-by-row or the whole raw chunk at once, but who knows what the GDI+ implementation looks like.
Regardless, the simplest solution is to write it to a MemoryStream
first, and then to the OutputStream
.
context.Response.ContentType = "image/png";
MemoryStream memoryStream = new MemoryStream();
bitmap.Save(memoryStream, ImageFormat.Png);
memoryStream.WriteTo(context.Response.OutputStream);
Comments
Anonymous
January 08, 2007
As a follow-up to yesterday's post , I needed the ImageRequestHandler to retrieve data from an externalAnonymous
May 12, 2007
Thank you, I was at an impasse when I could not send png to stream, which you have shed some light to successfully sending png to stream. thank youAnonymous
May 20, 2007
thanks for the png tip! exactly what I was looking for!Anonymous
March 10, 2008
I have a method that creates an Image (Bitmap) from a Stream (jpeg file on disc). The Image is passed around, eventually, as an Object, to a method which casts the Object back to an Image.using ( Stream newPartStream = packagePart.GetStream() ){ if ( partContent is Image ) { Image image = partContent as Image; ImageFormat imageFormat = image.RawFormat.CookFormat(); image.Save( newPartStream, imageFormat ); } else if ( ......}It seems that the problem is caused not by how I am trying to do the Save(), but by how the original bitmap was created!Beware: the underlying stream used to create a bitmap must remain for the bitmap to be functional (or, if FromFile() was used, the file will be locked until the bitmap is disposed). This seems pretty rubbish if you ask me, but that (apparently) is the way it is.Anonymous
September 09, 2008
thanks subpixel,I was closing the underlying stream before doing the Save().