11

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.

Michael
  • 913
  • 3
  • 10
  • 18

2 Answers2

16

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)
{
  using (MemoryStream memoryStream = DecryptImageBinary(Settings.Default.ImagePath + filename, _cryptPassword))
  {
      using (tmpImage = Image.FromStream(memoryStream))
      { 
         image = new Bitmap(tmpImage);
      }
  }

}

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
  • 33,454
  • 26
  • 120
  • 175
  • 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? – Michael Nov 23 '09 at 17:29
  • 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 Nov 24 '09 at 06:06
  • 1
    "using" constructs would be better than explicit Close() and Dispose() calls. – Brian Low Apr 21 '11 at 15:06
  • After the program has leaked 10,000 handles Windows won't give it anymore. You can diagnose these kind of leaks with TaskMgr.exe, Processes tab. View + Select Columns to add the columns for Handle, USER Objects and GDI Objects. GDI Objects is the one that's steadily increasing. – rboy Oct 19 '18 at 00:08
  • To add to the accepted answer from Kris, if you're seeing that the GDI error is thrown after a while, here is how to confirm that it's leaking GDI memory which is causing the exception. After the program has leaked 10,000 handles Windows won't give it anymore. You can diagnose these kind of leaks with TaskMgr.exe Click on Processes tab Right click on the columns and Select Columns to add the columns for GDI Objects If GDI Objects is the one that's steadily increasing - then you have a GDI memory leak. – rboy Oct 19 '18 at 18:09
2

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
  • 33,454
  • 26
  • 120
  • 175