Browse Source

Begin style 👮 for EXIF [skip ci]

Former-commit-id: 4a6cad5cac25407cab5f7ab09ff042c4ae03b1b9
Former-commit-id: 51a21bd5864dba0fe03806f9452b7b04b05aee65
Former-commit-id: f21194eb73883753cc16cbf803ee5b3434f133a1
af/merge-core
James Jackson-South 10 years ago
parent
commit
227c104460
  1. 130
      src/ImageProcessorCore/Profiles/Exif/ExifProfile.cs
  2. 231
      src/ImageProcessorCore/Profiles/Exif/ExifReader.cs

130
src/ImageProcessorCore/Profiles/Exif/ExifProfile.cs

@ -10,49 +10,67 @@ namespace ImageProcessorCore
using System.IO;
/// <summary>
/// Class that can be used to access an Exif profile.
/// Represents an EXIF profile providing access to the collection of values.
/// </summary>
public sealed class ExifProfile
{
private byte[] data;
/// <summary>
/// The byte array to read the EXIF profile from.
/// </summary>
private readonly byte[] data;
/// <summary>
/// The collection of EXIF values
/// </summary>
private Collection<ExifValue> values;
/// <summary>
/// The list of invalid EXIF tags
/// </summary>
private List<ExifTag> invalidTags;
/// <summary>
/// The thumbnail offset position in the byte stream
/// </summary>
private int thumbnailOffset;
/// <summary>
/// The thumbnail length in the byte stream
/// </summary>
private int thumbnailLength;
///<summary>
/// <summary>
/// Initializes a new instance of the <see cref="ExifProfile"/> class.
///</summary>
///<param name="data">The byte array to read the exif profile from.</param>
/// </summary>
public ExifProfile()
: this((byte[])null)
{
}
///<summary>
/// <summary>
/// Initializes a new instance of the <see cref="ExifProfile"/> class.
///</summary>
///<param name="data">The byte array to read the exif profile from.</param>
/// </summary>
/// <param name="data">The byte array to read the EXIF profile from.</param>
public ExifProfile(byte[] data)
{
Parts = ExifParts.All;
BestPrecision = false;
this.Parts = ExifParts.All;
this.BestPrecision = false;
this.data = data;
this.invalidTags = new List<ExifTag>();
}
/// <summary>
/// Initializes a new instance of the <see cref="ExifProfile"/> class
/// by making a copy from another exif profile.
/// by making a copy from another EXIF profile.
/// </summary>
/// <param name="other">The other exif profile, where the clone should be made from.</param>
/// <param name="other">The other EXIF profile, where the clone should be made from.</param>
/// <exception cref="ArgumentNullException"><paramref name="other"/> is null.</exception>
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<ExifValue>();
foreach(ExifValue value in other.values)
foreach (ExifValue value in other.values)
{
this.values.Add(new ExifValue(value));
}
@ -71,39 +89,34 @@ namespace ImageProcessorCore
}
}
///<summary>
/// 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.
///</summary>
/// <summary>
/// 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.
/// </summary>
public bool BestPrecision
{
get;
set;
}
///<summary>
/// Specifies which parts will be written when the profile is added to an image.
///</summary>
/// <summary>
/// Gets or sets which parts will be written when the profile is added to an image.
/// </summary>
public ExifParts Parts
{
get;
set;
}
///<summary>
/// Returns the tags that where found but contained an invalid value.
///</summary>
public IEnumerable<ExifTag> InvalidTags
{
get
{
return this.invalidTags;
}
}
/// <summary>
/// Gets the tags that where found but contained an invalid value.
/// </summary>
public IEnumerable<ExifTag> InvalidTags => this.invalidTags;
///<summary>
/// Returns the values of this exif profile.
///</summary>
/// <summary>
/// Gets the values of this EXIF profile.
/// </summary>
public IEnumerable<ExifValue> Values
{
get
@ -113,9 +126,9 @@ namespace ImageProcessorCore
}
}
///<summary>
/// Returns the thumbnail in the exif profile when available.
///</summary>
/// <summary>
/// Returns the thumbnail in the EXIF profile when available.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
public Image<T, TP> CreateThumbnail<T, TP>()
@ -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
}
}
///<summary>
/// <summary>
/// Returns the value with the specified tag.
///</summary>
///<param name="tag">The tag of the exif value.</param>
/// </summary>
/// <param name="tag">The tag of the EXIF value.</param>
public ExifValue GetValue(ExifTag tag)
{
foreach (ExifValue exifValue in Values)
@ -151,10 +168,10 @@ namespace ImageProcessorCore
return null;
}
///<summary>
/// <summary>
/// Removes the value with the specified tag.
///</summary>
///<param name="tag">The tag of the exif value.</param>
/// </summary>
/// <param name="tag">The tag of the EXIF value.</param>
public bool RemoveValue(ExifTag tag)
{
InitializeValues();
@ -171,14 +188,14 @@ namespace ImageProcessorCore
return false;
}
///<summary>
/// <summary>
/// Sets the value of the specified tag.
///</summary>
///<param name="tag">The tag of the exif value.</param>
///<param name="value">The value.</param>
/// </summary>
/// <param name="tag">The tag of the EXIF value.</param>
/// <param name="value">The value.</param>
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);
}
///<summary>
/// <summary>
/// Converts this instance to a byte array.
///</summary>
/// </summary>
/// <returns>The <see cref="T:byte[]"/></returns>
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)
{

231
src/ImageProcessorCore/Profiles/Exif/ExifReader.cs

@ -11,97 +11,122 @@ namespace ImageProcessorCore
using System.Linq;
using System.Text;
/// <summary>
/// Reads and parses EXIF data from a byte array
/// </summary>
internal sealed class ExifReader
{
private delegate TDataType ConverterMethod<TDataType>(byte[] data);
private byte[] data;
private Collection<ExifTag> invalidTags = new Collection<ExifTag>();
private uint index;
private readonly Collection<ExifTag> invalidTags = new Collection<ExifTag>();
private byte[] exifData;
private uint currentIndex;
private bool isLittleEndian;
private uint exifOffset;
private uint gpsOffset;
private uint startIndex;
/// <summary>
/// Gets the thumbnail length in the byte stream
/// </summary>
public uint ThumbnailLength
{
get;
private set;
}
/// <summary>
/// Gets the thumbnail offset position in the byte stream
/// </summary>
public uint ThumbnailOffset
{
get;
private set;
}
/// <summary>
/// Gets the remaining length.
/// </summary>
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;
}
}
/// <summary>
/// Reads and returns the collection of EXIF values.
/// </summary>
/// <param name="data">The data.</param>
/// <returns>
/// The <see cref="Collection{ExifValue}"/>.
/// </returns>
public Collection<ExifValue> Read(byte[] data)
{
Collection<ExifValue> result = new Collection<ExifValue>();
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<ExifTag> InvalidTags
{
get
{
return this.invalidTags;
}
}
public IEnumerable<ExifTag> InvalidTags => this.invalidTags;
private void AddValues(Collection<ExifValue> 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<TEnum>().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)
{

Loading…
Cancel
Save