One of the most frustrating things you run into when working with image files, Images objects, and PictureBoxes is that once you release your application, some users will get “file locked” errors when trying to manipulate files that you have displayed onto the screen via a picturebox. Finding a workaround can be downright maddening: It is sometimes hard to replicate the problem, and it is difficult to open an image and display it without displaying the image you have just opened from a file .
It does not help that on some machines the problem manifests itself as an “unknown GDI+ error,” so you might spend many hours chasing this unknown error only to realize it’s the same as the “file in use by another process” error you get from other machines.
Microsoft makes the problem worse, giving misinformation in Knowledge Base articles such as these: http://support.microsoft.com/kb/311754. This article gives a work around that does not fix the problem everywhere and it goes on to state this behavior is “by design.” Putting aside the issue of how someone could put in bugs such as these “by design,” the fact that the behavior is not consistent makes this a bug anyway.
If you do a search for the problem you might run into these suggested fixes:
- Dispose of the Image object before saving a file
- Dispose of the Image object before saving a file, and then call the garbage collector to make sure the Image really is disposed
- Make a copy of the Image before attaching it to the PictureBox (It seems .NET is way too smart to fall for that one and somehow still tracks where the image came from)
- Use the Image.Clone() method to make a copy of the Image before attaching to the PictureBox
- Use a FileStream to open the image
There are many, many posts on this issue all over the internet, and believe me, I read all of them, and none gave me a full answer, although all pointed me in the right direction: The less “connected” the image file was to the Image object, the better off everyone would be.
So how do we fix it? This seems to be a bug in Windows since at least the VB6 days, so we might as well work around for it.
After much hair pulling and trial and error, here is what finally worked for me: Open the file containing the image, and, in the most indirect, sneaky way possible, assign the file contents to the Image object which you will use to display the image in a PictureBox. Do the same when saving an Image to a file. Do not mix Image objects and files and everyone will be happy. Here are the general steps:
- To open an image file:
- Open the graphic file, but not into an Image object, open the file into a byte array.
- Convert the byte array into an Image object (the conversion will figure out which format (PNG, JPG, etc…) the file is using)
- Use that Image object to assign to a PictureBox to display to the user
- Do NOT call the Image’s .Save() method, as that ties the Image to a file. See below…
- To save an image file:
- Clone the image via the Image.Clone() method
- Save the cloned image to a MemoryStream, using your preferred encoding (PNG, JPG, etc…)
- Convert the stream to a byte array
- Save the byte array to a file
Here is some code that might help you:
To read the file:
public static Image NonLockingOpen(string filename) { Image result; #region Save file to byte array long size = (new FileInfo(filename)).Length; FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read); byte[] data = new byte[size]; try { fs.Read(data, 0, (int)size); } finally { fs.Close(); fs.Dispose(); } #endregion #region Convert bytes to image MemoryStream ms = new MemoryStream(); ms.Write(data, 0, (int)size); result = new Bitmap(ms); ms.Close(); #endregion return result; }
To write the file:
public static void NonLockingSave(Image img, string fn, ImageFormat format) { // Delete destination file if it already exists if (File.Exists(fn)) File.Delete(fn); try { #region Convert image to destination format MemoryStream ms = new MemoryStream(); Image img2 = (Image)img.Clone(); img2.Save(ms, format); img2.Dispose(); byte[] byteArray = ms.ToArray(); ms.Close(); ms.Dispose(); #endregion #region Save byte array to file FileStream fs = new FileStream(fn, FileMode.CreateNew, FileAccess.Write); try { fs.Write(byteArray, 0, byteArray.Length); } finally { fs.Close(); fs.Dispose(); } #endregion } catch { // Delete file if it was created if (File.Exists(fn)) File.Delete(fn); // Re-throw exception throw; } }
And finally, here is an extension method that will make your life easier:
public static void SaveViaStreams(this Image img, string fn, ImageFormat format) { NonLockingSave(img, fn, format); }
Once I used these methods to handle file opens/saves all my file locking errors went away, on all computers that used my system. Hope this helps you as well!
Great !
Worked like a charm, thank you very much for posting this!
I translated this to VB.NET but it did not work.
It Worked Great!! TYV for the share, keep the good work
thanks a lot.
great work boss….
quentininsa: Sorry, I never have worked with TIFF images…
Thanks for the info!
Any idea on how to minimize the memory consumption of a Tiff Image. Currently I convert it to BMP, but was thinking perhaps drawing to a JPeg might be more memory friendly??
I got error at this line :
—————————-
img2.Save(ms,imf);
—————————–
Error Message :
“A generic error occurred in GDI+.”
did u got a solution i m having the same situation
Hi,
I dont usually comment on articles I find on the internet but I wanted to take a minute to say thank you for posting this solution. Just saved me pulling whats left of my hair out.
Great technique – will always use this from now on!
Many thanks,
James
All I want to do is open an image, flip it 180 degrees and save it.
I get the “file in use” by another process message with this usage:
public static void FlipImage(string imagePath)
{
try
{
System.Drawing.Image originalImage = NonLockingOpen(imagePath);
originalImage.RotateFlip(RotateFlipType.Rotate180FlipNone);
NonLockingSave((System.Drawing.Image)originalImage.Clone(), imagePath, System.Drawing.Imaging.ImageFormat.Gif);
}
catch (Exception ex)
{
throw ex;
}
}
Excellent Code. Working fine without a single change. Now i am opening /saving all images after any operation using NoLock to avoid any system crash.
thanks a lot.
Share this link on my facebook.
Thx a lot dude (:
Robin,
The code is
Image img2 = (Image)img.Clone();
You are cloning img2, not img.
I tried implimenting your code in my project but
Image img2 = (Image)img2.Clone();
keeps giveing me an error.
Error 1 Use of unassigned local variable ‘img2’
any ideas would be greatly apreciated.
I love you in all the right and wrong ways. Thanks a lot!
Like the others…it worked perfectly for me. Thanks for the very descriptive blog.
Couldn’t figure this one out either, thanks to you my code is working great now!!!
I was pulling my hair out also until I found this information. The only problem I had with the code was that I would get a typlicaly unhelpfull error message of “A generic error occurred in GDI+” when I would attempt to fill my picturebox with your code. I did a little research and found that “You must keep the stream open for the lifetime of the Image.” Once I commented out “ms.Close();” the code worked fine and I am able to delete the root image as needed. Thanks so much for posting, it was very helpful.