|
@@ -1,613 +0,0 @@
|
|
|
-using System;
|
|
|
-using System.Collections.Generic;
|
|
|
-using System.Globalization;
|
|
|
-using System.IO;
|
|
|
-using System.Text;
|
|
|
-
|
|
|
-namespace MediaBrowser.Providers.Photos
|
|
|
-{
|
|
|
- /// <summary>
|
|
|
- /// A class for reading Exif data from a JPEG file. The file will be open for reading for as long as the class exists.
|
|
|
- /// <seealso cref="http://gvsoft.homedns.org/exif/Exif-explanation.html"/>
|
|
|
- /// </summary>
|
|
|
- public class ExifReader : IDisposable
|
|
|
- {
|
|
|
- private readonly FileStream fileStream = null;
|
|
|
- private readonly BinaryReader reader = null;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// The catalogue of tag ids and their absolute offsets within the
|
|
|
- /// file
|
|
|
- /// </summary>
|
|
|
- private Dictionary<ushort, long> catalogue;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Indicates whether to read data using big or little endian byte aligns
|
|
|
- /// </summary>
|
|
|
- private bool isLittleEndian;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// The position in the filestream at which the TIFF header starts
|
|
|
- /// </summary>
|
|
|
- private long tiffHeaderStart;
|
|
|
-
|
|
|
- public ExifReader(string fileName)
|
|
|
- {
|
|
|
- // 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;
|
|
|
-
|
|
|
- try
|
|
|
- {
|
|
|
- // Open the file in a stream
|
|
|
- fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
|
|
- reader = new BinaryReader(fileStream);
|
|
|
-
|
|
|
- // Make sure the file's a JPEG.
|
|
|
- if (ReadUShort() != 0xFFD8)
|
|
|
- throw new Exception("File is not a valid JPEG");
|
|
|
-
|
|
|
- // Scan to the start of the Exif content
|
|
|
- ReadToExifStart();
|
|
|
-
|
|
|
- // Create an index of all Exif tags found within the document
|
|
|
- CreateTagIndex();
|
|
|
- }
|
|
|
- catch (Exception)
|
|
|
- {
|
|
|
- // If instantiation fails, make sure there's no mess left behind
|
|
|
- Dispose();
|
|
|
-
|
|
|
- throw;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #region TIFF methods
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Returns the length (in bytes) per component of the specified TIFF data type
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- private byte GetTIFFFieldLength(ushort tiffDataType)
|
|
|
- {
|
|
|
- switch (tiffDataType)
|
|
|
- {
|
|
|
- case 1:
|
|
|
- case 2:
|
|
|
- case 6:
|
|
|
- return 1;
|
|
|
- case 3:
|
|
|
- case 8:
|
|
|
- return 2;
|
|
|
- case 4:
|
|
|
- case 7:
|
|
|
- case 9:
|
|
|
- case 11:
|
|
|
- return 4;
|
|
|
- case 5:
|
|
|
- case 10:
|
|
|
- case 12:
|
|
|
- return 8;
|
|
|
- default:
|
|
|
- throw new Exception(string.Format("Unknown TIFF datatype: {0}", tiffDataType));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region Methods for reading data directly from the filestream
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets a 2 byte unsigned integer from the file
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- private ushort ReadUShort()
|
|
|
- {
|
|
|
- return ToUShort(ReadBytes(2));
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets a 4 byte unsigned integer from the file
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- private uint ReadUint()
|
|
|
- {
|
|
|
- return ToUint(ReadBytes(4));
|
|
|
- }
|
|
|
-
|
|
|
- private string ReadString(int chars)
|
|
|
- {
|
|
|
- return Encoding.ASCII.GetString(ReadBytes(chars));
|
|
|
- }
|
|
|
-
|
|
|
- private byte[] ReadBytes(int byteCount)
|
|
|
- {
|
|
|
- return reader.ReadBytes(byteCount);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Reads some bytes from the specified TIFF offset
|
|
|
- /// </summary>
|
|
|
- /// <param name="tiffOffset"></param>
|
|
|
- /// <param name="byteCount"></param>
|
|
|
- /// <returns></returns>
|
|
|
- private byte[] ReadBytes(ushort tiffOffset, int byteCount)
|
|
|
- {
|
|
|
- // Keep the current file offset
|
|
|
- long originalOffset = fileStream.Position;
|
|
|
-
|
|
|
- // Move to the TIFF offset and retrieve the data
|
|
|
- fileStream.Seek(tiffOffset + tiffHeaderStart, SeekOrigin.Begin);
|
|
|
-
|
|
|
- byte[] data = reader.ReadBytes(byteCount);
|
|
|
-
|
|
|
- // Restore the file offset
|
|
|
- fileStream.Position = originalOffset;
|
|
|
-
|
|
|
- return data;
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region Data conversion methods for interpreting datatypes from a byte array
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Converts 2 bytes to a ushort using the current byte aligns
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- private ushort ToUShort(byte[] data)
|
|
|
- {
|
|
|
- if (isLittleEndian != BitConverter.IsLittleEndian)
|
|
|
- Array.Reverse(data);
|
|
|
-
|
|
|
- return BitConverter.ToUInt16(data, 0);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Converts 8 bytes to an unsigned rational using the current byte aligns.
|
|
|
- /// </summary>
|
|
|
- /// <param name="data"></param>
|
|
|
- /// <returns></returns>
|
|
|
- /// <seealso cref="ToRational"/>
|
|
|
- private double ToURational(byte[] data)
|
|
|
- {
|
|
|
- var numeratorData = new byte[4];
|
|
|
- var denominatorData = new byte[4];
|
|
|
-
|
|
|
- Array.Copy(data, numeratorData, 4);
|
|
|
- Array.Copy(data, 4, denominatorData, 0, 4);
|
|
|
-
|
|
|
- uint numerator = ToUint(numeratorData);
|
|
|
- uint denominator = ToUint(denominatorData);
|
|
|
-
|
|
|
- return numerator / (double)denominator;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Converts 8 bytes to a signed rational using the current byte aligns.
|
|
|
- /// </summary>
|
|
|
- /// <remarks>
|
|
|
- /// A TIFF rational contains 2 4-byte integers, the first of which is
|
|
|
- /// the numerator, and the second of which is the denominator.
|
|
|
- /// </remarks>
|
|
|
- /// <param name="data"></param>
|
|
|
- /// <returns></returns>
|
|
|
- private double ToRational(byte[] data)
|
|
|
- {
|
|
|
- var numeratorData = new byte[4];
|
|
|
- var denominatorData = new byte[4];
|
|
|
-
|
|
|
- Array.Copy(data, numeratorData, 4);
|
|
|
- Array.Copy(data, 4, denominatorData, 0, 4);
|
|
|
-
|
|
|
- int numerator = ToInt(numeratorData);
|
|
|
- int denominator = ToInt(denominatorData);
|
|
|
-
|
|
|
- return numerator / (double)denominator;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Converts 4 bytes to a uint using the current byte aligns
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- private uint ToUint(byte[] data)
|
|
|
- {
|
|
|
- if (isLittleEndian != BitConverter.IsLittleEndian)
|
|
|
- Array.Reverse(data);
|
|
|
-
|
|
|
- return BitConverter.ToUInt32(data, 0);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Converts 4 bytes to an int using the current byte aligns
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- private int ToInt(byte[] data)
|
|
|
- {
|
|
|
- if (isLittleEndian != BitConverter.IsLittleEndian)
|
|
|
- Array.Reverse(data);
|
|
|
-
|
|
|
- return BitConverter.ToInt32(data, 0);
|
|
|
- }
|
|
|
-
|
|
|
- private double ToDouble(byte[] data)
|
|
|
- {
|
|
|
- if (isLittleEndian != BitConverter.IsLittleEndian)
|
|
|
- Array.Reverse(data);
|
|
|
-
|
|
|
- return BitConverter.ToDouble(data, 0);
|
|
|
- }
|
|
|
-
|
|
|
- private float ToSingle(byte[] data)
|
|
|
- {
|
|
|
- if (isLittleEndian != BitConverter.IsLittleEndian)
|
|
|
- Array.Reverse(data);
|
|
|
-
|
|
|
- return BitConverter.ToSingle(data, 0);
|
|
|
- }
|
|
|
-
|
|
|
- private short ToShort(byte[] data)
|
|
|
- {
|
|
|
- if (isLittleEndian != BitConverter.IsLittleEndian)
|
|
|
- Array.Reverse(data);
|
|
|
-
|
|
|
- return BitConverter.ToInt16(data, 0);
|
|
|
- }
|
|
|
-
|
|
|
- private sbyte ToSByte(byte[] data)
|
|
|
- {
|
|
|
- // An sbyte should just be a byte with an offset range.
|
|
|
- return (sbyte)(data[0] - byte.MaxValue);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Retrieves an array from a byte array using the supplied converter
|
|
|
- /// to read each individual element from the supplied byte array
|
|
|
- /// </summary>
|
|
|
- /// <param name="data"></param>
|
|
|
- /// <param name="elementLengthBytes"></param>
|
|
|
- /// <param name="converter"></param>
|
|
|
- /// <returns></returns>
|
|
|
- private Array GetArray<T>(byte[] data, int elementLengthBytes, ConverterMethod<T> converter)
|
|
|
- {
|
|
|
- Array convertedData = Array.CreateInstance(typeof(T), data.Length / elementLengthBytes);
|
|
|
-
|
|
|
- var buffer = new byte[elementLengthBytes];
|
|
|
-
|
|
|
- // Read each element from the array
|
|
|
- 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);
|
|
|
-
|
|
|
- // Process the data and place it into the output array
|
|
|
- convertedData.SetValue(converter(buffer), elementCount);
|
|
|
- }
|
|
|
-
|
|
|
- return convertedData;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// A delegate used to invoke any of the data conversion methods
|
|
|
- /// </summary>
|
|
|
- /// <param name="data"></param>
|
|
|
- /// <returns></returns>
|
|
|
- private delegate T ConverterMethod<out T>(byte[] data);
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region Stream seek methods - used to get to locations within the JPEG
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Scans to the Exif block
|
|
|
- /// </summary>
|
|
|
- private void ReadToExifStart()
|
|
|
- {
|
|
|
- // The file has a number of blocks (Exif/JFIF), each of which
|
|
|
- // has a tag number followed by a length. We scan the document until the required tag (0xFFE1)
|
|
|
- // is found. All tags start with FF, so a non FF tag indicates an error.
|
|
|
-
|
|
|
- // Get the next tag.
|
|
|
- byte markerStart;
|
|
|
- byte markerNumber = 0;
|
|
|
- while (((markerStart = reader.ReadByte()) == 0xFF) && (markerNumber = reader.ReadByte()) != 0xE1)
|
|
|
- {
|
|
|
- // Get the length of the data.
|
|
|
- ushort dataLength = ReadUShort();
|
|
|
-
|
|
|
- // Jump to the end of the data (note that the size field includes its own size)!
|
|
|
- reader.BaseStream.Seek(dataLength - 2, SeekOrigin.Current);
|
|
|
- }
|
|
|
-
|
|
|
- // It's only success if we found the 0xFFE1 marker
|
|
|
- if (markerStart != 0xFF || markerNumber != 0xE1)
|
|
|
- throw new Exception("Could not find Exif data block");
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Reads through the Exif data and builds an index of all Exif tags in the document
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- private void CreateTagIndex()
|
|
|
- {
|
|
|
- // The next 4 bytes are the size of the Exif data.
|
|
|
- ReadUShort();
|
|
|
-
|
|
|
- // Next is the Exif data itself. It starts with the ASCII "Exif" followed by 2 zero bytes.
|
|
|
- if (ReadString(4) != "Exif")
|
|
|
- throw new Exception("Exif data not found");
|
|
|
-
|
|
|
- // 2 zero bytes
|
|
|
- if (ReadUShort() != 0)
|
|
|
- throw new Exception("Malformed Exif data");
|
|
|
-
|
|
|
- // We're now into the TIFF format
|
|
|
- tiffHeaderStart = reader.BaseStream.Position;
|
|
|
-
|
|
|
- // What byte align will be used for the TIFF part of the document? II for Intel, MM for Motorola
|
|
|
- isLittleEndian = ReadString(2) == "II";
|
|
|
-
|
|
|
- // Next 2 bytes are always the same.
|
|
|
- if (ReadUShort() != 0x002A)
|
|
|
- throw new Exception("Error in TIFF data");
|
|
|
-
|
|
|
- // Get the offset to the IFD (image file directory)
|
|
|
- uint ifdOffset = ReadUint();
|
|
|
-
|
|
|
- // Note that this offset is from the first byte of the TIFF header. Jump to the IFD.
|
|
|
- fileStream.Position = ifdOffset + tiffHeaderStart;
|
|
|
-
|
|
|
- // Catalogue this first IFD (there will be another IFD)
|
|
|
- CatalogueIFD();
|
|
|
-
|
|
|
- // 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(0x8769, out offset))
|
|
|
- throw new Exception("Unable to locate Exif data");
|
|
|
-
|
|
|
- // Jump to the exif SubIFD
|
|
|
- fileStream.Position = offset + tiffHeaderStart;
|
|
|
-
|
|
|
- // Add the subIFD to the catalogue too
|
|
|
- CatalogueIFD();
|
|
|
-
|
|
|
- // Go to the GPS IFD and catalogue that too. It's an optional
|
|
|
- // section.
|
|
|
- if (GetTagValue(0x8825, out offset))
|
|
|
- {
|
|
|
- // Jump to the GPS SubIFD
|
|
|
- fileStream.Position = offset + tiffHeaderStart;
|
|
|
-
|
|
|
- // Add the subIFD to the catalogue too
|
|
|
- CatalogueIFD();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region Exif data catalog and retrieval methods
|
|
|
-
|
|
|
- public bool GetTagValue<T>(ExifTags tag, out T result)
|
|
|
- {
|
|
|
- return GetTagValue((ushort)tag, out result);
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Retrieves an Exif value with the requested tag ID
|
|
|
- /// </summary>
|
|
|
- /// <param name="tagID"></param>
|
|
|
- /// <param name="result"></param>
|
|
|
- /// <returns></returns>
|
|
|
- public bool GetTagValue<T>(ushort tagID, out T result)
|
|
|
- {
|
|
|
- ushort tiffDataType;
|
|
|
- uint numberOfComponents;
|
|
|
- byte[] tagData = GetTagBytes(tagID, out tiffDataType, out numberOfComponents);
|
|
|
-
|
|
|
- if (tagData == null)
|
|
|
- {
|
|
|
- result = default(T);
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- byte fieldLength = GetTIFFFieldLength(tiffDataType);
|
|
|
-
|
|
|
- // Convert the data to the appropriate datatype. Note the weird boxing via object.
|
|
|
- // The compiler doesn't like it otherwise.
|
|
|
- switch (tiffDataType)
|
|
|
- {
|
|
|
- case 1:
|
|
|
- // unsigned byte
|
|
|
- if (numberOfComponents == 1)
|
|
|
- result = (T)(object)tagData[0];
|
|
|
- else
|
|
|
- result = (T)(object)tagData;
|
|
|
- return true;
|
|
|
- case 2:
|
|
|
- // ascii string
|
|
|
- string str = Encoding.ASCII.GetString(tagData);
|
|
|
-
|
|
|
- // There may be a null character within the string
|
|
|
- int nullCharIndex = str.IndexOf('\0');
|
|
|
- if (nullCharIndex != -1)
|
|
|
- str = str.Substring(0, nullCharIndex);
|
|
|
-
|
|
|
- // Special processing for dates.
|
|
|
- if (typeof(T) == typeof(DateTime))
|
|
|
- {
|
|
|
- result =
|
|
|
- (T)(object)DateTime.ParseExact(str, "yyyy:MM:dd HH:mm:ss", CultureInfo.InvariantCulture);
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- result = (T)(object)str;
|
|
|
- return true;
|
|
|
- case 3:
|
|
|
- // unsigned short
|
|
|
- if (numberOfComponents == 1)
|
|
|
- result = (T)(object)ToUShort(tagData);
|
|
|
- else
|
|
|
- result = (T)(object)GetArray(tagData, fieldLength, ToUShort);
|
|
|
- return true;
|
|
|
- case 4:
|
|
|
- // unsigned long
|
|
|
- if (numberOfComponents == 1)
|
|
|
- result = (T)(object)ToUint(tagData);
|
|
|
- else
|
|
|
- result = (T)(object)GetArray(tagData, fieldLength, ToUint);
|
|
|
- return true;
|
|
|
- case 5:
|
|
|
- // unsigned rational
|
|
|
- if (numberOfComponents == 1)
|
|
|
- result = (T)(object)ToURational(tagData);
|
|
|
- else
|
|
|
- result = (T)(object)GetArray(tagData, fieldLength, ToURational);
|
|
|
- return true;
|
|
|
- case 6:
|
|
|
- // signed byte
|
|
|
- if (numberOfComponents == 1)
|
|
|
- result = (T)(object)ToSByte(tagData);
|
|
|
- else
|
|
|
- result = (T)(object)GetArray(tagData, fieldLength, ToSByte);
|
|
|
- return true;
|
|
|
- case 7:
|
|
|
- // undefined. Treat it as an unsigned integer.
|
|
|
- if (numberOfComponents == 1)
|
|
|
- result = (T)(object)ToUint(tagData);
|
|
|
- else
|
|
|
- result = (T)(object)GetArray(tagData, fieldLength, ToUint);
|
|
|
- return true;
|
|
|
- case 8:
|
|
|
- // Signed short
|
|
|
- if (numberOfComponents == 1)
|
|
|
- result = (T)(object)ToShort(tagData);
|
|
|
- else
|
|
|
- result = (T)(object)GetArray(tagData, fieldLength, ToShort);
|
|
|
- return true;
|
|
|
- case 9:
|
|
|
- // Signed long
|
|
|
- if (numberOfComponents == 1)
|
|
|
- result = (T)(object)ToInt(tagData);
|
|
|
- else
|
|
|
- result = (T)(object)GetArray(tagData, fieldLength, ToInt);
|
|
|
- return true;
|
|
|
- case 10:
|
|
|
- // signed rational
|
|
|
- if (numberOfComponents == 1)
|
|
|
- result = (T)(object)ToRational(tagData);
|
|
|
- else
|
|
|
- result = (T)(object)GetArray(tagData, fieldLength, ToRational);
|
|
|
- return true;
|
|
|
- case 11:
|
|
|
- // single float
|
|
|
- if (numberOfComponents == 1)
|
|
|
- result = (T)(object)ToSingle(tagData);
|
|
|
- else
|
|
|
- result = (T)(object)GetArray(tagData, fieldLength, ToSingle);
|
|
|
- return true;
|
|
|
- case 12:
|
|
|
- // double float
|
|
|
- if (numberOfComponents == 1)
|
|
|
- result = (T)(object)ToDouble(tagData);
|
|
|
- else
|
|
|
- result = (T)(object)GetArray(tagData, fieldLength, ToDouble);
|
|
|
- return true;
|
|
|
- default:
|
|
|
- throw new Exception(string.Format("Unknown TIFF datatype: {0}", tiffDataType));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Gets the data in the specified tag ID, starting from before the IFD block.
|
|
|
- /// </summary>
|
|
|
- /// <param name="tiffDataType"></param>
|
|
|
- /// <param name="numberOfComponents">The number of items which make up the data item - i.e. for a string, this will be the
|
|
|
- /// number of characters in the string</param>
|
|
|
- /// <param name="tagID"></param>
|
|
|
- private byte[] GetTagBytes(ushort tagID, out ushort tiffDataType, out uint numberOfComponents)
|
|
|
- {
|
|
|
- // Get the tag's offset from the catalogue and do some basic error checks
|
|
|
- if (fileStream == null || reader == null || catalogue == null || !catalogue.ContainsKey(tagID))
|
|
|
- {
|
|
|
- tiffDataType = 0;
|
|
|
- numberOfComponents = 0;
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- long tagOffset = catalogue[tagID];
|
|
|
-
|
|
|
- // Jump to the TIFF offset
|
|
|
- fileStream.Position = tagOffset;
|
|
|
-
|
|
|
- // Read the tag number from the file
|
|
|
- ushort currentTagID = ReadUShort();
|
|
|
-
|
|
|
- if (currentTagID != tagID)
|
|
|
- throw new Exception("Tag number not at expected offset");
|
|
|
-
|
|
|
- // Read the offset to the Exif IFD
|
|
|
- tiffDataType = ReadUShort();
|
|
|
- numberOfComponents = ReadUint();
|
|
|
- byte[] tagData = ReadBytes(4);
|
|
|
-
|
|
|
- // 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));
|
|
|
-
|
|
|
- if (dataSize > 4)
|
|
|
- {
|
|
|
- ushort offsetAddress = ToUShort(tagData);
|
|
|
- return ReadBytes(offsetAddress, dataSize);
|
|
|
- }
|
|
|
-
|
|
|
- // The value is stored in the tagData starting from the left
|
|
|
- Array.Resize(ref tagData, dataSize);
|
|
|
-
|
|
|
- return tagData;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Records all Exif tags and their offsets within
|
|
|
- /// the file from the current IFD
|
|
|
- /// </summary>
|
|
|
- private void CatalogueIFD()
|
|
|
- {
|
|
|
- if (catalogue == null)
|
|
|
- catalogue = new Dictionary<ushort, long>();
|
|
|
-
|
|
|
- // Assume we're just before the IFD.
|
|
|
-
|
|
|
- // First 2 bytes is the number of entries in this IFD
|
|
|
- ushort entryCount = ReadUShort();
|
|
|
-
|
|
|
- for (ushort currentEntry = 0; currentEntry < entryCount; currentEntry++)
|
|
|
- {
|
|
|
- ushort currentTagNumber = ReadUShort();
|
|
|
-
|
|
|
- // Record this in the catalogue
|
|
|
- catalogue[currentTagNumber] = fileStream.Position - 2;
|
|
|
-
|
|
|
- // Go to the end of this item (10 bytes, as each entry is 12 bytes long)
|
|
|
- reader.BaseStream.Seek(10, SeekOrigin.Current);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region IDisposable Members
|
|
|
-
|
|
|
- public void Dispose()
|
|
|
- {
|
|
|
- // Make sure the file handle is released
|
|
|
- if (reader != null)
|
|
|
- reader.Close();
|
|
|
- if (fileStream != null)
|
|
|
- fileStream.Close();
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
- }
|
|
|
-}
|