Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C#

Safe Images from Streams

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
9 Oct 2016CPOL6 min read 13.5K   262   6   2
Alleviate issues with Image objects created from streams and files.

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 Streams 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...

C#
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

C#
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

C#
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:

C#
using (Image temp = Image.FromStream(stream)) {
    _image = new Bitmap(_image);
}

Construct SafeImage from a File

C#
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.

C#
// Download an Image
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);
        }
    }
}
C#
// Get Database BLOB Images
DbConnection connection;    
// ... set up the 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));
        }
    }
}
C#
// Assign a SafeImage to a PictureBox
string tempImage = "myFile.jpg";
pictureBox1.Image = new SafeImage(tempImage);
File.Delete(tempImage);     // Succeeds

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Australia Australia
Been programming for 40 years now, starting when I was 13 on DEC PDP 11 (back in the day of paper tape storage, and hex switch boot procedures). Got right into micro-computers from an early age, with machines like the Dick Smith Sorcerer and the CompuColor II. Started CP/M and MS-DOS programming in the mid 1980's. By the end of the '80's, I was just starting to get a good grip on OOP (Had Zortech C++ V1.0).

Got into ATL and COM programming early 2002. As a result, my gutter vocabulary has expanded, but it certainly keeps me off the streets.

Recently, I have had to stop working full time as a programmer due to permanent brain damage as a result of a tumour (I just can't keep up the pace required to meet KPI's). I still like to keep my hand in it, though, and will probably post more articles here as I discover various tricky things.

Comments and Discussions

 
QuestionA couple of points. Pin
Pete O'Hanlon28-Dec-16 1:27
subeditorPete O'Hanlon28-Dec-16 1:27 
While this is an interesting project, I would have liked to see more code along inside the article. By that, I mean that I would have liked you to show some of the code behind the scenes along with explanations as to why you chose to implement things that way; what you rejected as being the wrong solution, that type of thing.

Images are "heavy" objects. It would have been interesting to see if you could have done a weak referenced version where the image could be unloaded and reloaded as necessary.
This space for rent

AnswerRe: A couple of points. Pin
Midi_Mick28-Dec-16 4:07
professionalMidi_Mick28-Dec-16 4:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.