Thumbnail Images

When I wrote my little PhotoPlay applet I quickly realised that loading large numbers of images into memory was slow and enormously expensive in terms of memory use. I remember the first time I pointed PhotoPlay at my year 2004 photo folder – I think I managed to kill the task at around 3Gb of virtual memory use.

So I did a quick search and found a great bit of code from Kourosh Derakshan that allows you to use the thumbnail images that are embedded into photos by almost all digital cameras. The images are actually stored as metadata against the real image along with all the other EXIF metadata tags such as camera model, flash type, focal length etc.

Using this code I was able to load 100s of photos in several seconds, as opposed to minutes, and the memory usage was amazingly small. Of course the gotcha is that if the image doesn’t have a thumbnail this method won’t help you. If the image didn’t come from a camera then chances are it won’t have a thumbnail – so really this is for digital photos only. [The main reason the thumbnail image is stored is because this is the image that the cameras use themselves when you browse photos on the camera.]

The key to this code is the System.Drawing.Image.FromStream() method. The overload used has a parameter “validateImageData” that if set to false means the image isn’t loaded into memory. Not obvious and certainly not apparent from reading the documentation! You end up with an Image instance that still allows you access the metadata properties without having any overhead of processing the image itself.

    // Written by Kourosh Derakshan - minor mods by Nigel Spencer.
        /// <summary>
        /// Gets the thumbnail from the image metadata. Returns null if no thumbnail
        /// is stored in the image metadata
        /// </summary>
        /// <param name="path">The full path to the image.</param>
        /// <returns>The thumbnail image contained within the metadata (EXIF) of the image, or 
/// <see langword="null"/> if no thumbnail data is present.</returns> /// <remarks> /// The ExifTag metadata is copied from the original image to the thumbnail that is returned. /// </remarks> public static Image GetThumbnail (string path) { FileStream fileStream = null; try { fileStream = File.OpenRead(path); // Last parameter tells GDI+ not the load the actual image data Image originalImage = Image.FromStream(fileStream, false, false); // GDI+ throws an error if we try to read a property when the image // doesn't have that property. Check to make sure the thumbnail property // item exists. bool propertyFound = false; for (int i = 0; i < originalImage.PropertyIdList.Length; i++) if (originalImage.PropertyIdList[i] == (int)ExifTags.ThumbnailData) { propertyFound = true; break; } if (!propertyFound) return null; PropertyItem thumbnailPropertyItem = originalImage.GetPropertyItem((int)ExifTags.ThumbnailData); // The image data is in the form of a byte array. Write all // the bytes to a stream and create a new image from that stream byte[] imageBytes = thumbnailPropertyItem.Value; MemoryStream stream = new MemoryStream(imageBytes.Length); stream.Write(imageBytes, 0, imageBytes.Length); Image thumbnailImage = Image.FromStream(stream); // Copy all the original properties to the thumbnail. for (int i = 0; i < originalImage.PropertyIdList.Length; i++) { PropertyItem itemToCopy = originalImage.GetPropertyItem(originalImage.PropertyIdList[i]); thumbnailImage.SetPropertyItem(itemToCopy); } originalImage.Dispose(); return thumbnailImage; } finally { if (fileStream != null) { fileStream.Dispose(); } } }

So this method has been very useful to me, but in the last week I’ve been trying to do something similar in WPF. My first attempt was to just jump straight in and bind a ListBox to some images. This was incredibly slow to load – I mean horrendous! I wanted to use the GetThumbnail method but of course WPF doesn’t understand System.Drawing.Image – I need to use System.Windows.Media.ImageSource instead. I’ve messed briefly with DecodePixelWidth and Height but these only seem to control the size of the image that is held in memory (which is good) but it still takes forever to produce the thumbnail from the full image. I also noticed that the BitmapFrame class, not the BitmapSource seems to be more helpful because it allows Thumbnail and metadata access.

Well – its late – so it’ll have to be a job for tomorrow. Hopefully I’ll find an equivalent way of doing this in WPF that works and be able to blog the solution. Assuming I can get it working in WPF I’ll also include some performance comparisons against the method shown here.