Introduction
When an Image
object is created from any type of Stream
, that Image
is likely to demonstrate some idiosyncratic behavior down the track. The reason for this is explained by the single line of the MSDN documentation:
Quote:
You must keep the stream open for the lifetime of the Image.
Sometimes, knowing the life of a Stream
can be difficult. Even more difficult can be knowing when to eventually dispose of that Stream
when a reference to it has been maintained. This article provides a class to address those issues.
A related issue is that if an Image
object is created from a file, that file is locked for the life of the object. This means that creating temporary files for images in your application means that those files cannot be deleted until the image has been fully disposed.
Using the Code
The are two basic solutions to these issues. The most common one to just not close the Stream
that created the Image
. However, this can be troublesome because (a) there are open Stream
s all over your application just sitting there using up resources after they are no longer needed, and (b) It is feasible that the GC
may notice the orphaned stream and dispose of it while the Image
is still current, causing it to display the same odd behavior as if you had closed the stream.
The SafeImage
class addresses these above issues by duplicating the Stream
, and then creating the Image
from that duplicate. References to both the duplicate Stream
and the Image
are then maintained and disposed of together. It is still up to you, however, to properly dispose of the original Stream
.
The other possible and oft implemented solution is to create a copy of the Image
and use that instead of the one that has the above mentioned issues. This is a good and reliable solution providing you dispose of the original image as soon as it is copied. The only issue with this solution is that copying an image can be slow, so if you are doing it a lot, it can impact the performance of your application. The SafeImage
will optionally use this method if so desired.
You tell the SafeImage
which algorithm to use to protect the Image
's integrity by passing the optional UseStreamType
enumeration value to the constructor. The possible values for this parameter are:
UseStreamType.None | Uses the Stream to create a temporary Image , and then maintains a duplicate of that. It should be noted that using this type of construction actually explicitly creates a Bitmap , so the internal Image can safely be cast accordingly. |
UseStreamType.MemoryStream | Duplicates the Stream into an internal MemoryStream , and then creates the new Image from that., The MemoryStream is kept alive for the life of the SafeImage . |
UseStreamType.TempFileStream | Copies the original Stream to a temporary file, opens the file, and then creates the Image from that FileStream . |
UseStreamType.BestFit | Uses TempFileStream if the Length of the original stream is greater than an arbitrary static MaxMemoryStream value (currently set to ~200Mb), otherwise uses MemoryStream . This is the default for most of the constructors. |
A SafeImage
can be used basically as a drop-in replacement for an Image
. This is achieved by defining implicit
operators to cast the SafeImage
to or from an Image
. This means that code like...
SafeImage img = new SafeImage(filename);
pictureBox1.Image = img;
...will just work. The image returned is actually a copy of the one being saved, as the SafeImage
cannot know what the receiver is likely to do with it (e.g. disposing of it could be nasty).
All of the work of creating the SafeImage
is performed in the various constructors. The static FromXxxx
methods simply call the constructor with the matching signature. Each of the constructors are described below.
Construct SafeImage from an Existing Image
SafeImage(Image image, UseStreamType streamType = UseStreamType.None);
Duplicates the provided Image
. By default, this method uses the Bitmap.FromImage
method to create the new Image
. However, if one of the UseStreamType.MemoryStream
, UseStreamType.TempFileStream
, or UseStreamType.BestFit
options are passed, this constructor will create the appropriate Stream
, save the original Image
to it, and then create the new Image
from the newly created Stream
. The SafeImage.FromBitmap
method calls this constructor to create its return value.
Construct SafeImage from a Stream
SafeImage(Stream stream, UseStreamType streamType = UseStreamType.BestFit);
SafeImage(Stream stream, bool useEmbeddedColorManagement,
UseStreamType streamType = UseStreamType.BestFit);
SafeImage(Stream stream, bool useEmbeddedColorManagement,
bool validateImageData, UseStreamType streamType = UseStreamType.BestFit);
Each of the above constructors creates a new Stream
of the appropriate type (MemoryStream
or FileStream
), and copies the contents of the original Stream
to that new one. It then uses the Image.FromStream
method with the corresponding signature to create the Image
from the duplicate Stream
. A reference to this duplicate Stream
is kept in the SafeImage
, and not closed until the SafeImage
is disposed.
If these constructors are passed the UseStreamType.None
option, then a temporary Image
is created directly from the input Stream
. The new Image
is then created by duplicating the temporary Image
, and finally the temporary Image
is disposed of. The code looks like this:
using (Image temp = Image.FromStream(stream)) {
_image = new Bitmap(_image);
}
Construct SafeImage from a File
SafeImage(string filename, UseStreamType streamType = UseStreamType.BestFit);
SafeImage(string filename, bool useEmbeddedColorManagement,
UseStreamType streamType = UseStreamType.BestFit);
The above constructors work by opening the file, and copying the contents to a new MemoryStream
or FileStream
as appropriate. They then close the intermediate FileStream
, and create the Image
from the duplicate data. What this does is release the locks on the file, so the application is then able to perform any other operations on it as may see fit.
As with the Stream
type constructors, if these constructors are passed the UseStreamType.None
value, then a temporary Image
is created from the opened FileStream
, and then duplicated. Both the FileStream
and the temporary Image
are then disposed of.
Some Examples
Some of the particularly useful applications for the SafeImage
are downloading images, or reading images from a database BLOB field.
SafeImage image;
using (HttpWebRequest request = (HttpWebRequest)WebRequest.Create ("http://someImageUrl") {
using(HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
using (Stream responseStream = response.GetResponseStream()) {
image = new SafeImage(responseStream);
}
}
}
DbConnection connection;
List<SafeImage> images = new List<SafeImage>();
using (DbCommand cmd = connection.CreateCommand()) {
cmd.CommandText = "SELECT Image FROM Images WHERE Image IS NOT NULL";
using (DbDataReader rs = cmd.ExecuteReader()) {
while (rs.Read()) {
images.Add(new SafeImage(rs.GetStream(0));
}
}
}
string tempImage = "myFile.jpg";
pictureBox1.Image = new SafeImage(tempImage);
File.Delete(tempImage);
Points of Interest
Sometimes, I have noticed that the operating system can take a short time to release all the locks on a file after it has been closed. As such, when deleting the temporary file created by a SafeImage
with the UseStreamType.TempFileStream
flag, if the delete fails, I retry every ΒΌ second for up to ~10 seconds. So the whole system is not held up while this is happening, I have put this into a separate thread. 99% of the time, the first or second attempt will succeed, so this thread will terminate very quickly. But if some other operation is holding on to this file, or as does occasionally happen, the operating system gets its knickers in a knot and has its lock counts all mucking fixed up, it still does not hold up the entire application. This means that normally your application will clean up nicely after itself, with a worst case scenario that a file is left in the temp folder to be cleaned up by the user's regular maintenance procedures.