using PaintDotNet.SystemLayer; using System; using System.Collections; using System.Collections.Specialized; using System.Drawing.Imaging; namespace PaintDotNet { /// /// This class exposes two types of metadata: system, and user. /// It is provided mostly for batching operations: loading all the data, modifying the copy, /// and then saving back all the data. /// public class Metadata { /// /// This is the name of the section where EXIF tags are stored. /// /// /// All entries in here are expected to be PropertyItem objects which were serialized /// using PdnGraphics.SerializePropertyItem. The name of each entry in this section is /// irrelevant, as some EXIF tags are allowed to occur more than once. Thus, if you /// want to search for EXIF tags of a certain ID you will have to deserialize each /// one and compare the Id property. /// It is the responsibility of the FileType implementation to load and save these. /// public const string ExifSectionName = "$exif"; /// /// This is the name of the section where user-defined metadata may go. /// public const string UserSectionName = "$user"; /// /// This is the name of the section where the main document metadata goes that /// can be user-provided but is not necessarily user-defined. /// public const string MainSectionName = "$main"; private NameValueCollection userMetaData; private const string sectionSeparator = "."; private int suppressChangeEvents = 0; public event EventHandler Changing; protected virtual void OnChanging() { if (suppressChangeEvents <= 0 && Changing != null) { Changing(this, EventArgs.Empty); } } public event EventHandler Changed; protected virtual void OnChanged() { if (suppressChangeEvents <= 0 && Changed != null) { Changed(this, EventArgs.Empty); } } private class ExifInfo { public string[] names; public PropertyItem[] items; public ExifInfo(string[] names, PropertyItem[] items) { this.names = names; this.items = items; } } private Hashtable exifIdToExifInfo = new Hashtable(); // maps short -> ExifInfo public string[] GetKeys(string section) { string sectionName = section + sectionSeparator; ArrayList keys = new ArrayList(); foreach (string key in userMetaData.Keys) { if (key.StartsWith(sectionName)) { keys.Add(key.Substring(sectionName.Length)); } } return (string[])keys.ToArray(typeof(string)); } public string[] GetSections() { Set sections = new Set(); foreach (string key in userMetaData.Keys) { int dotIndex = key.IndexOf(sectionSeparator); if (dotIndex != -1) { string sectionName = key.Substring(0, dotIndex); if (!sections.Contains(sectionName)) { sections.Add(sectionName); } } } return (string[])sections.ToArray(typeof(string)); } /// /// Gets a value from the metadata collection. /// /// The logical section to retrieve from. /// The name of the value to retrieve. /// A string containing the value, or null if the value wasn't present. public string GetValue(string section, string name) { return userMetaData.Get(section + sectionSeparator + name); } public PropertyItem[] GetExifValues(ExifTagID id) { return GetExifValues((short)id); } public PropertyItem[] GetExifValues(short id) { ExifInfo info = (ExifInfo)this.exifIdToExifInfo[id]; if (info == null) { return new PropertyItem[0]; } else { return (PropertyItem[])info.items.Clone(); } } public string GetUserValue(string name) { return GetValue(UserSectionName, name); } /// /// Removes a value from the metadata collection. /// /// The logical section to remove from. /// The name of the value to retrieve. public void RemoveValue(string section, string name) { OnChanging(); userMetaData.Remove(section + sectionSeparator + name); OnChanged(); } public void ReplaceExifValues(ExifTagID id, PropertyItem[] items) { ReplaceExifValues((short)id, items); } public void ReplaceExifValues(short id, PropertyItem[] items) { OnChanging(); ++suppressChangeEvents; RemoveExifValues(id); AddExifValues(items); --suppressChangeEvents; OnChanged(); } public void RemoveExifValues(ExifTagID id) { RemoveExifValues((short)id); } public void RemoveExifValues(short id) { object idObj = (object)id; ExifInfo info = (ExifInfo)this.exifIdToExifInfo[idObj]; OnChanging(); ++suppressChangeEvents; if (info != null) { foreach (string name in info.names) { RemoveValue(ExifSectionName, name); } this.exifIdToExifInfo.Remove(idObj); } --suppressChangeEvents; OnChanged(); } public void RemoveUserValue(string name) { RemoveValue(UserSectionName, name); } private void SetValueConcrete(string section, string name, string value) { OnChanging(); userMetaData.Set(section + sectionSeparator + name, value); OnChanged(); } /// /// Sets a value in the metadata collection. /// /// The logical section to add or update date in. /// The name of the value to set. /// The value to set. public void SetValue(string section, string name, string value) { if (section == ExifSectionName) { throw new ArgumentException("you must use AddExifValues() to add items to the " + ExifSectionName + " section"); } SetValueConcrete(section, name, value); } public void SetUserValue(string name, string value) { SetValue(Metadata.UserSectionName, name, value); } public void AddExifValues(PropertyItem[] items) { if (items.Length == 0) { return; } short id = unchecked((short)items[0].Id); for (int i = 1; i < items.Length; ++i) { if (unchecked((short)items[i].Id) != id) { throw new ArgumentException("all PropertyItem instances in items must have the same id"); } } string[] names = new string[items.Length]; OnChanging(); ++suppressChangeEvents; for (int i = 0; i < items.Length; ++i) { names[i] = GetUniqueExifName(); string blob = PdnGraphics.SerializePropertyItem(items[i]); SetValueConcrete(ExifSectionName, names[i], blob); } object idObj = (object)id; // avoid boxing twice ExifInfo info = (ExifInfo)this.exifIdToExifInfo[idObj]; if (info == null) { PropertyItem[] newItems = new PropertyItem[items.Length]; for (int i = 0; i < newItems.Length; ++i) { newItems[i] = PdnGraphics.ClonePropertyItem(items[i]); } info = new ExifInfo(names, newItems); } else { string[] names2 = new string[info.names.Length + names.Length]; PropertyItem[] items2 = new PropertyItem[info.items.Length + items.Length]; info.names.CopyTo(names2, 0); names.CopyTo(names2, info.names.Length); info.items.CopyTo(items2, 0); for (int i = 0; i < items.Length; ++i) { items2[i + info.items.Length] = PdnGraphics.ClonePropertyItem(items[i]); } info = new ExifInfo(names2, items2); } this.exifIdToExifInfo[idObj] = info; --suppressChangeEvents; OnChanged(); } private int nextUniqueId = 0; private string GetUniqueExifName() { int num = nextUniqueId; const string namePrefix = "tag"; while (true) { string name = namePrefix + num.ToString(); if (GetValue(ExifSectionName, name) == null) { nextUniqueId = num + 1; return name; } else { ++num; } } } public void ReplaceWithDataFrom(Metadata source) { OnChanging(); ++suppressChangeEvents; if (source != this && source.userMetaData != this.userMetaData) { Clear(); foreach (string key in source.userMetaData.Keys) { string value = source.userMetaData.Get(key); this.userMetaData.Set(key, value); } ReconstructExifInfoCache(); } --suppressChangeEvents; OnChanged(); } public void Clear() { OnChanging(); ++suppressChangeEvents; this.userMetaData.Clear(); this.exifIdToExifInfo.Clear(); --suppressChangeEvents; OnChanged(); } private void ReconstructExifInfoCache() { OnChanging(); ++suppressChangeEvents; exifIdToExifInfo.Clear(); string[] exifKeys = GetKeys(ExifSectionName); string[] piBlobs = new string[exifKeys.Length]; for (int i = 0; i < exifKeys.Length; ++i) { piBlobs[i] = GetValue(ExifSectionName, exifKeys[i]); this.RemoveValue(ExifSectionName, exifKeys[i]); } foreach (string piBlob in piBlobs) { PropertyItem pi = PdnGraphics.DeserializePropertyItem(piBlob); AddExifValues(new PropertyItem[] { pi }); } --suppressChangeEvents; OnChanged(); } internal Metadata(NameValueCollection userMetaData) { this.userMetaData = userMetaData; ReconstructExifInfoCache(); } } }