The Weekly Source Code 50 - A little on "A generic error occurred in GDI+" and trouble generating images on with ASP.NET
I got a nice little Yellow Screen of Death (YSOD) error on some code running under IIS that worked fine when running on the VS Developer Web Server. The error was "A generic error occurred in GDI+" and you know that if an error is generic, it's sure in the heck not specific.
My little application takes an overhead map that's stored in a local file, does some calculations from user input and draws an X on the map, then returns the resulting dynamically generated image.
There's basically three ways to do images on the server side. Use Native APIs and Interop, which only works in full trust, use System.Drawing, which "isn't supported" or use WPF on the server side, which also, ahem, isn't officially supported. I'm still trying to figure out why, but just to be clear, I used System.Drawing in extremely high traffic sites with no problems. As long as paid close attention to my unmanaged resources, I have never had a problem. I've heard anecdotally of people having trouble with GDI+ (System.Drawing) and switching over to WPF and having no problem with that. As with all things, test what you're doing. There's even some ASP.NET Controls on CodePlex that might help.
Now this post can't answer ALL reasons you're getting "a generic error occurred in GDI+" but it can answer mine. In my particular case (and I think this is the most common mistake) I was saving the composited image as a PNG.
First, I'll show you a little chunk of a code from 5 years ago that took two images and built a single image from them.
public class SomeCheckImageHandler : IHttpHandler
{
//some stuff snipped
public SomeCheckImageHandler(){}
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "image/jpg";
//some stuff snipped
GetCheckImageRequest req = new GetCheckImageRequest();
//some stuff snipped, get the params from the QueryString
GetCheckImageResponse res = banking.GetCheckImage(req);
//some stuff snipped
if (res.ImageBack != null)
{
//merge them into one image
using(MemoryStream m = new MemoryStream(res.BackImageBytes))
using(Image backImage = System.Drawing.Image.FromStream(m))
using(MemoryStream m2 = new MemoryStream(res.BrontImageBytes))
using(Image frontImage = System.Drawing.Image.FromStream(m2))
using(Bitmap compositeImage = new Bitmap(frontImage.Width,frontImage.Height+backImage.Height))
using(Graphics compositeGraphics = Graphics.FromImage(compositeImage))
{
compositeGraphics.CompositingMode = CompositingMode.SourceCopy;
compositeGraphics.DrawImageUnscaled(frontImage,0,0);
compositeGraphics.DrawImageUnscaled(backImage,0,frontImage.Height);
compositeImage.Save(context.Response.OutputStream, ImageFormat.Jpeg);
}
}
else //just show the front, we've got no back
{
using(MemoryStream m = new MemoryStream(frontImageBytes))
using(Image image = System.Drawing.Image.FromStream(m))
{
image.Save(context.Response.OutputStream, ImageFormat.Jpeg);
}
}
}
}
This code generated a JPEG. No problems, runs fine even today. Now, the code I was working on created a PNG and when you create a PNG you need a seekable stream. This little sample uses the BaseHttpHandler Phil and I made.
Note the highlighted lines in this sample. (You'll see the highlights if you view this post on my blog directly, rather than from RSS.)
The Trick for making PNGs: Without the extra intermediate MemoryStream to save to, you won't be able to make PNGs. You can't image.Save() a PNG directly to Response.OutputStream.
namespace FooFoo
{
public class MapHandler : BaseHttpHandler
public override void HandleRequest(HttpContext context)
{
string filename = context.Server.MapPath(".") + @"\images\newmap2000.jpg";
using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
using (System.Drawing.Image image = System.Drawing.Image.FromStream(fs))
{
using (Graphics g = Graphics.FromImage(image))
{
BrickFinderDrawing drawer = new BrickFinderDrawing(...somedata...);
drawer.DrawBrick(g);
drawer.DrawName(g);
using (MemoryStream stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Png);
stream.WriteTo(context.Response.OutputStream);
}
}
}
}
}
public override bool RequiresAuthentication
{
get { return false; }
}
public override string ContentMimeType
{
get { return "image/png"; }
}
public override bool ValidateParameters(HttpContext context)
{
return true;
}
}
}
Just a little something I wish I remembered when I hit the YSOD. Sure glad I have a blog to put this kind of stuff on! ;)
Related Links
- ASP.NET Futures - Generating Dynamic Images with HttpHandlers gets Easier
- Compositing two images into one from the ASP.NET Server Side
- What InterpolationMode and CompositingQuality to use when generating thumbnails via System.Drawing
- Generating image thumbnails in ASP.NET
- Server-side resizing with WPF: now with JPG
- Resizing images from the server using WPF/WIC instead of GDI+
- Gamma Correction and Color Correction - PNG is still too hard
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Perhaps I'm missing something obvious, but why does it only fail in IIS and not in the VS Web Dev Server?
http://blogs.msdn.com/directx/archive/2009/09/11/using-direct2d-for-server-side-rendering.aspx
Thanks
Vladimir
It seems that GDI+ defers some processing locking the streams at the same time System.Drawing try to copy it.
Thanks for posting the tip ;)
BTW, what kind of error message is that? Can you find out which department is responsible and tell to clean that module up with some proper error handling?
'Try the following code
' Dim oBitmap As Bitmap
'oBitmap = New Bitmap("c:\\example.jpg")
'Dim oGraphic As Graphics
'oGraphic = Graphics.FromImage(oBitmap)
'Dim oBrush As New SolidBrush(Color.Black)
'Dim ofont As New Font("Arial", 8)
'oGraphic.DrawString("Some text to write", ofont, oBrush, 10, 10)
'oBitmap.Save("c:\\example.jpg", ImageFormat.Jpeg)
'oBitmap.Dispose(oGraphic.Dispose())
'You will get the above mentioned error:A generic error occurred in GDI+.
'This problem occurs because until the bitmap object is disposed,
'it creates a lock on the underlying image file.
'So you can save the newly generated file
'with different name but not overwrite the file because of lock.
'Now suppose you want to overwrite the file
'then create another bitmap from old bitmap.
'dispose the object of old bitmap,
'process new bitmap object and save the
'new bitmap object with original file name.
'The above chunk of code should be written in the following way.
' Dim oBitmap As Bitmap
'oBitmap = New Bitmap("c:\\example.jpg")
'Dim oGraphic As Graphics
'' Here create a new bitmap object of the same height and width of the image.
'Dim bmpNew As Bitmap = New Bitmap(oBitmap.Width, oBitmap.Height)
'oGraphic = Graphics.FromImage(bmpNew)
'oGraphic.DrawImage(oBitmap, New Rectangle(0, 0, _
'bmpNew.Width, bmpNew.Height), 0, 0, oBitmap.Width, _
'oBitmap.Height, GraphicsUnit.Pixel)
'' Release the lock on the image file. Of course,
'' image from the image file is existing in Graphics object
'oBitmap.Dispose()
'oBitmap = bmpNew
'Dim oBrush As New SolidBrush(Color.Black)
'Dim ofont As New Font("Arial", 8)
'oGraphic.DrawString("Some text to write", ofont, oBrush, 10, 10)
'oGraphic.Dispose()
'ofont.Dispose()
'oBrush.Dispose()
'oBitmap.Save("c:\\example.jpg", ImageFormat.Jpeg)
'oBitmap.Dispose()
'Posted On: 04/08/2006 12:41
' Feedback()
'2 comments has been posted.
'Murray Ferguson @ 06/06/2006 04:07
'Many thanks for the fix.
'Had been struggling with this issue for quite some time. I very grateful.
' Murray()
'9479r9r33n @ 06/09/2006 08:25
'Thanks for your post. It gave me a template to
'create a method that looks at an image a
'nd coninues to take 25% off of
'it until it reaches a predetermined file size:
'if((File1.PostedFile != null) && (File1.PostedFile.ContentLength > 0))
'{
'int x = Convert.ToInt32(File1.PostedFile.InputStream.Length);//100009;
'string fileName = Path.GetFileName(File1.PostedFile.FileName);
'File1.PostedFile.SaveAs(@"C:\temp\" + fileName);
'File1.Dispose();
' While (x > 100000)
'{
'Bitmap disposablebmp = new Bitmap(@"C:\temp\" + fileName);
'Bitmap newbmp = new Bitmap(disposablebmp,
'new System.Drawing.Size(Convert.ToInt32(disposablebmp.Width*.75),
'Convert.ToInt32(disposablebmp.Height*.75)));
'disposablebmp.Dispose();
'disposablebmp = newbmp;
'FileInfo fi = new FileInfo(@"C:\temp\" + fileName);
' x = Convert
Comments are closed.