Working with EXIF metadata

Introduction

Few days back I had a requirement to generate random images, and add EXIF metadata to them to test some functionality. It was hard to find images with specfic EXIF metadata, so I decided to write a small code which could take care of this requirement.

This blog gives an idea of adding/updating EXIF metadata of an image, and is based on the Kevin's work. The original post can be found here.

Implementation

The sample code below demonstrates functionality to add (if not exist) and update EXIF metadata.  The function WriteExifField actually takes care of adding EXIF data.

  1.  
  2.     /// <summary>
  3.     /// Change/Add standard EXIF field vlaue
  4.     /// </summary>
  5.     /// <param name="file"></param>
  6.     /// <param name="objExifInfo"></param>
  7.     /// <returns></returns>
  8.     public static bool ChangeImageExif(string file, EXIFInfo objExifInfo)
  9.     {
  10.         #region Vars
  11.         string sPropValue = string.Empty;
  12.         EXIFBase.ExifField field;
  13.         PropertyItem propItem = null;
  14.         ImageFormat ifOriginal = null;
  15.         Graphics gSave = null;
  16.         Image iOriginal = null;
  17.         Image iSave = null;
  18.         #endregion
  19.  
  20.         try
  21.         {
  22.             iOriginal = new Bitmap(file);
  23.             ifOriginal = iOriginal.RawFormat;
  24.  
  25.             // For each EXIFField in objExifInfo, add it to Image EXIF
  26.             foreach (var exField in objExifInfo)
  27.             {
  28.                 field = (EXIFBase.ExifField)Enum.Parse(typeof(EXIFBase.ExifField), exField.Key.ToString());
  29.                 try
  30.                 {
  31.                     // Get the EXIF value from Image
  32.                     propItem = iOriginal.GetPropertyItem((int)field);
  33.                     sPropValue = System.Text.Encoding.UTF8.GetString(propItem.Value);
  34.  
  35.                     //Change the value
  36.                     sPropValue = sPropValue.Replace(sPropValue, exField.Value);
  37.  
  38.                     // Get bytes
  39.                     propItem.Value = System.Text.Encoding.UTF8.GetBytes(sPropValue);
  40.  
  41.                     //Set the property on the image
  42.                     iOriginal.SetPropertyItem(propItem);
  43.                 }
  44.                 catch (System.ArgumentException)
  45.                 {
  46.                     // EXIF tag doesn't exist, add it to image
  47.                     WriteEXIFField(iOriginal, field, exField.Value.ToString() + "\0");
  48.                 }
  49.             }
  50.             //Store the list of properties that exist on the image
  51.             ArrayList alPropertyItems = new ArrayList();
  52.  
  53.             foreach (PropertyItem pi in iOriginal.PropertyItems)
  54.                 alPropertyItems.Add(pi);
  55.  
  56.             //Create temp image
  57.             iSave = new Bitmap(iOriginal.Width, iOriginal.Height, iOriginal.PixelFormat);
  58.  
  59.             //Copy the original image over to the temp image
  60.             gSave = Graphics.FromImage(iSave);
  61.  
  62.             //If you check iSave at this point, it does not have any EXIF properties -
  63.             //only the image gets recreated
  64.             gSave.DrawImage(iOriginal, 0, 0, iOriginal.Width, iOriginal.Height);
  65.  
  66.             //Get rid of the locks on the original image
  67.             iOriginal.Dispose();
  68.             gSave.Dispose();
  69.  
  70.             //Copy the original EXIF properties to the new image
  71.             foreach (PropertyItem pi in alPropertyItems)
  72.                 iSave.SetPropertyItem(pi);
  73.  
  74.             //Save the temp image over the original image
  75.             iSave.Save(file, ifOriginal);
  76.  
  77.             return true;
  78.         }
  79.         catch (Exception)
  80.         {
  81.             // TODO: Exception logging
  82.             return false;
  83.         }
  84.         finally
  85.         {                
  86.             iSave.Dispose();                
  87.         }            
  88.     }
  89.  
  90.     /// <summary>
  91.     /// Add a standard EXIF field to the image
  92.     /// </summary>
  93.     /// <param name="image"></param>
  94.     /// <param name="field"></param>
  95.     /// <param name="fieldValue"></param>
  96.     private static void WriteEXIFField(Image image, ExifField field, string fieldValue)
  97.     {
  98.         Encoding asciiEncoding = new ASCIIEncoding();
  99.         System.Text.Encoder encoder = asciiEncoding.GetEncoder();
  100.         char[] tagTextChars = fieldValue.ToCharArray();
  101.         int byteCount = encoder.GetByteCount(tagTextChars, 0, tagTextChars.Length, true);
  102.         byte[] tagTextBytes = new byte[byteCount];
  103.         encoder.GetBytes(tagTextChars, 0, tagTextChars.Length, tagTextBytes, 0, true);
  104.  
  105.         if (image.PropertyItems != null && image.PropertyItems.Length > 0)
  106.         {
  107.             PropertyItem propertyItem = image.PropertyItems[0];
  108.             propertyItem.Id = (int)field;
  109.             propertyItem.Type = 2;  // ASCII
  110.             propertyItem.Len = tagTextBytes.Length;
  111.             propertyItem.Value = tagTextBytes;
  112.             image.SetPropertyItem(propertyItem);
  113.         }
  114.     }
  115. }

In order to apply multiple EXIF metadata changes to an image, let's use a custom class which inherits from System.Collections.Generic.Dictionary

A simple implementation should look like below

  1. public class EXIFInfo : Dictionary<EXIFBase.ExifField, string>
  2. {
  3.     /// <summary>
  4.     /// Initializes a new instance of the EXIFInfo class.
  5.     /// </summary>
  6.     public EXIFInfo()  : base()
  7.     {
  8.         // Default constructor
  9.     }
  10.    
  11.     /// <summary>
  12.     /// Adds an EXIFField with the specified property value into the EXIFInfo
  13.     /// </summary>
  14.     public new void Add(EXIFBase.ExifField key, string value)
  15.     {
  16.         if (string.IsNullOrEmpty(value))
  17.             throw new ArgumentException("Value can not be empty");
  18.            
  19.         base.Add(key, value);
  20.     }
  21. }

To make this code work, we need to know EXIF tag id. I have added Equipment make and model information for demonstration purpose, but other EXIF fields can be added. A detaild EXIF specification can be found here.

  1. public enum ExifField : int
  2. {
  3.     EquipMake = 0x10F,
  4.     EquipModel = 0x110
  5.  
  6.     /// More fields here
  7. }

Now, we can use ExifInfo to add/update multiple EXIF metadata to an image like following 

  1. public void DoIt(string fileName)
  2. {
  3.     EXIFInfo info = new EXIFInfo();
  4.  
  5.     /// Add Exif Info to be added/updated
  6.     info.Add(EXIFBase.ExifField.EquipMake, "Nikon");
  7.     info.Add(EXIFBase.ExifField.EquipModel, "SomeMake");
  8.  
  9.     /// Call the main function
  10.     EXIFBase.ChangeImageExif(fileName, info);
  11. }