The BitmapImage class is the preferred way of handing image files in a WPF application.
A great advantage of BitmapImage is that it can open and handle a wide range of image types. It uses the Windows Imaging Component, which allows third parties to incorporate custom image formats into BitmapImage.
Ultimately, what this means is that, quite amazingly, I am able to open fancy file types like Nikon’s NEF raw format in a way that feels native in my C# applications.
But this presents a challenge. If a user wants to open an image in my application, how do I know if BitmapImage can handle it?
I could use a try-catch, but this is quite inefficient. And since BitmapImage is extensible, I can’t just check the file extension against a hard-coded list. I need to be able to check what file types are supported on a particular machine.
I presented this problem on this Stack Overflow post, where NineBerry provided a solution.
How the solution works
The gist of NineBerry’s solution is that we look in the registry for any third party codecs that help BitmapImage to open any additional file types. Once we have found them, we can then read which file extensions are typically associated with these file types.
NineBerry’s solution doesn’t find natively supported file types. We can use Microsoft’s documentation to hard-code these natively-supported types.
Then, when a user wants to open a file, we can compare its file extension with the extensions we found above.
Note that this isn’t fool-proof. A file with a supposedly valid extension may still not open. The file may be corrupted, have an incorrect extension, or the extension may have multiple uses. Still, this method should catch the vast majority of unsuitable files, and help to speed up your program.
How to use the class in your program
I took NineBerry’s code and turned it into a class with a few useful methods and properties. Here you can…
Check if a filename contains a BitmapImage-suitable file extension
string path = "myImage.jpg";
BitmapImageCheck bic = new BitmapImageCheck();
bool imageValid = bic.IsExtensionSupported(path); //returns true
path
can be a file name with extension, an extension by itself, or a full address.
Get a list of supported file extensions
Get a list of file extensions provided natively, provided by third party decoders, or these two lists combined.
BitmapImageCheck bic = new BitmapImageCheck();
string[] supportedExtensions = bic.AllSupportedExtensions;
Get information about the decoders found on your system
Use the ToString() method to get information about where support for different file extensions is coming from.
BitmapImageCheck bic = new BitmapImageCheck();
MessageBox.Show(bic.ToString());
C# class to get supported file types
Copy the code below into a new C# file in your project. The only thing you might want to change is the namespace.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Win32;
namespace WpfApplication1.Classes
{
/// <summary>
/// Provides methods for checking whether a file can likely be opened as a BitmapImage, based upon its file extension
/// </summary>
public class BitmapImageCheck : IDisposable
{
#region class variables
string baseKeyPath;
RegistryKey baseKey;
private const string WICDecoderCategory = "{7ED96837-96F0-4812-B211-F13C24117ED3}";
private string[] allExtensions;
private string[] nativeExtensions;
private string[] customExtensions;
#endregion
#region constructors
public BitmapImageCheck()
{
if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess)
{
baseKeyPath = "Wow6432Node\\CLSID";
}
else
{
baseKeyPath = "CLSID";
}
baseKey = Registry.ClassesRoot.OpenSubKey(baseKeyPath, false);
recalculateExtensions();
}
#endregion
#region properties
/// <summary>
/// File extensions that are supported by decoders found elsewhere on the system
/// </summary>
public string[] CustomSupportedExtensions
{
get
{
return customExtensions;
}
}
/// <summary>
/// File extensions that are supported natively by .NET
/// </summary>
public string[] NativeSupportedExtensions
{
get
{
return nativeExtensions;
}
}
/// <summary>
/// File extensions that are supported both natively by NET, and by decoders found elsewhere on the system
/// </summary>
public string[] AllSupportedExtensions
{
get
{
return allExtensions;
}
}
#endregion
#region public methods
/// <summary>
/// Check whether a file is likely to be supported by BitmapImage based upon its extension
/// </summary>
/// <param name="extension">File extension (with or without leading full stop), file name or file path</param>
/// <returns>True if extension appears to contain a supported file extension, false if no suitable extension was found</returns>
public bool IsExtensionSupported(string extension)
{
//prepare extension, should a full path be given
if (extension.Contains("."))
{
extension = extension.Substring(extension.LastIndexOf('.') + 1);
}
extension = extension.ToUpper();
extension = extension.Insert(0, ".");
if (AllSupportedExtensions.Contains(extension)) return true;
return false;
}
#endregion
#region private methods
/// <summary>
/// Re-calculate which extensions are available on this system. It's unlikely this ever needs to be called outside of the constructor.
/// </summary>
private void recalculateExtensions()
{
customExtensions = GetSupportedExtensions().ToArray();
nativeExtensions = new string[] { ".BMP", ".GIF", ".ICO", ".JPEG", ".PNG", ".TIFF", ".DDS", ".JPG", ".JXR", ".HDP", ".WDP" };
string[] cse = customExtensions;
string[] nse = nativeExtensions;
string[] ase = new string[cse.Length + nse.Length];
Array.Copy(nse, ase, nse.Length);
Array.Copy(cse, 0, ase, nse.Length, cse.Length);
allExtensions = ase;
}
/// <summary>
/// Represents information about a WIC decoder
/// </summary>
private struct DecoderInfo
{
public string FriendlyName;
public string FileExtensions;
}
/// <summary>
/// Gets a list of additionally registered WIC decoders
/// </summary>
/// <returns></returns>
private IEnumerable<DecoderInfo> GetAdditionalDecoders()
{
var result = new List<DecoderInfo>();
foreach (var codecKey in GetCodecKeys())
{
DecoderInfo decoderInfo = new DecoderInfo();
decoderInfo.FriendlyName = Convert.ToString(codecKey.GetValue("FriendlyName", ""));
decoderInfo.FileExtensions = Convert.ToString(codecKey.GetValue("FileExtensions", ""));
result.Add(decoderInfo);
}
return result;
}
private List<string> GetSupportedExtensions()
{
var decoders = GetAdditionalDecoders();
List<string> rtnlist = new List<string>();
foreach (var decoder in decoders)
{
string[] extensions = decoder.FileExtensions.Split(',');
foreach (var extension in extensions) rtnlist.Add(extension);
}
return rtnlist;
}
private IEnumerable<RegistryKey> GetCodecKeys()
{
var result = new List<RegistryKey>();
if (baseKey != null)
{
var categoryKey = baseKey.OpenSubKey(WICDecoderCategory + "\\instance", false);
if (categoryKey != null)
{
// Read the guids of the registered decoders
var codecGuids = categoryKey.GetSubKeyNames();
foreach (var codecGuid in GetCodecGuids())
{
// Read the properties of the single registered decoder
var codecKey = baseKey.OpenSubKey(codecGuid);
if (codecKey != null)
{
result.Add(codecKey);
}
}
}
}
return result;
}
private string[] GetCodecGuids()
{
if (baseKey != null)
{
var categoryKey = baseKey.OpenSubKey(WICDecoderCategory + "\\instance", false);
if (categoryKey != null)
{
// Read the guids of the registered decoders
return categoryKey.GetSubKeyNames();
}
}
return null;
}
#endregion
#region overrides and whatnot
public override string ToString()
{
string rtnstring = "";
rtnstring += "\nNative support for the following extensions is available: ";
foreach (var item in nativeExtensions)
{
rtnstring += item + ",";
}
if (nativeExtensions.Count() > 0) rtnstring = rtnstring.Remove(rtnstring.Length - 1);
var decoders = GetAdditionalDecoders();
if (decoders.Count() == 0) rtnstring += "\n\nNo custom decoders found.";
else
{
rtnstring += "\n\nThese custom decoders were also found:";
foreach (var decoder in decoders)
{
rtnstring += "\n" + decoder.FriendlyName + ", supporting extensions " + decoder.FileExtensions;
}
}
return rtnstring;
}
public void Dispose()
{
baseKey.Dispose();
}
#endregion
}
}
Get more information about the decoders on your system
The public methods implemented were intended to make available a basic level of file extension information.
For more advanced tasks, some of the private methods in the class could be made public. It is possible to expose further information about each codec, such as their locations in the registry.