From 227c104460bace3dbfa98ee962a8896689583dd8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 15 Aug 2016 00:09:23 +1000 Subject: [PATCH] Begin style :cop: for EXIF [skip ci] Former-commit-id: 4a6cad5cac25407cab5f7ab09ff042c4ae03b1b9 Former-commit-id: 51a21bd5864dba0fe03806f9452b7b04b05aee65 Former-commit-id: f21194eb73883753cc16cbf803ee5b3434f133a1 --- .../Profiles/Exif/ExifProfile.cs | 130 ++++++---- .../Profiles/Exif/ExifReader.cs | 231 ++++++++++++------ 2 files changed, 239 insertions(+), 122 deletions(-) diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifProfile.cs b/src/ImageProcessorCore/Profiles/Exif/ExifProfile.cs index 2caa22859..5db095f09 100644 --- a/src/ImageProcessorCore/Profiles/Exif/ExifProfile.cs +++ b/src/ImageProcessorCore/Profiles/Exif/ExifProfile.cs @@ -10,49 +10,67 @@ namespace ImageProcessorCore using System.IO; /// - /// Class that can be used to access an Exif profile. + /// Represents an EXIF profile providing access to the collection of values. /// public sealed class ExifProfile { - private byte[] data; + /// + /// The byte array to read the EXIF profile from. + /// + private readonly byte[] data; + + /// + /// The collection of EXIF values + /// private Collection values; + + /// + /// The list of invalid EXIF tags + /// private List invalidTags; + + /// + /// The thumbnail offset position in the byte stream + /// private int thumbnailOffset; + + /// + /// The thumbnail length in the byte stream + /// private int thumbnailLength; - /// + /// /// Initializes a new instance of the class. - /// - ///The byte array to read the exif profile from. + /// public ExifProfile() : this((byte[])null) { } - /// + /// /// Initializes a new instance of the class. - /// - ///The byte array to read the exif profile from. + /// + /// The byte array to read the EXIF profile from. public ExifProfile(byte[] data) { - Parts = ExifParts.All; - BestPrecision = false; + this.Parts = ExifParts.All; + this.BestPrecision = false; this.data = data; this.invalidTags = new List(); } /// /// Initializes a new instance of the class - /// by making a copy from another exif profile. + /// by making a copy from another EXIF profile. /// - /// The other exif profile, where the clone should be made from. + /// The other EXIF profile, where the clone should be made from. /// is null. public ExifProfile(ExifProfile other) { Guard.NotNull(other, nameof(other)); - Parts = other.Parts; - BestPrecision = other.BestPrecision; + this.Parts = other.Parts; + this.BestPrecision = other.BestPrecision; this.thumbnailLength = other.thumbnailLength; this.thumbnailOffset = other.thumbnailOffset; @@ -60,7 +78,7 @@ namespace ImageProcessorCore if (other.values != null) { this.values = new Collection(); - foreach(ExifValue value in other.values) + foreach (ExifValue value in other.values) { this.values.Add(new ExifValue(value)); } @@ -71,39 +89,34 @@ namespace ImageProcessorCore } } - /// - /// Specifies if rationals should be stored with the best precision possible. This is disabled - /// by default, setting this to true will have an impact on the performance. - /// + /// + /// Gets or sets a value indicating whether rational numbers should be stored with the best + /// precision possible. This is disabled by default, setting this to true will have an + /// impact on the performance. + /// public bool BestPrecision { get; set; } - /// - /// Specifies which parts will be written when the profile is added to an image. - /// + /// + /// Gets or sets which parts will be written when the profile is added to an image. + /// public ExifParts Parts { get; set; } - /// - /// Returns the tags that where found but contained an invalid value. - /// - public IEnumerable InvalidTags - { - get - { - return this.invalidTags; - } - } + /// + /// Gets the tags that where found but contained an invalid value. + /// + public IEnumerable InvalidTags => this.invalidTags; - /// - /// Returns the values of this exif profile. - /// + /// + /// Gets the values of this EXIF profile. + /// public IEnumerable Values { get @@ -113,9 +126,9 @@ namespace ImageProcessorCore } } - /// - /// Returns the thumbnail in the exif profile when available. - /// + /// + /// Returns the thumbnail in the EXIF profile when available. + /// /// The pixel format. /// The packed format. long, float. public Image CreateThumbnail() @@ -125,10 +138,14 @@ namespace ImageProcessorCore InitializeValues(); if (this.thumbnailOffset == 0 || this.thumbnailLength == 0) + { return null; + } if (this.data.Length < (this.thumbnailOffset + this.thumbnailLength)) + { return null; + } using (MemoryStream memStream = new MemoryStream(this.data, this.thumbnailOffset, this.thumbnailLength)) { @@ -136,10 +153,10 @@ namespace ImageProcessorCore } } - /// + /// /// Returns the value with the specified tag. - /// - ///The tag of the exif value. + /// + /// The tag of the EXIF value. public ExifValue GetValue(ExifTag tag) { foreach (ExifValue exifValue in Values) @@ -151,10 +168,10 @@ namespace ImageProcessorCore return null; } - /// + /// /// Removes the value with the specified tag. - /// - ///The tag of the exif value. + /// + /// The tag of the EXIF value. public bool RemoveValue(ExifTag tag) { InitializeValues(); @@ -171,14 +188,14 @@ namespace ImageProcessorCore return false; } - /// + /// /// Sets the value of the specified tag. - /// - ///The tag of the exif value. - ///The value. + /// + /// The tag of the EXIF value. + /// The value. public void SetValue(ExifTag tag, object value) { - foreach (ExifValue exifValue in Values) + foreach (ExifValue exifValue in this.Values) { if (exifValue.Tag == tag) { @@ -191,25 +208,32 @@ namespace ImageProcessorCore this.values.Add(newExifValue); } - /// + /// /// Converts this instance to a byte array. - /// + /// + /// The public byte[] ToByteArray() { if (this.values == null) - return data; + { + return this.data; + } if (this.values.Count == 0) + { return null; + } - ExifWriter writer = new ExifWriter(this.values, Parts, BestPrecision); + ExifWriter writer = new ExifWriter(this.values, this.Parts, this.BestPrecision); return writer.GetData(); } private void InitializeValues() { if (this.values != null) + { return; + } if (this.data == null) { diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs b/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs index c37d15388..ef601596e 100644 --- a/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs +++ b/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs @@ -11,97 +11,122 @@ namespace ImageProcessorCore using System.Linq; using System.Text; + /// + /// Reads and parses EXIF data from a byte array + /// internal sealed class ExifReader { private delegate TDataType ConverterMethod(byte[] data); - private byte[] data; - private Collection invalidTags = new Collection(); - private uint index; + private readonly Collection invalidTags = new Collection(); + private byte[] exifData; + private uint currentIndex; private bool isLittleEndian; private uint exifOffset; private uint gpsOffset; private uint startIndex; + /// + /// Gets the thumbnail length in the byte stream + /// public uint ThumbnailLength { get; private set; } + /// + /// Gets the thumbnail offset position in the byte stream + /// public uint ThumbnailOffset { get; private set; } + /// + /// Gets the remaining length. + /// private int RemainingLength { get { - if (this.index >= this.data.Length) + if (this.currentIndex >= this.exifData.Length) + { return 0; + } - return this.data.Length - (int)this.index; + return this.exifData.Length - (int)this.currentIndex; } } + /// + /// Reads and returns the collection of EXIF values. + /// + /// The data. + /// + /// The . + /// public Collection Read(byte[] data) { Collection result = new Collection(); - this.data = data; + this.exifData = data; - if (GetString(4) == "Exif") + if (this.GetString(4) == "Exif") { - if (GetShort() != 0) + if (this.GetShort() != 0) + { return result; + } this.startIndex = 6; } else { - this.index = 0; + this.currentIndex = 0; } - this.isLittleEndian = GetString(2) == "II"; + this.isLittleEndian = this.GetString(2) == "II"; - if (GetShort() != 0x002A) + if (this.GetShort() != 0x002A) + { return result; + } - uint ifdOffset = GetLong(); - AddValues(result, ifdOffset); + uint ifdOffset = this.GetLong(); + this.AddValues(result, ifdOffset); - uint thumbnailOffset = GetLong(); - GetThumbnail(thumbnailOffset); + uint thumbnailOffset = this.GetLong(); + this.GetThumbnail(thumbnailOffset); if (this.exifOffset != 0) - AddValues(result, this.exifOffset); + { + this.AddValues(result, this.exifOffset); + } if (this.gpsOffset != 0) - AddValues(result, this.gpsOffset); + { + this.AddValues(result, this.gpsOffset); + } return result; } - public IEnumerable InvalidTags - { - get - { - return this.invalidTags; - } - } + public IEnumerable InvalidTags => this.invalidTags; private void AddValues(Collection values, uint index) { - this.index = this.startIndex + index; + this.currentIndex = this.startIndex + index; ushort count = GetShort(); for (ushort i = 0; i < count; i++) { ExifValue value = CreateValue(); if (value == null) + { continue; + } bool duplicate = false; foreach (ExifValue val in values) @@ -114,27 +139,37 @@ namespace ImageProcessorCore } if (duplicate) + { continue; + } if (value.Tag == ExifTag.SubIFDOffset) { if (value.DataType == ExifDataType.Long) + { this.exifOffset = (uint)value.Value; + } } else if (value.Tag == ExifTag.GPSIFDOffset) { if (value.DataType == ExifDataType.Long) + { this.gpsOffset = (uint)value.Value; + } } else + { values.Add(value); + } } } private object ConvertValue(ExifDataType dataType, byte[] data, uint numberOfComponents) { if (data == null || data.Length == 0) + { return null; + } switch (dataType) { @@ -144,93 +179,120 @@ namespace ImageProcessorCore return ToString(data); case ExifDataType.Byte: if (numberOfComponents == 1) + { return ToByte(data); - else - return data; + } + + return data; case ExifDataType.DoubleFloat: if (numberOfComponents == 1) - return ToDouble(data); - else - return ToArray(dataType, data, ToDouble); + { + return this.ToDouble(data); + } + + return ToArray(dataType, data, this.ToDouble); case ExifDataType.Long: if (numberOfComponents == 1) - return ToLong(data); - else - return ToArray(dataType, data, ToLong); + { + return this.ToLong(data); + } + + return ToArray(dataType, data, ToLong); case ExifDataType.Rational: if (numberOfComponents == 1) - return ToRational(data); - else - return ToArray(dataType, data, ToRational); + { + return this.ToRational(data); + } + + return ToArray(dataType, data, this.ToRational); case ExifDataType.Short: if (numberOfComponents == 1) - return ToShort(data); - else - return ToArray(dataType, data, ToShort); + { + return this.ToShort(data); + } + + return ToArray(dataType, data, this.ToShort); case ExifDataType.SignedByte: if (numberOfComponents == 1) - return ToSignedByte(data); - else - return ToArray(dataType, data, ToSignedByte); + { + return this.ToSignedByte(data); + } + + return ToArray(dataType, data, this.ToSignedByte); case ExifDataType.SignedLong: if (numberOfComponents == 1) - return ToSignedLong(data); - else - return ToArray(dataType, data, ToSignedLong); + { + return this.ToSignedLong(data); + } + + return ToArray(dataType, data, this.ToSignedLong); case ExifDataType.SignedRational: if (numberOfComponents == 1) - return ToSignedRational(data); - else - return ToArray(dataType, data, ToSignedRational); + { + return this.ToSignedRational(data); + } + + return ToArray(dataType, data, this.ToSignedRational); case ExifDataType.SignedShort: if (numberOfComponents == 1) - return ToSignedShort(data); - else - return ToArray(dataType, data, ToSignedShort); + { + return this.ToSignedShort(data); + } + + return ToArray(dataType, data, this.ToSignedShort); case ExifDataType.SingleFloat: if (numberOfComponents == 1) + { return ToSingle(data); - else - return ToArray(dataType, data, ToSingle); + } + + return ToArray(dataType, data, ToSingle); case ExifDataType.Undefined: if (numberOfComponents == 1) + { return ToByte(data); - else - return data; + } + + return data; default: - throw new NotImplementedException(); + throw new NotSupportedException(); } } private ExifValue CreateValue() { if (RemainingLength < 12) + { return null; + } - ExifTag tag = ToEnum(GetShort(), ExifTag.Unknown); - ExifDataType dataType = ToEnum(GetShort(), ExifDataType.Unknown); - object value = null; + ExifTag tag = ToEnum(this.GetShort(), ExifTag.Unknown); + ExifDataType dataType = ToEnum(this.GetShort(), ExifDataType.Unknown); + object value; if (dataType == ExifDataType.Unknown) - return new ExifValue(tag, dataType, value, false); + { + return new ExifValue(tag, dataType, null, false); + } - uint numberOfComponents = (uint)GetLong(); + uint numberOfComponents = this.GetLong(); uint size = numberOfComponents * ExifValue.GetSize(dataType); byte[] data = GetBytes(4); if (size > 4) { - uint oldIndex = this.index; - this.index = ToLong(data) + this.startIndex; + uint oldIndex = this.currentIndex; + this.currentIndex = ToLong(data) + this.startIndex; if (RemainingLength < size) { this.invalidTags.Add(tag); - this.index = oldIndex; + this.currentIndex = oldIndex; return null; } - value = ConvertValue(dataType, GetBytes(size), numberOfComponents); - this.index = oldIndex; + + value = ConvertValue(dataType, this.GetBytes(size), numberOfComponents); + this.currentIndex = oldIndex; } else { @@ -246,19 +308,23 @@ namespace ImageProcessorCore { TEnum enumValue = (TEnum)(object)value; if (Enum.GetValues(typeof(TEnum)).Cast().Any(v => v.Equals(enumValue))) + { return enumValue; + } return defaultValue; } private byte[] GetBytes(uint length) { - if (this.index + length > (uint)this.data.Length) + if (this.currentIndex + length > (uint)this.exifData.Length) + { return null; + } byte[] data = new byte[length]; - Array.Copy(this.data, (int)this.index, data, 0, (int)length); - this.index += length; + Array.Copy(this.exifData, (int)this.currentIndex, data, 0, (int)length); + this.currentIndex += length; return data; } @@ -286,9 +352,13 @@ namespace ImageProcessorCore foreach (ExifValue value in values) { if (value.Tag == ExifTag.JPEGInterchangeFormat && (value.DataType == ExifDataType.Long)) + { ThumbnailOffset = (uint)value.Value + this.startIndex; + } else if (value.Tag == ExifTag.JPEGInterchangeFormatLength && value.DataType == ExifDataType.Long) + { ThumbnailLength = (uint)value.Value; + } } } @@ -319,7 +389,9 @@ namespace ImageProcessorCore private double ToDouble(byte[] data) { if (!ValidateArray(data, 8)) + { return default(double); + } return BitConverter.ToDouble(data, 0); } @@ -327,7 +399,9 @@ namespace ImageProcessorCore private uint ToLong(byte[] data) { if (!ValidateArray(data, 4)) + { return default(uint); + } return BitConverter.ToUInt32(data, 0); } @@ -336,7 +410,9 @@ namespace ImageProcessorCore { if (!ValidateArray(data, 2)) + { return default(ushort); + } return BitConverter.ToUInt16(data, 0); } @@ -344,7 +420,9 @@ namespace ImageProcessorCore private float ToSingle(byte[] data) { if (!ValidateArray(data, 4)) + { return default(float); + } return BitConverter.ToSingle(data, 0); } @@ -354,7 +432,9 @@ namespace ImageProcessorCore string result = Encoding.UTF8.GetString(data, 0, data.Length); int nullCharIndex = result.IndexOf('\0'); if (nullCharIndex != -1) + { result = result.Substring(0, nullCharIndex); + } return result; } @@ -362,11 +442,14 @@ namespace ImageProcessorCore private double ToRational(byte[] data) { if (!ValidateArray(data, 8, 4)) + { return default(double); + } uint numerator = BitConverter.ToUInt32(data, 0); uint denominator = BitConverter.ToUInt32(data, 4); + // TODO: investigate the possibility of a Rational struct return numerator / (double)denominator; } @@ -378,7 +461,9 @@ namespace ImageProcessorCore private int ToSignedLong(byte[] data) { if (!ValidateArray(data, 4)) + { return default(int); + } return BitConverter.ToInt32(data, 0); } @@ -386,7 +471,9 @@ namespace ImageProcessorCore private double ToSignedRational(byte[] data) { if (!ValidateArray(data, 8, 4)) + { return default(double); + } int numerator = BitConverter.ToInt32(data, 0); int denominator = BitConverter.ToInt32(data, 4); @@ -397,7 +484,9 @@ namespace ImageProcessorCore private short ToSignedShort(byte[] data) { if (!ValidateArray(data, 2)) + { return default(short); + } return BitConverter.ToInt16(data, 0); } @@ -410,10 +499,14 @@ namespace ImageProcessorCore private bool ValidateArray(byte[] data, int size, int stepSize) { if (data == null || data.Length < size) + { return false; + } if (this.isLittleEndian == BitConverter.IsLittleEndian) + { return true; + } for (int i = 0; i < data.Length; i += stepSize) {