Scott Hanselman

The Weekly Source Code 50 - A little on "A generic error occurred in GDI+" and trouble generating images on with ASP.NET

February 18, 2010 Comment on this post [12] Posted in ASP.NET | Source Code
Sponsored By

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

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.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service
February 18, 2010 13:31
Seems like you get that error message when pretty much anything goes wrong in GDI+. I got it when I called Save when the folder I was trying to save to didn't exist. Not had any problems myself with System.Drawing on the server-side. Be nice if it was supported, since it's the simplest way to fiddle with images
February 18, 2010 13:37
Thanks for the tip!
Perhaps I'm missing something obvious, but why does it only fail in IIS and not in the VS Web Dev Server?
February 18, 2010 14:08
How about Direct2D:

http://blogs.msdn.com/directx/archive/2009/09/11/using-direct2d-for-server-side-rendering.aspx
February 18, 2010 15:11
So why don't you get a NotSupportedException from the Stream?
February 18, 2010 15:39
@Frank, because the VS Web Dev Server runs as a regular Windows Forms application, IIS runs as a service.
February 18, 2010 16:15
So, what you are saying is that MS doesn't support any kind of image manipulation in ASP.NET. I haven't used it yet, but the project I'm working on will need some kind of image manipulation, and the server is far away from full trust. Do you know any good libraries or controls? The link you posted above doesn't work.

Thanks

Vladimir
February 18, 2010 17:03
I've run into a similar problem last week, and did the same as you, an intermediate MemoryStream solved the problem.

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 ;)
February 18, 2010 18:20
The Codeplex link appears to be broken.
February 18, 2010 21:30
Check space on disk, you may be running low.
February 19, 2010 2:24
I got the same error on poorly-configured (but otherwise displayable) images from the web and sometimes seemingly completely at random intervals.

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?
February 19, 2010 13:52
Broken Link: the one with the text "some ASP.NET Controls on CodePlex that might help." should link to this post about asp.net futures
February 25, 2010 0:30
I keep this snippit right inside my sub procedure that makes and saves thumbnail and image from uploaded camera picture. Every once and a while I get that generic error and will use this code someday. . .


'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.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.