When dealing with GDI+ in .Net (e.g. certain objects in System.Drawing namespace), we would sometimes get the following exception:

System.Runtime.InteropServices.ExternalException: "A generic error occurred in GDI+."

Debugging this exception can be extremely frustrating as it can happen under many different circumstances (here and here), and the error message is too generic to provide any useful information.

This exception mostly occurs when trying to write over an image opened by an Image or Bitmap object. For example, the following code snippet will generate this error:

            Image img = Image.FromFile(fileName);
            try
            {
                img.Save(fileName); //throws exception!
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);

            }

The method listed above tries to open an image file, process the image (code not shown) and then save the image under the same file name. Unfortunately, due to the way GDI+ handles images, the image file was locked during the whole life time of the img object. And thus an exception was thrown from the underlying Windows API.

Based on Microsoft’s Knowledgebase article’s recommendations, we can prevent this exceptions in two ways (see the article for details):

1. Create a non-indexed image:

        public void Method1()
        {
            Image img = Image.FromFile(fileName);
            Bitmap bmp = img as Bitmap;
            Graphics g = Graphics.FromImage(bmp);
            Bitmap bmpNew = new Bitmap(bmp);
            g.DrawImage(bmpNew, new Point(0, 0));
            g.Dispose();
            bmp.Dispose();
            img.Dispose();
 
            //code to manipulate bmpNew goes here.
 
            bmpNew.Save(fileName);
        }

2. Create an indexed image:

        public void Method2()
        {
            Image img = Image.FromFile(fileName);
            Bitmap bmp = img as Bitmap;
 
            BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
 
            int byteCount = bmpData.Stride * bmpData.Height;
            byte[] bytes = new byte[byteCount];
 
            Marshal.Copy(bmpData.Scan0,  bytes,0, byteCount);
            bmp.UnlockBits(bmpData);
 
            Bitmap bmpNew = new Bitmap(bmp.Width, bmp.Height);
            BitmapData bmpData1 = bmpNew.LockBits(new Rectangle(new Point(), bmpNew.Size), ImageLockMode.ReadWrite, bmp.PixelFormat);
            Marshal.Copy(bytes, 0, bmpData1.Scan0, bytes.Length);
            bmpNew.UnlockBits(bmpData1);
            bmp.Dispose();
 
            //code to manipulate bmpNew goes here.
            bmpNew.Save(fileName);
        }

As can be seen, both methods utilize a secondary image to hold a copy of the original image and makes modifications there. The second image is effectively decoupled from the original one since an explicit copy is made in either method.

The source code of this article can be downloaded here.

Be Sociable, Share!