Click here to Skip to main content
11,927,190 members (50,387 online)
Click here to Skip to main content
Add your own
alternative version


147 bookmarked

Photo Properties

, 27 Feb 2003
Rate this:
Please Sign up or sign in to vote.
Reads properties - such as EXIF - from graphic files.



I always knew that the image JPEGs from my digital camera contained more than just the image. I even researched the topic, learning about the innards of a JPEG file and the digital camera's file format standard entitled EXIF (exchangeable image file format). EXIF places metadata "tags" within a JPEG file to indicate various camera settings and picture taking conditions that occurred while creating the photo.

Unfortunately, this metadata was only available through complex, low-level byte manipulation of the image's raw data. A few products existed to access this data (including Windows XP), but not as a simple API call.

However, with the release of Microsoft .NET's GDI+ class, access to the metadata tags became simple. A PropertyItem class was available to access any of the (public) tags in a file. With just a few lines of code I thought I would be able to get to the hidden information about the exposure rate or flash setting for my favorite pictures.

Not So Easy After All

Then, realization set in -- the PropertyItem class only provides access to raw data values. Tag name and description strings are not available. And to add to the frustration, the values are only available in byte format; the tag's type and length is supplied, but no transformations are available. To illustrate the problem, here is an example of a PropertyItem instance:

  Id    = 33434
  Len   = 8
  Type  = 5
  Value = 0A-00-00-00-E8-03-00-00

So... what does this data mean? How can we find out that 33434 is the EXIF ExposureTime tag? And, how can we decipher 0A-00-00-00-E8-03-00-00 into a readable output of 1/100?

To answer these questions, I created the PhotoProperties dll to provide a simple way to obtain the hidden image properties in a readable format -- an image file is analyzed and XML text is returned.

PhotoPropertiesApp is also included as a sample front-end Windows application that uses the PhotoProperties library.

The PhotoProperties Library

A Little More Research

Microsoft's Visual C++ .NET includes a GdiplusImaging.h header file containing definitions of all of the PropertyItem IDs. For example the EXIF ExposureTime tag is defined as:

#define PropertyTagExifExposureTime 0x829A

But this barely helped the problem. I then looked at the EXIF specification document (see and found the data I needed.


Exposure time, given in seconds (sec).
  Tag     = 33434 (829A.H)
  Type    = RATIONAL
  Count   = 1
  Default = none

However, there was still some missing data. The header file had a number of tag definitions that were not part of the EXIF specification. Further research on the web got me to the "Property Item Descriptions " page in the MSDN Library. Though there were a few differences between the two documents, I finally found all of the pieces I needed to create a solution.

An XML Metadata Resource

To provide a concise method of accessing this new information I decided to store it in an XML file. This way I could easily add/modify/delete the information without changing any of the code.

The XML data consists of tagMetadata elements, each containing various attributes and sub elements such as id, category, name, description, and possibly formatInstr or valueOptions. For example,

<tagMetadata id="33434" category="EXIF">
  <description>Exposure time, measured in seconds.</description>


<tagMetadata id="37377" category="EXIF">
  <description>Shutter speed. The unit is the Additive System of Photographic
  Exposure (APEX) value.</description>


<tagMetadata id="37383" category="EXIF">
  <description>Metering mode.</description>
    <option key="0" value="unknown" />
    <option key="1" value="Average" />
    <option key="2" value="CenterWeightedAverage" />
    <option key="3" value="Spot" />
    <option key="4" value="MultiSpot" />
    <option key="5" value="Pattern" />
    <option key="6" value="Partial" />
    <optionRange from="7" to="254" value="reserved" />
    <option key="255" value="other" />

The id, category, name, and description definitions are obvious. The formatInstr, when provided, contains an additional formatting instruction to be applied to the PropertyItem value. The valueOptions, when provided, contains pretty-print results that can be obtained from the value. The use of formatInstr and valueOptions will be explained further along in this page.

The Library's Public Methods

The PhotoProperties library contains three key public methods: Initialize, Analyze, and WriteXml.

The Initialize method

The Initialize method uses the XmlTextReader and XmlSerializer functions to read the XML file and deserialize the data into appropriate C# classes. More directly, the XML tagMetadata elements are deserialized into a collection of PhotoTagMetadata items accessible by id values.

Deserializing the tagMetadata

To deserialize an XML file, there must be a set of classes that provide a direct mapping between XML elements and attributes and .NET constructs. The easiest way to create these classes is to use the XML Schema Definition Tool (Xsd.exe).

For example, the photoMetadata element in the PhotoMetadata.xsd schema

<xs:element name="photoMetadata">
            <xs:element name="tagMetadata" type="TagMetadata" minOccurs="0"
                maxOccurs="unbounded" />
        <xs:attribute name="category" type="xs:string" use="required" />

was generated into the following photoMetadata class.

    Namespace="", IsNullable=false)]
