tags:

views:

919

answers:

2

Hi,

I've got a global Graphics object created from a Panel. At regular intervals an image is picked up from the disk and drawn into the panel using Graphics.DrawImage(). It works fine for a few iterations and then I'm getting the following helpful exception:

System.Runtime.InteropServices.ExternalException: A generic error occurred in GDI+.
at System.Drawing.Graphics.CheckErrorStatus(Int32 status)
at System.Drawing.Graphics.DrawImage(Image image, Int32 x, Int32 y)
at System.Drawing.Graphics.DrawImage(Image image, Point point)

I ruled out memory leaks as I dispose of the image object when I'm done with it. I know that the images are not corrupted and can be read fine as the program executes fine for a while before the panel stops showing.

I ran into the same problem when using a PictureBox but this time at least I got an error instead of nothing.

I checked the GDI objects and USER objects in the Task Manager but they're always around 65 user objects and 165 GDI objects when the app works and when it doesn't.

I do need to get to the bottom of this as soon as and it's not like I can stick breakpoints in .NET System libraries and see where exactly execution fails.

Thanks in advance.

EDIT: This is the display code:

private void DrawImage(Image image)
{
  Point leftCorner = new Point((this.Bounds.Width / 2) - (image.Width / 2), (this.Bounds.Height / 2) - (image.Height / 2));
  _graphics.DrawImage(image, leftCorner);
}

the image load code:

private void LoadImage(string filename, ref Image image)
{
  MemoryStream memoryStream = DecryptImageBinary(Settings.Default.ImagePath + filename, _cryptPassword);

  image = Image.FromStream(memoryStream);

  memoryStream.Close();
  memoryStream.Dispose();
  memoryStream = null;
}

_image is global and its reference is updated in LoadImage. They are passed as parameters as I want to change the global references from as few places as possible only and keep the other methods self contained. _graphics is also global.

I've also got a webBrowser control for web sites and I either show an image or a website at one time. when there's time to display an image, the following code executes:

webBrowser.Visible = false;
panel.Visible = true;
DrawImage(_image)
_image.Dispose();
_image = null;

_image is referencing a pre-loaded image.

Hope this helps.

+1  A: 

Without a little more code there is not enough to properly diagnose here, however, one thing to look at is that you may have disposed on the image your a drawing with at some point earlier and it is only after the garbage collector runs that your code is failing. Are you using cloned images anywhere? One thing I was suprised to learn is that if you do a straight clone of an image, you are not cloning the underlying bitmap that the image rely's upon, only the image structure, to create a proper copy of an image you have to create a new image:

var newImage = new Bitmap(img)

as

var newImage = oldImg.Clone();
oldImg.Dispose();
...
gr.DrawImage(newImage, new Rectangle(0,0,newImage.Width,newImage.Height);

will work for a while, but then fail at some random point...

Kris Erickson
I've put in some more code now. Hope that will shed some light.
FrancisCastiglione
Just to keep things clear I have posted the answer to your problem below.
Kris Erickson
+2  A: 

Your problem is similar to what I thought, but not quite. When you are loading the image, you are loading it from a MemoryStream. You have to keep the stream open for the lifetime of the image, see MSDN Image.FromStream.

You must keep the stream open for the lifetime of the Image.

The solution is to make a copy of your image in the FromImage function:

private void LoadImage(string filename, ref Image image)
{
  MemoryStream memoryStream = DecryptImageBinary(Settings.Default.ImagePath + filename, _cryptPassword);

  var tmpImage = Image.FromStream(memoryStream);
  image = new Bitmap(tmpImage);
  tmpImage.Dispose();

  memoryStream.Close();
}

Similar to the dispose problem I mentioned, the image will seem to work and then randomly fail when the underlying stream is garbage collected.

Kris Erickson
I'll try that, thanks. Do we not need to dispose tmpImage explicitly? Will the disposal of the memoryStream dispose of the underlying image as well?
FrancisCastiglione
Yeah, you should dispose of tmpImage explicitly, although the disposal of the memoryStream will garbage collect most of the underlying data. I've changed the answer to show that.
Kris Erickson
Thanks. That seems to have solved it.
FrancisCastiglione