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.
- /// <summary>
- /// Change/Add standard EXIF field vlaue
- /// </summary>
- /// <param name="file"></param>
- /// <param name="objExifInfo"></param>
- /// <returns></returns>
- public static bool ChangeImageExif(string file, EXIFInfo objExifInfo)
- {
- #region Vars
- string sPropValue = string.Empty;
- EXIFBase.ExifField field;
- PropertyItem propItem = null;
- ImageFormat ifOriginal = null;
- Graphics gSave = null;
- Image iOriginal = null;
- Image iSave = null;
- #endregion
- try
- {
- iOriginal = new Bitmap(file);
- ifOriginal = iOriginal.RawFormat;
- // For each EXIFField in objExifInfo, add it to Image EXIF
- foreach (var exField in objExifInfo)
- {
- field = (EXIFBase.ExifField)Enum.Parse(typeof(EXIFBase.ExifField), exField.Key.ToString());
- try
- {
- // Get the EXIF value from Image
- propItem = iOriginal.GetPropertyItem((int)field);
- sPropValue = System.Text.Encoding.UTF8.GetString(propItem.Value);
- //Change the value
- sPropValue = sPropValue.Replace(sPropValue, exField.Value);
- // Get bytes
- propItem.Value = System.Text.Encoding.UTF8.GetBytes(sPropValue);
- //Set the property on the image
- iOriginal.SetPropertyItem(propItem);
- }
- catch (System.ArgumentException)
- {
- // EXIF tag doesn't exist, add it to image
- WriteEXIFField(iOriginal, field, exField.Value.ToString() + "\0");
- }
- }
- //Store the list of properties that exist on the image
- ArrayList alPropertyItems = new ArrayList();
- foreach (PropertyItem pi in iOriginal.PropertyItems)
- alPropertyItems.Add(pi);
- //Create temp image
- iSave = new Bitmap(iOriginal.Width, iOriginal.Height, iOriginal.PixelFormat);
- //Copy the original image over to the temp image
- gSave = Graphics.FromImage(iSave);
- //If you check iSave at this point, it does not have any EXIF properties -
- //only the image gets recreated
- gSave.DrawImage(iOriginal, 0, 0, iOriginal.Width, iOriginal.Height);
- //Get rid of the locks on the original image
- iOriginal.Dispose();
- gSave.Dispose();
- //Copy the original EXIF properties to the new image
- foreach (PropertyItem pi in alPropertyItems)
- iSave.SetPropertyItem(pi);
- //Save the temp image over the original image
- iSave.Save(file, ifOriginal);
- return true;
- }
- catch (Exception)
- {
- // TODO: Exception logging
- return false;
- }
- finally
- {
- iSave.Dispose();
- }
- }
- /// <summary>
- /// Add a standard EXIF field to the image
- /// </summary>
- /// <param name="image"></param>
- /// <param name="field"></param>
- /// <param name="fieldValue"></param>
- private static void WriteEXIFField(Image image, ExifField field, string fieldValue)
- {
- Encoding asciiEncoding = new ASCIIEncoding();
- System.Text.Encoder encoder = asciiEncoding.GetEncoder();
- char[] tagTextChars = fieldValue.ToCharArray();
- int byteCount = encoder.GetByteCount(tagTextChars, 0, tagTextChars.Length, true);
- byte[] tagTextBytes = new byte[byteCount];
- encoder.GetBytes(tagTextChars, 0, tagTextChars.Length, tagTextBytes, 0, true);
- if (image.PropertyItems != null && image.PropertyItems.Length > 0)
- {
- PropertyItem propertyItem = image.PropertyItems[0];
- propertyItem.Id = (int)field;
- propertyItem.Type = 2; // ASCII
- propertyItem.Len = tagTextBytes.Length;
- propertyItem.Value = tagTextBytes;
- image.SetPropertyItem(propertyItem);
- }
- }
- }
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
- public class EXIFInfo : Dictionary<EXIFBase.ExifField, string>
- {
- /// <summary>
- /// Initializes a new instance of the EXIFInfo class.
- /// </summary>
- public EXIFInfo() : base()
- {
- // Default constructor
- }
- /// <summary>
- /// Adds an EXIFField with the specified property value into the EXIFInfo
- /// </summary>
- public new void Add(EXIFBase.ExifField key, string value)
- {
- if (string.IsNullOrEmpty(value))
- throw new ArgumentException("Value can not be empty");
- base.Add(key, value);
- }
- }
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.
- public enum ExifField : int
- {
- EquipMake = 0x10F,
- EquipModel = 0x110
- /// More fields here
- }
Now, we can use ExifInfo to add/update multiple EXIF metadata to an image like following
- public void DoIt(string fileName)
- {
- EXIFInfo info = new EXIFInfo();
- /// Add Exif Info to be added/updated
- info.Add(EXIFBase.ExifField.EquipMake, "Nikon");
- info.Add(EXIFBase.ExifField.EquipModel, "SomeMake");
- /// Call the main function
- EXIFBase.ChangeImageExif(fileName, info);
- }