Web Toys

Discussion of all kinds of web technologies

About the author

Bret Patterson.
E-mail me Send mail

Recent comments

Authors

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010

Working with Image Metadata in .NET 3.0 Part I

Working with image metadata has always been pretty painful. There are several standards out there, with alot of overlap and data conversion to/from the meta data to consumable values is usually required. There have been numerous blogs and code samples for working with metadata in .NET 2.0, so I'm only going to focus on using the latest .NET 3.0.

In .NET 3.0 accessing basic image metadata is very simple.

First get the System.Windows.Media.Imaging.BitmapSource:

   1: public static BitmapSource GetBitmapSource(string filename)
   2: {
   3:     BitmapSource rv = null;
   4:     BitmapDecoder decoder;
   5:     if (File.Exists(filename))
   6:     {
   7:         using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
   8:         {
   9:             decoder = BitmapDecoder.Create(fs, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
  10:  
  11:             if (decoder != null)
  12:             {
  13:                 rv = decoder.Frames[0];
  14:             }
  15:  
  16:             fs.Close();
  17:         }
  18:     }
  19:  
  20: }

Next you can pull out all of the metadata into a Dictionary<string,string> using something simple like:

   1: public const string METADATA_TITLE = "Title";
   2: public const string METADATA_SUBJECT = "Subject";
   3: public const string METADATA_RATING = "Rating";
   4: public const string METADATA_LOCATION = "Location";
   5: public const string METADATA_KEYWORDS = "Keywords";
   6: public const string METADATA_FORMAT = "Format";
   7: public const string METADATA_DATETAKEN = "DateTaken";
   8: public const string METADATA_COPYRIGHT = "Copyright";
   9: public const string METADATA_COMMENT = "Comment";
  10: public const string METADATA_CAMERAMODEL = "CameraModel";
  11: public const string METADATA_CAMERAMANUFACTURER = "CameraManufacturer";
  12: public const string METADATA_AUTHOR = "Author";
  13: public const string METADATA_APPLICATIONNAME = "ApplicationName";
  14: public static void GetImageMetadata(Dictionary<string, string> result, BitmapSource bs)
  15: {
  16:  
  17:     if (bs != null)
  18:     {
  19:  
  20:         BitmapMetadata meta = bs.Metadata as BitmapMetadata;
  21:         if (meta.Title != null)
  22:         {
  23:             result.Add(METADATA_TITLE, meta.Title);
  24:         }
  25:         if (meta.Subject != null)
  26:         {
  27:             result.Add(METADATA_SUBJECT, meta.Subject);
  28:         }
  29:  
  30:         result.Add(METADATA_RATING, meta.Rating.ToString());
  31:         if (meta.Location != null)
  32:         {
  33:             result.Add(METADATA_LOCATION, meta.Location);
  34:         }
  35:         if (meta.Format != null)
  36:         {
  37:             result.Add(METADATA_FORMAT, meta.Format);
  38:         }
  39:         if (meta.DateTaken != null)
  40:         {
  41:             result.Add(METADATA_DATETAKEN, meta.DateTaken);
  42:         }
  43:         if (meta.Copyright != null)
  44:         {
  45:             result.Add(METADATA_COPYRIGHT, meta.Copyright);
  46:         }
  47:         if (meta.Comment != null)
  48:         {
  49:             result.Add(METADATA_COMMENT, meta.Comment);
  50:         }
  51:         if (meta.CameraModel != null)
  52:         {
  53:             result.Add(METADATA_CAMERAMODEL, meta.CameraModel);
  54:         }
  55:         if (meta.CameraManufacturer != null)
  56:         {
  57:             result.Add(METADATA_CAMERAMANUFACTURER, meta.CameraManufacturer);
  58:         }
  59:         StringBuilder keywords = new StringBuilder();
  60:         if (meta.Keywords != null)
  61:         {
  62:             foreach (string s in meta.Keywords)
  63:             {
  64:                 keywords.Append(s).Append(" ");
  65:             }
  66:             result.Add(METADATA_KEYWORDS, keywords.ToString().Trim());
  67:         }
  68:         if (meta.ApplicationName != null)
  69:         {
  70:             result.Add(METADATA_APPLICATIONNAME, meta.ApplicationName);
  71:         }
  72:         if (meta.Author != null)
  73:         {
  74:             StringBuilder authors = new StringBuilder();
  75:             foreach (string s in meta.Author)
  76:             {
  77:                 authors.Append(s).Append(",");
  78:             }
  79:             result.Add(METADATA_AUTHOR, authors.ToString().TrimEnd(new char[] { ',' }));
  80:         }
  81:  
  82:     }
  83: }

As you can see in the code .NET 3.0 provides some really useful convenience methods for accessing image metadata. You will also notice that the Keywords and Author properties are collections and can have multiple values. More on that later.

 

The above is fine if those are the only metadata fields you wish to access. If you want to access all of the metadata fields you need to use the new Query interface:

   1: BitmapMetadata meta = bs.Metadata;
   2: // to obtain the camera manufacturer
   3: // check if the data is present, otherwise you will get an exception in GetQuery
   4: if (bs.ContainsQuery("/xmp/tiff:make")) {
   5:     string CameraManufacturer = (string)bs.GetQuery("/xmp/tiff:make");
   6: }
   7: // or you can get the exif Camera Manufacturer
   8: if (bs.ContainsQuery("/app1/ifd/{ushort=271}")) {
   9:     string CameraManufacturer = (string)bs.GetQuery("/app1/ifd/{ushort=271}");
  10: }



Pretty nice. However those query syntaxes really leave alot to be desired. You can also use the following syntax to perform BOTH of the above in a priority order, with the first one called and if not data exists then it calls the second one:

   1: if (meta.ContainsQuery("System.Photo.CameraManufacturer")) {
   2:     string CameraManufacturer = (string)meta.GetQuery("System.Photo.CameraManufacturer");
   3: }


You can find all of the "System" query documentation at the following URL: http://msdn2.microsoft.com/en-us/library/bb643802.aspx
*NOTE: I recently opened a defect against microsoft for these fields, it appears this works for everything except those of type RATIONAL.

The documentation is for C/C++ code, however you can translate it into equivalent c# pretty easily.

One little quirk is that the code doesn't necessarily return very easy to work with data types. A good example of this is the System.Photo.DateTaken query returns a FILETIME object. You can convert this to a datetime object using:

 

   1: System.Runtime.InteropServices.ComTypes.FILETIME ft = (System.Runtime.InteropServices.ComTypes.FILETIME)_Value; 
   2:  
   3: DateTime time; 
   4:  
   5: long ticks = 0; 
   6:  
   7:  
   8:  
   9: ticks = ((long)ft.dwHighDateTime) << 32; 
  10:  
  11: += ft.dwLowDateTime; 
  12:  
  13: time = DateTime.FromFileTimeUtc(ticks); 

.NET 3.0 also allows you to modify these values using SetQuery, this will be the topic of Part II.


Posted by bpatters on Thursday, December 06, 2007 8:45 AM
Permalink | Comments (4) | Post RSSRSS comment feed