Tuesday, December 11, 2007

Accessing JPEG Exif Information in .NET

Syopsis: How to JPEG access date/time information from .NET code.
If you've ever looked at the properties of one of your digital photos, you've noticed a ton of information in the Details tab such as the date the picture was taken, what kind of camera was used, and many other camera details such as aperture, exposure, etc.
This information is supplied for you by the camera and stored in the picture when it is taken.  Thankfully, all cameras use the same format when recording this metadata in their resulting JPEG or TIFF images.  The name of this format is Exif.
Exif is an abbreviation for Exchangeable Image File Format.  This standard basically spells out the properties that can be stored with an image.  There are a lot of properties available.  I've listed a few below and you can read more on it here and here.
Friendly Tag Name Tag Value (Hexadecimal) Sample Value Data Type Detail
Manufacturer 010f Canon ASCII String
Model 0110 Canon DIGITAL IXUS ASCII String
Date and Time 9003 2003:08:11 16:14:32 ASCII String YYYY:MM:DD HH:MM:SS
Flash 9209 0 Unsigned Short 0 indicates that the flash did not fire.
Aperture Value 9202 262144/65536 Unsigned Rational Indicates the amount of light the camera lets in.
Firmware Version 0007 Firmware Version 1.0 ASCII String

Retrieving the Date/Time

What we're concerned about here is the date and time the picture was taken.  In .NET we can easily tap into that data by using PropertyIdList and the PropertyItems properties of an instance of the System.Drawing.Image class.  PropertyIdList gives us a listing of the Exif tags available in integer format.  And PropertyItems gives us access to those values in a byte array.  One collection for both of these would have been nice, but I can't really complain.
So, the first step is to open our JPEG image and get an Image instance to work with:
Image image = Image.FromFile(fileName);
Next, we'll loop through all the tag ID's and find the date/time tag that we're looking for (0x0132):
int tagIndex = -1;
for (int i = 0; i < image.PropertyIdList.Length; i++)
{
    if (image.PropertyIdList[i] == Int32.Parse("132", NumberStyles.HexNumber))
    {
        tagIndex = i;
        break;
    }
}
The next step is to lookup the particular value and translate that to ASCII text that we can work with.
PropertyItem item = image.PropertyItems[tagIndex];
dateString = ASCIIEncoding.ASCII.GetString(item.Value);
Finally, let's go ahead and stuff that date back into a real DateTime structure for easy encapsulation.
string[] dParts = dateString.Split(new string[] { ":", " " }, StringSplitOptions.RemoveEmptyEntries);
int year = Convert.ToInt32(dParts[0].Trim());
int month = Convert.ToInt32(dParts[1].Trim());
int day = Convert.ToInt32(dParts[2].Trim());
int hour = Convert.ToInt32(dParts[3].Trim());
int minute = Convert.ToInt32(dParts[4].Trim());
int second = Convert.ToInt32(dParts[5].Trim());

date = new DateTime(year, month, day, hour, minute, second);
Now that we have our pieces, we'll go ahead and create a nice, clean method that we can use on any given image object:
private static Nullable<DateTime> GetImageDate(Image image)
{
    int tagIndex = -1;

    Nullable<DateTime> date = null;
    // find the index of the id we're looking for
    // Note: the Exif specification at
http://exif.org specifies 0x9003 for
    // the date/time the image was taken
    for (int i = 0; i < image.PropertyIdList.Length; i++)
    {
        if (image.PropertyIdList[i] == Int32.Parse("9003", NumberStyles.HexNumber))
        {
            tagIndex = i;
            break;
        }
    }

    // return if the tag is not found
    if (tagIndex < 0) return null;

    // parse the date string which is in the format: yyyy:mm:dd hh:mm:ss
    string dateString = null;

    try
    {
        PropertyItem item = image.PropertyItems[tagIndex];
        dateString = ASCIIEncoding.ASCII.GetString(item.Value);

        string[] dParts = dateString.Split(new string[] { ":", " " }, StringSplitOptions.RemoveEmptyEntries);
        int year = Convert.ToInt32(dParts[0].Trim());
        int month = Convert.ToInt32(dParts[1].Trim());
        int day = Convert.ToInt32(dParts[2].Trim());
        int hour = Convert.ToInt32(dParts[3].Trim());
        int minute = Convert.ToInt32(dParts[4].Trim());
        int second = Convert.ToInt32(dParts[5].Trim());

        date = new DateTime(year, month, day, hour, minute, second);
    }
    catch { }
    // in case of an exception, we'll just ignore it and not return a date

    return date;
}
Now you can drop the above code in any application and have some fun.