From 1e9ba01f658364dc6d126d0f89fe0115c3f96e2e Mon Sep 17 00:00:00 2001 From: Andrei Nicholson Date: Mon, 17 Feb 2014 21:09:08 -0500 Subject: [PATCH] ExifLib 1.5.0 --- Slideshow/ExifLib/ExifReader.cs | 226 ++++++++++++++++++-------------- Slideshow/ExifLib/ExifTags.cs | 6 +- 2 files changed, 131 insertions(+), 101 deletions(-) diff --git a/Slideshow/ExifLib/ExifReader.cs b/Slideshow/ExifLib/ExifReader.cs index f9732b2..6ab112b 100644 --- a/Slideshow/ExifLib/ExifReader.cs +++ b/Slideshow/ExifLib/ExifReader.cs @@ -4,7 +4,7 @@ using System.IO; using System.Text; using System.Text.RegularExpressions; - + namespace ExifLib { /// @@ -18,36 +18,43 @@ public sealed class ExifReader : IDisposable private static readonly Regex _nullDateTimeMatcher = new Regex(@"^[\s0]{4}[:\s][\s0]{2}[:\s][\s0]{5}[:\s][\s0]{2}[:\s][\s0]{2}$"); /// - /// The main tag id/absolute file offset catalogue + /// The primary tag catalogue (absolute file offsets to tag data, indexed by tag ID) + /// + private Dictionary _ifd0PrimaryCatalogue; + + /// + /// The EXIF tag catalogue (absolute file offsets to tag data, indexed by tag ID) + /// + private Dictionary _ifdExifCatalogue; + + /// + /// The GPS tag catalogue (absolute file offsets to tag data, indexed by tag ID) /// - private Dictionary ifd0Catalogue; + private Dictionary _ifdGPSCatalogue; /// - /// The thumbnail tag id/absolute file offset catalogue + /// The thumbnail tag catalogue (absolute file offsets to tag data, indexed by tag ID) /// /// JPEG images contain 2 main sections - one for the main image (which contains most of the useful EXIF data), and one for the thumbnail /// image (which contains little more than the thumbnail itself). This catalogue is only used by . - private Dictionary ifd1Catalogue; + private Dictionary _ifd1Catalogue; /// /// Indicates whether to read data using big or little endian byte aligns /// - private bool isLittleEndian; + private bool _isLittleEndian; /// /// The position in the filestream at which the TIFF header starts /// - private long tiffHeaderStart; - - /// - /// The location of the thumbnail IFD - /// - private uint ifd1Offset; + private long _tiffHeaderStart; - public ExifReader(string fileName) - : this(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) +// Windows 8 store apps don't support the FileStream class +#if !NETFX_CORE + public ExifReader(string fileName) : this(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { } +#endif public ExifReader(Stream stream) { @@ -60,7 +67,7 @@ public ExifReader(Stream stream) // JPEG encoding uses big endian (i.e. Motorola) byte aligns. The TIFF encoding // found later in the document will specify the byte aligns used for the // rest of the document. - isLittleEndian = false; + _isLittleEndian = false; try { @@ -97,6 +104,9 @@ private byte GetTIFFFieldLength(ushort tiffDataType) { switch (tiffDataType) { + case 0: + // Unknown datatype, therefore it can't be interpreted reliably + return 0; case 1: case 2: case 7: @@ -163,7 +173,7 @@ private byte[] ReadBytes(ushort tiffOffset, int byteCount) long originalOffset = _stream.Position; // Move to the TIFF offset and retrieve the data - _stream.Seek(tiffOffset + tiffHeaderStart, SeekOrigin.Begin); + _stream.Seek(tiffOffset + _tiffHeaderStart, SeekOrigin.Begin); byte[] data = _reader.ReadBytes(byteCount); @@ -183,7 +193,7 @@ private byte[] ReadBytes(ushort tiffOffset, int byteCount) /// private ushort ToUShort(byte[] data) { - if (isLittleEndian != BitConverter.IsLittleEndian) + if (_isLittleEndian != BitConverter.IsLittleEndian) Array.Reverse(data); return BitConverter.ToUInt16(data, 0); @@ -204,10 +214,9 @@ private uint[] ToURationalFraction(byte[] data) uint numerator = ToUint(numeratorData); uint denominator = ToUint(denominatorData); - return new[] { numerator, denominator }; + return new[] {numerator, denominator}; } - /// /// Converts 8 bytes to an unsigned rational using the current byte aligns /// @@ -216,7 +225,7 @@ private double ToURational(byte[] data) { var fraction = ToURationalFraction(data); - return fraction[0] / (double)fraction[1]; + return fraction[0]/(double) fraction[1]; } /// @@ -238,7 +247,7 @@ private int[] ToRationalFraction(byte[] data) int numerator = ToInt(numeratorData); int denominator = ToInt(denominatorData); - return new[] { numerator, denominator }; + return new[] {numerator, denominator}; } /// @@ -249,7 +258,7 @@ private double ToRational(byte[] data) { var fraction = ToRationalFraction(data); - return fraction[0] / (double)fraction[1]; + return fraction[0]/(double) fraction[1]; } /// @@ -257,7 +266,7 @@ private double ToRational(byte[] data) /// private uint ToUint(byte[] data) { - if (isLittleEndian != BitConverter.IsLittleEndian) + if (_isLittleEndian != BitConverter.IsLittleEndian) Array.Reverse(data); return BitConverter.ToUInt32(data, 0); @@ -268,7 +277,7 @@ private uint ToUint(byte[] data) /// private int ToInt(byte[] data) { - if (isLittleEndian != BitConverter.IsLittleEndian) + if (_isLittleEndian != BitConverter.IsLittleEndian) Array.Reverse(data); return BitConverter.ToInt32(data, 0); @@ -276,7 +285,7 @@ private int ToInt(byte[] data) private double ToDouble(byte[] data) { - if (isLittleEndian != BitConverter.IsLittleEndian) + if (_isLittleEndian != BitConverter.IsLittleEndian) Array.Reverse(data); return BitConverter.ToDouble(data, 0); @@ -284,7 +293,7 @@ private double ToDouble(byte[] data) private float ToSingle(byte[] data) { - if (isLittleEndian != BitConverter.IsLittleEndian) + if (_isLittleEndian != BitConverter.IsLittleEndian) Array.Reverse(data); return BitConverter.ToSingle(data, 0); @@ -292,7 +301,7 @@ private float ToSingle(byte[] data) private short ToShort(byte[] data) { - if (isLittleEndian != BitConverter.IsLittleEndian) + if (_isLittleEndian != BitConverter.IsLittleEndian) Array.Reverse(data); return BitConverter.ToInt16(data, 0); @@ -301,7 +310,7 @@ private short ToShort(byte[] data) private sbyte ToSByte(byte[] data) { // An sbyte should just be a byte with an offset range. - return (sbyte)(data[0] - byte.MaxValue); + return (sbyte) (data[0] - byte.MaxValue); } /// @@ -314,15 +323,15 @@ private sbyte ToSByte(byte[] data) /// private static Array GetArray(byte[] data, int elementLengthBytes, ConverterMethod converter) { - Array convertedData = new T[data.Length / elementLengthBytes]; + Array convertedData = new T[data.Length/elementLengthBytes]; var buffer = new byte[elementLengthBytes]; // Read each element from the array - for (int elementCount = 0; elementCount < data.Length / elementLengthBytes; elementCount++) + for (int elementCount = 0; elementCount < data.Length/elementLengthBytes; elementCount++) { // Place the data for the current element into the buffer - Array.Copy(data, elementCount * elementLengthBytes, buffer, 0, elementLengthBytes); + Array.Copy(data, elementCount*elementLengthBytes, buffer, 0, elementLengthBytes); // Process the data and place it into the output array convertedData.SetValue(converter(buffer), elementCount); @@ -387,10 +396,10 @@ private void CreateTagIndex() throw new ExifLibException("Malformed Exif data"); // We're now into the TIFF format - tiffHeaderStart = _stream.Position; + _tiffHeaderStart = _stream.Position; // What byte align will be used for the TIFF part of the document? II for Intel, MM for Motorola - isLittleEndian = ReadString(2) == "II"; + _isLittleEndian = ReadString(2) == "II"; // Next 2 bytes are always the same. if (ReadUShort() != 0x002A) @@ -400,44 +409,41 @@ private void CreateTagIndex() uint ifdOffset = ReadUint(); // Note that this offset is from the first byte of the TIFF header. Jump to the IFD. - _stream.Position = ifdOffset + tiffHeaderStart; + _stream.Position = ifdOffset + _tiffHeaderStart; // Catalogue this first IFD (there will be another IFD) - ifd0Catalogue = new Dictionary(); - CatalogueIFD(ref ifd0Catalogue); + _ifd0PrimaryCatalogue = CatalogueIFD(); // The address to the IFD1 (the thumbnail IFD) is located immediately after the main IFD - ifd1Offset = ReadUint(); + uint ifd1Offset = ReadUint(); // There's more data stored in the subifd, the offset to which is found in tag 0x8769. // As with all TIFF offsets, it will be relative to the first byte of the TIFF header. uint offset; - if (!GetTagValue(ifd0Catalogue, 0x8769, out offset)) + if (!GetTagValue(_ifd0PrimaryCatalogue, 0x8769, out offset)) throw new ExifLibException("Unable to locate Exif data"); // Jump to the exif SubIFD - _stream.Position = offset + tiffHeaderStart; + _stream.Position = offset + _tiffHeaderStart; // Add the subIFD to the catalogue too - CatalogueIFD(ref ifd0Catalogue); + _ifdExifCatalogue = CatalogueIFD(); - // Go to the GPS IFD and catalogue that too. It's an optional - // section. - if (GetTagValue(ifd0Catalogue, 0x8825, out offset)) + // Go to the GPS IFD and catalogue that too. It's an optional section. + if (GetTagValue(_ifd0PrimaryCatalogue, 0x8825, out offset)) { // Jump to the GPS SubIFD - _stream.Position = offset + tiffHeaderStart; + _stream.Position = offset + _tiffHeaderStart; // Add the subIFD to the catalogue too - CatalogueIFD(ref ifd0Catalogue); + _ifdGPSCatalogue = CatalogueIFD(); } // Finally, catalogue the thumbnail IFD if it's present if (ifd1Offset != 0) { - _stream.Position = ifd1Offset + tiffHeaderStart; - ifd1Catalogue = new Dictionary(); - CatalogueIFD(ref ifd1Catalogue); + _stream.Position = ifd1Offset + _tiffHeaderStart; + _ifd1Catalogue = CatalogueIFD(); } } #endregion @@ -446,13 +452,23 @@ private void CreateTagIndex() public bool GetTagValue(ExifTags tag, out T result) { - return GetTagValue((ushort)tag, out result); + return GetTagValue((ushort) tag, out result); } public bool GetTagValue(ushort tagID, out T result) { - // All useful EXIF tags are stored in the ifd0 catalogue. The ifd1 catalogue is only for thumbnail retrieval. - return GetTagValue(ifd0Catalogue, tagID, out result); + // Select the correct catalogue based on the tag value. Note that the thumbnail catalogue (ifd1) + // is only used for thumbnails, never for tag retrieval + + Dictionary catalogue; + if (tagID > (int)ExifTags.Copyright) + catalogue = _ifdExifCatalogue; + else if (tagID > (int) ExifTags.GPSDifferential) + catalogue = _ifd0PrimaryCatalogue; + else + catalogue = _ifdGPSCatalogue; + + return GetTagValue(catalogue, tagID, out result); } /// @@ -472,6 +488,13 @@ private bool GetTagValue(Dictionary tagDictionary, ushort tagID byte fieldLength = GetTIFFFieldLength(tiffDataType); + if (fieldLength == 0) + { + // Some fields have no data at all. Treat them as though they're absent, as they're bogus + result = default(T); + return false; + } + // Convert the data to the appropriate datatype. Note the weird boxing via object. // The compiler doesn't like it otherwise. switch (tiffDataType) @@ -479,9 +502,9 @@ private bool GetTagValue(Dictionary tagDictionary, ushort tagID case 1: // unsigned byte if (numberOfComponents == 1) - result = (T)(object)tagData[0]; + result = (T) (object) tagData[0]; else - result = (T)(object)tagData; + result = (T) (object) tagData; return true; case 2: // ascii string @@ -493,31 +516,31 @@ private bool GetTagValue(Dictionary tagDictionary, ushort tagID str = str.Substring(0, nullCharIndex); // Special processing for dates. - if (typeof(T) == typeof(DateTime)) + if (typeof (T) == typeof (DateTime)) { DateTime dateResult; bool success = ToDateTime(str, out dateResult); - result = (T)(object)dateResult; + result = (T)(object) dateResult; return success; } - result = (T)(object)str; + result = (T) (object) str; return true; case 3: // unsigned short if (numberOfComponents == 1) - result = (T)(object)ToUShort(tagData); + result = (T) (object) ToUShort(tagData); else - result = (T)(object)GetArray(tagData, fieldLength, ToUShort); + result = (T) (object) GetArray(tagData, fieldLength, ToUShort); return true; case 4: // unsigned long if (numberOfComponents == 1) - result = (T)(object)ToUint(tagData); + result = (T) (object) ToUint(tagData); else - result = (T)(object)GetArray(tagData, fieldLength, ToUint); + result = (T) (object) GetArray(tagData, fieldLength, ToUint); return true; case 5: // unsigned rational @@ -525,41 +548,41 @@ private bool GetTagValue(Dictionary tagDictionary, ushort tagID { // Special case - sometimes it's useful to retrieve the numerator and // denominator in their raw format - if (typeof(T).IsArray) - result = (T)(object)ToURationalFraction(tagData); + if (typeof (T).IsArray) + result = (T) (object) ToURationalFraction(tagData); else - result = (T)(object)ToURational(tagData); + result = (T) (object) ToURational(tagData); } else - result = (T)(object)GetArray(tagData, fieldLength, ToURational); + result = (T) (object) GetArray(tagData, fieldLength, ToURational); return true; case 6: // signed byte if (numberOfComponents == 1) - result = (T)(object)ToSByte(tagData); + result = (T) (object) ToSByte(tagData); else - result = (T)(object)GetArray(tagData, fieldLength, ToSByte); + result = (T) (object) GetArray(tagData, fieldLength, ToSByte); return true; case 7: // undefined. Treat it as a byte. if (numberOfComponents == 1) - result = (T)(object)tagData[0]; + result = (T) (object)tagData[0]; else - result = (T)(object)tagData; + result = (T) (object) tagData; return true; case 8: // Signed short if (numberOfComponents == 1) - result = (T)(object)ToShort(tagData); + result = (T) (object) ToShort(tagData); else - result = (T)(object)GetArray(tagData, fieldLength, ToShort); + result = (T) (object) GetArray(tagData, fieldLength, ToShort); return true; case 9: // Signed long if (numberOfComponents == 1) - result = (T)(object)ToInt(tagData); + result = (T) (object) ToInt(tagData); else - result = (T)(object)GetArray(tagData, fieldLength, ToInt); + result = (T) (object) GetArray(tagData, fieldLength, ToInt); return true; case 10: // signed rational @@ -567,27 +590,27 @@ private bool GetTagValue(Dictionary tagDictionary, ushort tagID { // Special case - sometimes it's useful to retrieve the numerator and // denominator in their raw format - if (typeof(T).IsArray) - result = (T)(object)ToRationalFraction(tagData); + if (typeof (T).IsArray) + result = (T) (object) ToRationalFraction(tagData); else - result = (T)(object)ToRational(tagData); + result = (T) (object) ToRational(tagData); } else - result = (T)(object)GetArray(tagData, fieldLength, ToRational); + result = (T) (object) GetArray(tagData, fieldLength, ToRational); return true; case 11: // single float if (numberOfComponents == 1) - result = (T)(object)ToSingle(tagData); + result = (T) (object) ToSingle(tagData); else - result = (T)(object)GetArray(tagData, fieldLength, ToSingle); + result = (T) (object) GetArray(tagData, fieldLength, ToSingle); return true; case 12: // double float if (numberOfComponents == 1) - result = (T)(object)ToDouble(tagData); + result = (T) (object) ToDouble(tagData); else - result = (T)(object)GetArray(tagData, fieldLength, ToDouble); + result = (T) (object) GetArray(tagData, fieldLength, ToDouble); return true; default: throw new ExifLibException(string.Format("Unknown TIFF datatype: {0}", tiffDataType)); @@ -655,7 +678,7 @@ private byte[] GetTagBytes(Dictionary tagDictionary, ushort tagID, // If the total space taken up by the field is longer than the // 2 bytes afforded by the tagData, tagData will contain an offset // to the actual data. - var dataSize = (int)(numberOfComponents * GetTIFFFieldLength(tiffDataType)); + var dataSize = (int) (numberOfComponents*GetTIFFFieldLength(tiffDataType)); if (dataSize > 4) { @@ -670,11 +693,12 @@ private byte[] GetTagBytes(Dictionary tagDictionary, ushort tagID, } /// - /// Records all Exif tags and their offsets within - /// the file from the current IFD + /// Reads the current IFD header and records all Exif tags and their offsets in a /// - private void CatalogueIFD(ref Dictionary tagOffsets) + private Dictionary CatalogueIFD() { + Dictionary tagOffsets = new Dictionary(); + // Assume we're just before the IFD. // First 2 bytes is the number of entries in this IFD @@ -690,6 +714,8 @@ private void CatalogueIFD(ref Dictionary tagOffsets) // Go to the end of this item (10 bytes, as each entry is 12 bytes long) _stream.Seek(10, SeekOrigin.Current); } + + return tagOffsets; } #endregion @@ -704,12 +730,12 @@ private void CatalogueIFD(ref Dictionary tagOffsets) /// public byte[] GetJpegThumbnailBytes() { - if (ifd1Catalogue == null) + if (_ifd1Catalogue == null) return null; // Get the thumbnail encoding ushort compression; - if (!GetTagValue(ifd1Catalogue, (ushort)ExifTags.Compression, out compression)) + if (!GetTagValue(_ifd1Catalogue, (ushort) ExifTags.Compression, out compression)) return null; // This method only handles JPEG thumbnails (compression type 6) @@ -718,12 +744,12 @@ public byte[] GetJpegThumbnailBytes() // Get the location of the thumbnail uint offset; - if (!GetTagValue(ifd1Catalogue, (ushort)ExifTags.JPEGInterchangeFormat, out offset)) + if (!GetTagValue(_ifd1Catalogue, (ushort) ExifTags.JPEGInterchangeFormat, out offset)) return null; // Get the length of the thumbnail data uint length; - if (!GetTagValue(ifd1Catalogue, (ushort)ExifTags.JPEGInterchangeFormatLength, out length)) + if (!GetTagValue(_ifd1Catalogue, (ushort) ExifTags.JPEGInterchangeFormatLength, out length)) return null; _stream.Position = offset; @@ -737,7 +763,6 @@ public byte[] GetJpegThumbnailBytes() break; previousByte = currentByte; - } if (currentByte != 0xD8) @@ -747,10 +772,10 @@ public byte[] GetJpegThumbnailBytes() _stream.Position -= 2; var imageBytes = new byte[length]; - _stream.Read(imageBytes, 0, (int)length); + _stream.Read(imageBytes, 0, (int) length); // A valid JPEG stream ends with 0xFFD9. The stream may be padded at the end with multiple 0xFF or 0x00 bytes. - int jpegStreamEnd = (int)length - 1; + int jpegStreamEnd = (int) length - 1; for (; jpegStreamEnd > 0; jpegStreamEnd--) { var lastByte = imageBytes[jpegStreamEnd]; @@ -760,7 +785,7 @@ public byte[] GetJpegThumbnailBytes() if (jpegStreamEnd <= 0 || imageBytes[jpegStreamEnd] != 0xD9 || imageBytes[jpegStreamEnd - 1] != 0xFF) return null; - + return imageBytes; } #endregion @@ -769,11 +794,18 @@ public byte[] GetJpegThumbnailBytes() public void Dispose() { - // Make sure the file handle is released + // Make sure the file handle is released. Note the different options for Windows Store apps +#if NETFX_CORE + if (_reader != null) + _reader.Dispose(); + if (_stream != null) + _stream.Dispose(); +#else if (_reader != null) _reader.Close(); if (_stream != null) _stream.Close(); +#endif } #endregion @@ -785,14 +817,12 @@ public ExifLibException() { } - public ExifLibException(string message) - : base(message) + public ExifLibException(string message) : base(message) { } - public ExifLibException(string message, Exception innerException) - : base(message, innerException) + public ExifLibException(string message, Exception innerException) : base(message, innerException) { } } -} \ No newline at end of file +} diff --git a/Slideshow/ExifLib/ExifTags.cs b/Slideshow/ExifLib/ExifTags.cs index aa8b8a1..89e0be7 100644 --- a/Slideshow/ExifLib/ExifTags.cs +++ b/Slideshow/ExifLib/ExifTags.cs @@ -7,7 +7,7 @@ namespace ExifLib /// public enum ExifTags : ushort { - // IFD0 items + // primary tags ImageWidth = 0x100, ImageLength = 0x101, BitsPerSample = 0x102, @@ -39,7 +39,7 @@ public enum ExifTags : ushort ReferenceBlackWhite = 0x214, Copyright = 0x8298, - // SubIFD items + // EXIF tags ExposureTime = 0x829A, FNumber = 0x829D, ExposureProgram = 0x8822, @@ -97,7 +97,7 @@ public enum ExifTags : ushort SubjectDistanceRange = 0xA40C, ImageUniqueID = 0xA420, - // GPS subifd items + // GPS tags GPSVersionID = 0x0, GPSLatitudeRef = 0x1, GPSLatitude = 0x2,