.NET Forum / Languages / C# / January 2008
Image from file (release handle)
|
|
Thread rating:  |
Steve K. - 10 Jan 2008 07:56 GMT I have a method that I use to get a System.Drawing.Image from a file without keeping a handle on the file open (so I can delete the file). Here is the code:
<code> public static Image ImageFromFileReleaseHandle(string filename) { FileStream fs = null; try { fs = new FileStream(filename, FileMode.Open, FileAccess.Read); return Image.FromStream(fs); } finally { fs.Close(); } } </code>
It's worked great for years... until I tried loading a multi-frame(page) tiff. When loading a tif using the above method I'm unable to call SetActiveFrame() to any frame index other than 0. When I do, it throws the dreaded "Generic GDI+ Error..."
If I use System.Drawing.FromFile() I can work with the TIF no problem - problem is I can't delete the source file.
So what I'm wondering is how is Image.FromFile() different than what I'm doing? Does anyone know what, if anything that method is doing that is special?
Any help greatly appreciated.
-Steve
Peter Duniho - 10 Jan 2008 08:16 GMT > [...] > It's worked great for years... until I tried loading a multi-frame(page) [quoted text clipped - 9 lines] > doing? Does anyone know what, if anything that method is doing that is > special? Nothing special. It's just that if you use FromFile(), the Image hangs on to the file resource for you. I believe that if you didn't close the stream, you'd find that SetActiveFrame() works as expected.
In other words, the issue here is that a TIFF-based Image requires the file to remain open in order to navigate the frames in the file. It just happens that using FromFile(), that retention of the file resource happens automatically, while in the other case you're explicitly discarding the file resource.
I see a few options, in decreasing order of preference (for my taste, anyway):
* Change the logic so that you don't close the stream/dispose the Image until you actually want to delete the file. This of course would mean that once the file's deleted, the Image is no longer valid either (or at least cannot be switched to a different page of the TIFF, in the stream case)
* Read the entire file into a MemoryStream and use that stream as the source for the Image instead. This the most flexible, but given the potential for arbitrarily large TIFF files it could unnecessarily limit your ability to display any arbitrary TIFF file.
* Copy the original file to a temp file, and use that file as the source for the Image instead. This has the problem that for small files, you might as well just read them into memory, and for large files, copying the file may cause a prohibitive delay in presenting the image. But at least it would work for arbitrarily large files, assuming enough disk space to copy the file.
Pete
Steve K. - 10 Jan 2008 08:42 GMT >> [...] >> It's worked great for years... until I tried loading a multi-frame(page) [quoted text clipped - 13 lines] > to the file resource for you. I believe that if you didn't close the > stream, you'd find that SetActiveFrame() works as expected. Hi, thanks for the reply! That makes sense now, I had always thought that Image.FromFile() would load the entire contents into the Image object and not *need* a handle to the file.
> In other words, the issue here is that a TIFF-based Image requires the > file to remain open in order to navigate the frames in the file. It just [quoted text clipped - 10 lines] > at least cannot be switched to a different page of the TIFF, in the stream > case) This is probably what I will end up doing. The reason I'm trying to avoid using files is that all these TIF images are coming from BLOBS out of a DB, I generally design my applications to make is little contact with the file system as possible, no good reason... just my style. I was stuck on the idea of avoiding having open handles out there that I need to add cleanup code to make sure they aren't left behind.
> * Read the entire file into a MemoryStream and use that stream as the > source for the Image instead. This the most flexible, but given the > potential for arbitrarily large TIFF files it could unnecessarily limit > your ability to display any arbitrary TIFF file. True and I need to manage the Stream lifecycle to make sure it's relased - not difficult, just added cleanup.
> * Copy the original file to a temp file, and use that file as the > source for the Image instead. This has the problem that for small files, [quoted text clipped - 4 lines] > > Pete In a frantic solution-finding flurry I came up with a hack of sorts, I'm still not sure what I think of it. Check it out: <code> _attachment = Image.FromFile(path);
// Get the frames into an array _attachmentPages = ImageUtils.MultiFrameImageToArray(_attachment, FrameDimension.Page);
// Dispose the tif Image so we can delete the file _attachment.Dispose();
if (System.IO.File.Exists(path)) { System.IO.File.Delete(path); }
public static Image[] MultiFrameImageToArray(Image source, FrameDimension frameDimension) { int numFrames = source.GetFrameCount(frameDimension);
Image[] frames = new Image[numFrames]; for(int i = 0; i < numFrames; i++) { source.SelectActiveFrame(frameDimension, i); frames[i] = new Bitmap(source); }
return frames; } </code>
Thanks again for the reply, I'll think about your suggestions. I feel better after you've explained that the TIF needs an open stream to it's base data, that would explain why GetFrameCount() returned a valid value (it's stored in the header) but attempts to acces the frames failed (stream closed) - always nice to know *WHY* something is failing ;0)
-Steve
Peter Duniho - 10 Jan 2008 09:31 GMT > In a frantic solution-finding flurry I came up with a hack of sorts, I'm > still not sure what I think of it. Check it out: That's essentially the same as copying the file into a MemoryStream, except that it's even worse because it will decompress the file data into memory. At least if you copy the file into a MemoryStream, whatever compression is used in the TIFF (if any) still benefits you.
I'm glad you got a solution that works, just to prove it can be done. But I don't think that's going to be the way you want to go, ultimately. :)
Pete
Steve K. - 10 Jan 2008 08:22 GMT According to reflector Image.FromStream() and Image.FromFile() are both calling the same internal GDI methods. Well, not the same but they ARE GDI methods (I think) It's hard to explain. What I'm saying is that they both look like they work the same way.
Sheesh!
So then I had this bright idea! <code> Image i = Bitmap.FromFile(filename); Guard.ReferenceNotNull(i, "Failed loading Image from file: " + filename);
Image j = i.Clone() as Image; i.Dispose(); return j; </code>
It doesn't allow me to delete the file either "File locked by another process...." if I dispose j as well, then I can delete the file. So it appears that close still keep some reference to the source object? That doesn't sound right.
And finally: <code> Image i = Bitmap.FromFile(filename); Guard.ReferenceNotNull(i, "Failed loading Image from file: " + filename);
// try painting one into the other Image freeCopy = new Bitmap(i); i.Dispose(); return freeCopy; </code>
This drops all the frames from the tiff. I can delete the source file, but th resultant Image is junk.
The only last option I have is to load the tiff, grab all the frames into an Image[], dispose of the source tiff Image, delete the source file and just work with the Image[] array. This will work for my needs, but I imagine for other it won't.
If anyone has any ideas or suggestion I would really like to hear them.
-Steve
>I have a method that I use to get a System.Drawing.Image from a file >without keeping a handle on the file open (so I can delete the file). Here [quoted text clipped - 31 lines] > > -Steve Peter Duniho - 10 Jan 2008 08:42 GMT > [...] > If anyone has any ideas or suggestion I would really like to hear them. Is it safe to assume that you hadn't seen my reply when you wrote that post?
The behaviors you've seen with the things you've tried all seem unsurprising to me. It wouldn't make sense for the object class to read in the entire TIFF implicitly, even when you clone it. It's just too inefficient to make that the general behavior.
Hopefully something in my previous post helps.
Pete
Steve K. - 10 Jan 2008 09:44 GMT >> [...] >> If anyone has any ideas or suggestion I would really like to hear them. > > Is it safe to assume that you hadn't seen my reply when you wrote that > post? Hi Peter, yes, I sent this before seeing your reply. Your post made a lot of sense, thank you again.
> The behaviors you've seen with the things you've tried all seem > unsurprising to me. It wouldn't make sense for the object class to read [quoted text clipped - 4 lines] > > Pete RobinS - 10 Jan 2008 09:03 GMT This is what I'm using to read images w/o locking the file.
public static Image LoadImageFromFile(string fileName) { Image theImage = null; using (FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { byte[] img; img = new byte[fileStream.Length]; fileStream.Read(img, 0, img.Length); fileStream.Close(); theImage = Image.FromStream(new MemoryStream(img)); img = null; } GC.Collect(); return theImage; }
Hope it works for you.
RobinS. GoldMail, Inc. ------------------------
>I have a method that I use to get a System.Drawing.Image from a file >without keeping a handle on the file open (so I can delete the file). Here [quoted text clipped - 31 lines] > > -Steve Steve K. - 10 Jan 2008 09:45 GMT Hi Robin,
Thanks for the reply, that is what I've been doing as well for most other image types. It's the TIFF that is the problem, as Peter pointed out the a TIFF loaded into an Image still needs access to it's source stream.
Thanks, Steve
> This is what I'm using to read images w/o locking the file. > [quoted text clipped - 55 lines] >> >> -Steve Kevin Spencer - 10 Jan 2008 16:03 GMT I've dealt with similar problems when working with Tiffs in particular, and with GeoTiffs specifically, since that is what sort I work with primarily. I ended up writing my own Tiff class, which initially only parses the Tiff tags using a StreamReader, and creates a Collection of "ImageFileDirectory" instances, one for each image. The Image property of the ImageFileDirectory class reopens a StreamReader and gets the image out of the file. This is also useful with Tiffs that are tiles rather than stripped, as I haven't found a .Net class that can parse tiled Tiff format images, but my class can work with either. Also, because these images are so large, not loading the entire file saves a good bit of memory.
I was then able to create several derivatives of the class, a GeoTiff class that recognizes the GeoTiff tags and can parse the Geographic information in them, and a couple of derivatives of that for parsing several varieties of GeoTiffs.
The Adobe Tiff file format is an open specification.
 Signature HTH,
Kevin Spencer Chicken Salad Surgeon Microsoft MVP
> Hi Robin, > [quoted text clipped - 64 lines] >>> >>> -Steve Peter Duniho - 10 Jan 2008 18:48 GMT > Thanks for the reply, that is what I've been doing as well for most other > image types. It's the TIFF that is the problem, as Peter pointed out > the a > TIFF loaded into an Image still needs access to it's source stream. Actually, the code Robin posted is exactly my second suggestion: it reads the file entirely into a MemoryStream and uses that instead of the original file as the source for the image.
It should work fine as long as the files aren't too big.
Pete
Steve K. - 11 Jan 2008 05:56 GMT > This is what I'm using to read images w/o locking the file. > [quoted text clipped - 19 lines] > RobinS. > GoldMail, Inc. Hi Robin,
I have a newbie question: Is the MemoryStream safe from GC as long as the Image is holding onto it? Once the image is disposed (either by me explicitly or by the GC) the stream will be closed?
Thanks again for posting the code, Steve
Peter Duniho - 11 Jan 2008 08:17 GMT > I have a newbie question: Is the MemoryStream safe from GC as long as > the > Image is holding onto it? Once the image is disposed (either by me > explicitly or by the GC) the stream will be closed? I would expect a call to Dispose() to release the MemoryStream, but in the worst case you would have to release the reference to the Image itself.
In either case, you should not have to worry about maintaining a reference to the MemoryStream yourself.
Pete
Peter Duniho - 11 Jan 2008 08:36 GMT >> I have a newbie question: Is the MemoryStream safe from GC as long as >> the [quoted text clipped - 4 lines] > the worst case you would have to release the reference to the Image > itself. To elaborate: if you'll recall, calling Dispose() on the Image releases the file for deletion. So it stands to reason doing so would also release a MemoryStream.
By the way, when something like this comes up and you want to investigate so that you know for sure what the behavior is, you can take advantage of the WeakReference class. For example, in this case you'd initialize an instance of WeakReference with the MemoryStream, create the Image from the MemoryStream, call Dispose() on the Image, and then force a garbage collection using the GC class.
If the WeakReference target survives (check WeakReference.IsAlive), then calling Dispose() isn't releasing the reference.
Obviously this is something you'd do in a test program. No point in putting code like that in your actual useful program. :)
Pete
Steve K. - 11 Jan 2008 10:06 GMT >>> I have a newbie question: Is the MemoryStream safe from GC as long as >>> the [quoted text clipped - 23 lines] > > Pete Right on, thanks Pete, that's some good debugging help and God knows I do a lot of debugging!
Mythran - 10 Jan 2008 16:51 GMT > I have a method that I use to get a System.Drawing.Image from a file > without keeping a handle on the file open (so I can delete the file). [quoted text clipped - 31 lines] > > -Steve You stated in another post that your image data is coming directly from a database blob field. Why don't you just take this data and write it to a MemoryStream and then do a FromStream method call to create the image? This way, you don't have to create/delete a file on the file system.
HTH, Mythran
Steve K. - 11 Jan 2008 05:58 GMT >> I have a method that I use to get a System.Drawing.Image from a file >> without keeping a handle on the file open (so I can delete the file). [quoted text clipped - 39 lines] > HTH, > Mythran Good point! :0)
As the MemoryStream seems to be the consensus and my own research is indicating that is is indeed the best solution (for me) I will be going that route.
Thanks everyone for the great ideas and support, this was a good thread, lots of help.
Take care, Steve
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|