public class photoMetadata {
    public TagMetadata[] tagMetadata;
    public string category;

As you can see, there are one-to-one mappings between the XML photoMetadata element and C# class, the XML tagMetadata sequence and C# array, and the XML category attribute and C# field.

The array of tagMetadata objects was not the form I needed. I needed a collection that could accessed by their id value -- a Hashtable. Luckily, as long as certain guidelines are followed, functionality can be added and modified without breaking the mappings. These guidelines include:

  • Names can be changed as long as the original XML item name is supplied in the attribute name.
  • Public fields can be added as long as the XmlIgnoreAttribute attribute is provided.
  • Since only public fields are used in XML serialization, addition of non-public fields require no added attributes.
  • Field accessors can be used in place of mapped fields.

So the above class was changed to:

    Namespace="", IsNullable=false)]
public class PhotoMetadata {

    private Hashtable _tagMetadataCollection = new Hashtable();

    public Hashtable TagMetadataCollection {
        get { return _tagMetadataCollection; }

    public PhotoTagMetadata[] TagMetadata {
        get {
            if (_tagMetadataCollection.Count == 0)
                return null;
            PhotoTagMetadata[] tagArray =
        new PhotoTagMetadata[_tagMetadataCollection.Count];
            _tagMetadataCollection.Values.CopyTo(tagArray, 0);
            return tagArray;
        set {
            if (value == null)
            PhotoTagMetadata[] tagArray = (PhotoTagMetadata[])value;
            foreach(PhotoTagMetadata tag in tagArray) {
                _tagMetadataCollection[tag.Id] = tag;

    public string category;

In this format, the sequence of XML tagMetadata elements are auto magically transformed into a collection of PhotoTagMetadata objects accessed by the object's id value.

Utilizing An XML Resource

The PhotoMetadata XML data could be deployed as an additional file along with the PhotoProperties.dll library. But that would require the user to make sure it was always copied with the library. A much more elegant method would store the XML file as a resource within the library assembly. Borrowing some of the code from Paul DiLascia's MOTLib.NET "goodies" (, I was able to store the XML file as a resource in the assembly. The Initialize method also allows the use of an external XML file if needed; the external file must be valid based upon the PhotoMetadata.xsd schema.

The Analyze method

In a nutshell, the Analyze method loops through the tag properties in an image file and converts the byte data values into a collection of formatted strings. It wasn't quite that simple, though.

The PropertyTagFormat Class

The PropertyItem object contains four fields:

  • Id, the tag identifier;
  • Len, the length of the data in bytes;
  • Type, one of the following eight property types:

    Id Name Description
    1 BYTE An 8-bit unsigned integer.
    2 ASCII An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
    3 SHORT A 16-bit (2-byte) unsigned integer.
    4 LONG A 32-bit (4-byte) unsigned integer.
    5 RATIONAL Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator.
    7 UNDEFINED An 8-bit byte that can take any value depending on the field definition.
    9 SLONG A 32-bit (4-byte) signed integer (2's complement notation).
    10 SRATIONAL Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.

  • Value, the property value (in byte format).

The PropertyTagFormat 's FormatValue method uses a PropertyItem and its associated PhotoTagMetadata object to convert the data to a formatted string.

public static string FormatValue(PropertyItem propItem,
    PhotoTagMetadata tagMetadata) {

    if (propItem == null)
        return String.Empty;

    FormatInstr formatInstr;
    if (tagMetadata != null && tagMetadata.FormatInstrSpecified == true)
        formatInstr = tagMetadata.FormatInstr;
        formatInstr = FormatInstr.NO_OP;

    string strRet;

    switch (propItem.Type) {
        case PropertyTagTypeByte:
            strRet = FormatTagByte(propItem, formatInstr);
        case PropertyTagTypeASCII:
            strRet = FormatTagAscii(propItem, formatInstr);
        case PropertyTagTypeShort:
            strRet = FormatTagShort(propItem, formatInstr);
        case PropertyTagTypeLong:
            strRet = FormatTagLong(propItem, formatInstr);
        case PropertyTagTypeRational:
            strRet = FormatTagRational(propItem, formatInstr);
        case PropertyTagTypeUndefined:
            strRet = FormatTagUndefined(propItem, formatInstr);
        case PropertyTagTypeSLong:
            strRet = FormatTagSLong(propItem, formatInstr);
        case PropertyTagTypeSRational:
            strRet = FormatTagSRational(propItem, formatInstr);
            strRet = "";
    return strRet;

A Format Example

Once again, let's look at the ExposureTime example with the following PropertyItem values:

  Id    = 33434
  Len   = 8
  Type  = 5
  Value = 0A-00-00-00-E8-03-00-00

The Type value of 5 indicates a RATIONAL type. Based on the FormatValue method, the FormatTagRational method is called.

private const int BYTEJUMP_LONG     = 4;
private const int BYTEJUMP_RATIONAL = 8;

private const string DOUBLETYPE_FORMAT = "0.0####";

private static string FormatTagRational(PropertyItem propItem,
    FormatInstr formatInstr) {
    string strRet = "";
    for (int i = 0; i < propItem.Len; i = i + BYTEJUMP_RATIONAL) {
        System.UInt32 numer = BitConverter.ToUInt32(propItem.Value, i);
        System.UInt32 denom = BitConverter.ToUInt32(propItem.Value, i
            + BYTEJUMP_LONG);
        if (formatInstr == FormatInstr.FRACTION) {
            UFraction frac = new UFraction(numer, denom);
            strRet += frac.ToString();
        else {
            double dbl;
            if (denom  == 0)
                dbl = 0.0;
                dbl = (double)numer / (double)denom;
            strRet += dbl.ToString(DOUBLETYPE_FORMAT);
        if (i + BYTEJUMP_RATIONAL < propItem.Len)
            strRet += " ";
    return strRet;

The length of a rational value is 8 bytes (BYTEJUMP_RATIONAL). In this case, the Len field value indicates a single rational value.

A PropertyItem Rational consists of two unsigned 4-byte integers, the numerator and the denominator. The BitConverter class converts arrays of bytes to and from base data types; in this case, arrays of four bytes into 32-bit unsigned integers. The byte array "0A-00-00-00-E8-03-00-00" is converted into numer=10 and denom=1000.

In most cases, the numerator would be divided by the denominator; the result being formatted into the string "0.01". However, the ExposureTime's associated PhotoTagMetadata value contains an additional formatting instruction:

  Id          = 33434
  Category    = "EXIF"
  Name        = "ExposureTime"
  Description = "Exposure time, measured in seconds."
  FormatInstr = 1 (FRACTION)

This FormatInstr value instructs the method to return the value as a fraction string. But, how do we convert "10/1000" into "1/100"?

The Fraction and UFraction Classes

If you remember back to high school: you can reduce a fraction to its lowest terms by dividing the numerator and the denominator by their greatest common denominator (gcd). Unfortunately, there is no Reduce or GCD function provided by the .NET framework. So I create an ad hoc Fraction and UFraction class to provide this functionality.

public class UFraction {
    private UInt32 _numer;
    private UInt32 _denom;

    public UFraction(UInt32 numer, UInt32 denom) {
        _numer = numer;
        _denom = denom;

    public override string ToString() {
        UInt32 numer = _numer;
        UInt32 denom = (_denom == 0) ? 1 : _denom;

        Reduce(ref numer, ref denom);

        string result;
        if (numer == 0)
            result = "0";
        else if (denom == 1)
            result = numer + "";
            result = numer + "/" + denom;

        return result;

    private static void Reduce(ref UInt32 numer, ref UInt32 denom) {
        if (numer != 0) {
            UInt32 common = GCD(numer, denom);

            numer = numer / common;
            denom = denom / common;

    private static UInt32 GCD(UInt32 num1, UInt32 num2) {
        while (num1 != num2)
            if (num1 > num2)
                num1 = num1 - num2;
                num2 = num2 - num1;

        return num1;

So in this case, the values, 10 and 1000 are presented as "1/100".

The WriteXml method

The WriteXml method returns the analysis data as a specified XML output (based on the PhotoPropertyOutput.xsd schema). Each tag item is returned as a <tagDatum> element containing the item's id, category, name, description, and value. For example,

<tagDatum id="37434" category="EXIF">
  <description>Exposure time, measured in seconds.</description>

A prettyPrintValue may also exist if the associated PhotoTagMetadata contained a valueOptions value. The MeteringMode tag is such an example:

<tagDatum id="37383" category="EXIF">
  <description>Metering mode.</description>

PrettyPrint Options

In a number of cases, the formatted value that is obtained from the analysis phase is not enough. A value of 5 for a MeteringMode tag is not particularly useful to a viewer. That is why the valueOptions element exists; It provides a pretty-print value for an internal value.

The MeteringMode tagMetadata contains a number of options that match various values.

<tagMetadata id="37383" category="EXIF">
  <description>Metering mode.</description>
    <option key="0" value="unknown" />
    <option key="1" value="Average" />
    <option key="2" value="CenterWeightedAverage" />
    <option key="3" value="Spot" />
    <option key="4" value="MultiSpot" />
    <option key="5" value="Pattern" />
    <option key="6" value="Partial" />
    <optionRange from="7" to="254" value="reserved" />
    <option key="255" value="other" />

Through key matching, the value of 5 can be presented as "Pattern". The <tagDatum> element would include the prettyPrintValue value, "Pattern".

<tagDatum id="37383" category="EXIF">
  <description>Metering mode.</description>

An XSLT Transform

The XML output also includes an optional XSLT instruction to transform the XML output into a more readable format. The default XSLT instruction uses the provided PhotoPropertyOutput.xslt file to transform the XML into an HTML file.

Using The PhotoProperties Library

Now that I created the PhotoProperties library, I wanted to create an application to use the library. PhotoPropertiesApp is a Windows application that uses the PhotoProperties library to analyze a selected image file. The XML result is presented in two views. The top view presents the XML result in a simple Textbox. The bottom view parses the XML result into a detailed ListView. The ListView can be sorted based upon Tag Id, Category, Name, or Value data. When a tag row is selected, the tag's description is presented in the lower window. The pretty-print value is presented in the Value column when available.


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

Jeffrey S. Gangel
United States United States
No Biography provided

You may also be interested in...

Comments and Discussions

GeneralMy vote of 5 Pin
Md. Humayun Rashed27-Mar-12 3:02
memberMd. Humayun Rashed27-Mar-12 3:02 
GeneralMy vote of 5 Pin
manoj kumar choubey26-Feb-12 22:28
membermanoj kumar choubey26-Feb-12 22:28 
GeneralAccess Denied Error when trying to move file. Pin
smitshah24-May-10 7:00
membersmitshah24-May-10 7:00 
GeneralIPTC headers Pin
beaglepuppy28-Aug-08 9:42
memberbeaglepuppy28-Aug-08 9:42 
Generalreading more than one comment chunk from a JPEG Pin
Martin081531-May-07 3:29
memberMartin081531-May-07 3:29 
GeneralGreat tool, more information Pin
TWelberg7-May-07 2:41
memberTWelberg7-May-07 2:41 
QuestionLicense Areement? Pin
t0pc0d3r26-Apr-07 23:11
membert0pc0d3r26-Apr-07 23:11 
QuestionHow to calculate Tag Count ?? Pin
Jethlia16-Apr-07 7:46
memberJethlia16-Apr-07 7:46 
QuestionProblem with PhotoProperties.Initialize(string) Pin
JEstes14-Apr-07 11:24
memberJEstes14-Apr-07 11:24 
QuestionAdding exif data from tiff image to jpeg [modified] Pin
lalitharaj1-Mar-07 5:09
memberlalitharaj1-Mar-07 5:09 
QuestionCan I read properties photo without read from xml? Pin
nguyenthuyaivy10-Jan-07 22:37
membernguyenthuyaivy10-Jan-07 22:37 
QuestionAny idea what is needed to get this info from RAW Files? Pin
Mike Upshon30-Sep-06 2:06
memberMike Upshon30-Sep-06 2:06 
GeneralMaker Note Tag Pin
dazfl20-Feb-06 10:12
memberdazfl20-Feb-06 10:12 
GeneralRe: Maker Note Tag Pin
Jeffrey S. Gangel20-Feb-06 11:30
memberJeffrey S. Gangel20-Feb-06 11:30 
GeneralStatus for Flash Pin
noodle4215-Jan-05 10:45
membernoodle4215-Jan-05 10:45 
GeneralRe: Status for Flash Pin
noodle4218-Jan-05 11:06
membernoodle4218-Jan-05 11:06 
GeneralRe: Status for Flash Pin
noodle4218-Jan-05 11:07
membernoodle4218-Jan-05 11:07 
GeneralNice work, but..... Pin
Apius4226-Nov-04 3:07
memberApius4226-Nov-04 3:07 
GeneralRe: Nice work, but..... Pin
noodle4218-Jan-05 11:03
membernoodle4218-Jan-05 11:03 
GeneralAbout Adobe DNG format Pin
domgom31-Oct-04 4:07
sussdomgom31-Oct-04 4:07 
GeneralRe: About Adobe DNG format Pin
FlyFishGood19-Oct-05 13:19
memberFlyFishGood19-Oct-05 13:19 
GeneralRe: About Adobe DNG format Pin
domgom19-Oct-05 17:43
memberdomgom19-Oct-05 17:43 
GeneralRe: About Adobe DNG format Pin
FlyFishGood19-Oct-05 22:56
memberFlyFishGood19-Oct-05 22:56 
Generalplease help me refind this pic Pin
efpson10-Sep-04 19:25
sussefpson10-Sep-04 19:25 
QuestionCan i get code for FileResGen.exe Pin
cyrusj2328-Jun-04 11:52
membercyrusj2328-Jun-04 11:52 
GeneralFantastic - Any Idea how to Add Tags Pin
BluDog14-Jun-04 23:02
memberBluDog14-Jun-04 23:02 
GeneralRe: Fantastic - Any Idea how to Add Tags Pin
mikasa9-Sep-04 5:42
membermikasa9-Sep-04 5:42 
GeneralRe: Fantastic - Any Idea how to Add Tags Pin
DNewman28-Sep-04 10:21
memberDNewman28-Sep-04 10:21 
QuestionHow to merge two or more images? Pin
xieguoli16-May-04 20:58
memberxieguoli16-May-04 20:58 
AnswerRe: How to merge two or more images? Pin
Numan Manzoor12-Jan-07 4:53
memberNuman Manzoor12-Jan-07 4:53 
GeneralC # API Pin
Ritesh Dubey10-Sep-03 1:08
memberRitesh Dubey10-Sep-03 1:08 
GeneralRe: C # API Pin
Jeffrey S. Gangel12-Sep-03 1:41
memberJeffrey S. Gangel12-Sep-03 1:41 
GeneralFailure to release memory in Analyze procedure. Pin
andrewmacaulay663-Sep-03 4:51
memberandrewmacaulay663-Sep-03 4:51 
GeneralRe: Failure to release memory in Analyze procedure. Pin
Jeffrey S. Gangel12-Sep-03 2:02
memberJeffrey S. Gangel12-Sep-03 2:02 
GeneralThis is the pot of gold at the end of the rainbow!! Pin
nzmike17-Aug-03 15:32
membernzmike17-Aug-03 15:32 
GeneralRe: This is the pot of gold at the end of the rainbow!! Pin
nzmike18-Aug-03 15:09
membernzmike18-Aug-03 15:09 
GeneralRe: This is the pot of gold at the end of the rainbow!! Pin
Jeffrey S. Gangel12-Sep-03 2:00
memberJeffrey S. Gangel12-Sep-03 2:00 
GeneralRe: This is the pot of gold at the end of the rainbow!! Pin
nzmike12-Sep-03 14:45
membernzmike12-Sep-03 14:45 
GeneralRe: This is the pot of gold at the end of the rainbow!! Pin
aquester24-Sep-03 15:30
memberaquester24-Sep-03 15:30 
Generalproblems getting Lat &amp; Long Ref Pin
clcharle18-Jul-03 6:26
memberclcharle18-Jul-03 6:26 
GeneralRe: problems getting Lat &amp; Long Ref Pin
Jeffrey S. Gangel12-Sep-03 1:50
memberJeffrey S. Gangel12-Sep-03 1:50 
GeneralStatus of the source code Pin
Hervé LE ROY26-Jun-03 12:26
memberHervé LE ROY26-Jun-03 12:26 
Generalsame stuff in C++ Pin
LucChirac16-Jun-03 4:37
memberLucChirac16-Jun-03 4:37 
GeneralRe: same stuff in C++ Pin
LucChirac19-Jun-03 3:13
memberLucChirac19-Jun-03 3:13 
GeneralThis is going to save me a lot of work Pin
rdearnaley9-Jun-03 18:17
memberrdearnaley9-Jun-03 18:17 
QuestionAwesome! But how do I....? Pin
budjunkie20-May-03 11:49
memberbudjunkie20-May-03 11:49 
GeneralExcellent article and application Pin
Todd Smith28-Feb-03 9:42
memberTodd Smith28-Feb-03 9:42 
GeneralRe: Excellent article and application Pin
Jeffrey S. Gangel1-Mar-03 6:10
memberJeffrey S. Gangel1-Mar-03 6:10 
GeneralRe: Excellent article and application Pin
Richard A. Johnn1-Mar-03 20:19
memberRichard A. Johnn1-Mar-03 20:19 
GeneralCool Pin
Thomas Freudenberg27-Feb-03 23:42
memberThomas Freudenberg27-Feb-03 23:42 

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.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.151126.1 | Last Updated 28 Feb 2003
Article Copyright 2003 by Jeffrey S. Gangel
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid