Dealing with Images with Bad Metadata - Corrupted Color Profiles in WPF
Creating a Twitter client is a really interesting exercise in application development because, amongst many reasons, it's taking input from effectively an infinite number of people and places. Never trust user input, right? Input to your application comes not only in the form of text, but also images. Writing a Twitter client is effectively writing a web browser that only browses one website. Getting a browser stable is hard.
Long Zheng, Raphael Rivera and the MetroTwit team (MetroTwit is a lovely new Twitter client) have hit an extremely interesting crashing bug. The input comes in the form of a corrupted JPG image from the web.
Here's the bad image. Looks like a picture some folks speaking on a panel. However, even though this image looks fine, this specific binary version of it has a corrupted Color Profile.
Sometimes folks don't realize that image formats contain lots of metadata that you can't see. Your JPGs may show what camera you used, what lens, what settings, possibly even the geo-coordinates of where you took the picture!
You can view all this extended information (EXIF) with a number of tools. A great free one is ExifTool by Phil Harvey at the command line, or a non-command line one like ExifPro. Windows Live Photo Gallery lets you view the data also.
Here's a snippet of some of the info in this pic:
Device Mfg Desc : IEC http://www.iec.ch
Device Model Desc : IEC 61966-2.1 Default RGB colour space - sRGB
Viewing Cond Desc : Reference Viewing Condition in IEC61966-2.1
Viewing Cond Illuminant : 19.6445 20.3718 16.8089
Viewing Cond Surround : 3.92889 4.07439 3.36179
Viewing Cond Illuminant Type : D50
Make : Leica Camera AG
Camera Model Name : M8 Digital Camera
Software : Aperture 3.0.2
Shutter Speed Value : 1/256
Exposure Compensation : 0
Max Aperture Value : 1.0
Metering Mode : Center-weighted average
Light Source : Flash
Focal Length : 0.0 mm
You can extract the image profile (ICC Profile) from an image like this with exiftool:
exiftool -icc_profile -b foo.jpg > profile.icc
If you're hardcore, you can get the Windows Imaging Component (WIC) Tools and run WICExplorer. WPF uses WIC to decode images. WICExplorer will report the error with this image as you load it.
Loading Images in WPF
When you're using WPF (Windows Presentation Foundation) to display an image on Windows, you might do something like this:
<Image Width="300" Height="300" ImageFailed="Image_ImageFailed">
Except with this particular image, I'll get an exception the Color Profile (the image metadata) is corrupted. "ArgumentException: Value does not fall within the expected range." This is a corrupted file.
at System.Windows.Media.ColorContext.GetColorContextsHelper(GetColorContextsDelegate getColorContexts)
at System.Windows.Media.Imaging.BitmapImage.OnDownloadCompleted(Object sender, EventArgs e)
at System.Windows.Media.UniqueEventHelper.InvokeEvents(Object sender, EventArgs args)
at System.Windows.Media.Imaging.LateBoundBitmapDecoder.DownloadCallback(Object arg)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
If I get this exception, I can try to load the image again and ignore its color profile. Here's how I'd do that in XAML:
<Image Width="300" Height="300" ImageFailed="Image_ImageFailed" >
<BitmapImage CreateOptions="IgnoreColorProfile" UriSource="http://hanselman.com/blog/images/JPGwithBadColorProfile.jpg"/>
If you're loading from code, you can ignore color profile information by adding the BitmapCreateOptions.IgnoreColorProfile flag to CreateOptions.
As an aside, Andrew Eichacker has a nice post on how to read all the BitmapMetadata in WPF. There's lots in there!
Here's loading the Bitmap into an image Control called "Foo."
var bi = new BitmapImage();
bi.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
bi.UriSource = new Uri("http://hanselman.com/blog/images/JPGwithBadColorProfile.jpg");
foo.Source = bi;
Knowing about possible corruption is important to be aware of, especially if you're loading arbitrary images from all over the place. If you don't care about color profiles, I'd just ignore them by default in your image loading code. If you are writing an image editor or you care about profiles, I'd catch the exception, let the user know, then load the image again without the profile.