Browse Source

Use Rational + Moar EXIF cleanup.

Former-commit-id: 74b6d92136ee0329f3d627e9ff0ef4afce178752
Former-commit-id: 85a44b41c37f4dd15c467cbad538c0dead3d2344
Former-commit-id: 7ef29e8855f928bb5cb00e69f56e69b3bcb38612
pull/1/head
James Jackson-South 10 years ago
parent
commit
d52cf7cfcc
  1. 158
      src/ImageProcessorCore/Numerics/Rational.cs
  2. 4
      src/ImageProcessorCore/Profiles/Exif/ExifDataType.cs
  3. 5
      src/ImageProcessorCore/Profiles/Exif/ExifParts.cs
  4. 26
      src/ImageProcessorCore/Profiles/Exif/ExifProfile.cs
  5. 87
      src/ImageProcessorCore/Profiles/Exif/ExifReader.cs
  6. 191
      src/ImageProcessorCore/Profiles/Exif/ExifValue.cs
  7. 167
      src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs
  8. 1
      tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs

158
src/ImageProcessorCore/Numerics/Rational.cs

@ -8,27 +8,25 @@ namespace ImageProcessorCore
using System;
using System.Globalization;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
/// <summary>
/// Represents a number that can be expressed as a fraction
/// </summary>
/// <remarks>
/// This is a very simplified implimentation of a rational number designed for use with
/// metadata only.
/// This is a very simplified implimentation of a rational number designed for use with metadata only.
/// </remarks>
public struct Rational : IEquatable<Rational>
{
/// <summary>
/// Represents a rational object that is not a number.
/// Represents a rational object that is not a number. NaN
/// </summary>
public static Rational Indeterminate = new Rational(0, 0);
public static Rational Indeterminate = new Rational(3, 0);
/// <summary>
/// Represents a rational object that is equal to 0.
/// </summary>
public static Rational Zero = new Rational(0, 1);
public static Rational Zero = new Rational(0, 0);
/// <summary>
/// Represents a rational object that is equal to 1.
@ -127,17 +125,20 @@ namespace ImageProcessorCore
this = Indeterminate;
return;
}
else if (double.IsPositiveInfinity(value))
if (double.IsPositiveInfinity(value))
{
this = PositiveInfinity;
return;
}
else if (double.IsNegativeInfinity(value))
if (double.IsNegativeInfinity(value))
{
this = NegativeInfinity;
return;
}
// TODO: Not happy with parsing a string like this. I should be able to use maths but maths is HARD!
this = Parse(value.ToString("R", CultureInfo.InvariantCulture));
}
@ -154,32 +155,87 @@ namespace ImageProcessorCore
/// <summary>
/// Gets a value indicating whether this instance is indeterminate.
/// </summary>
public bool IsIndeterminate => (this.Equals(Indeterminate));
public bool IsIndeterminate
{
get
{
if (this.Denominator != 0)
{
return false;
}
return this.Numerator == 3;
}
}
/// <summary>
/// Gets a value indicating whether this instance is an integer.
/// </summary>
public bool IsInteger => (this.Denominator == 1);
public bool IsInteger => this.Denominator == 1;
/// <summary>
/// Gets a value indicating whether this instance is equal to 0
/// </summary>
public bool IsZero => (this.Equals(Zero));
public bool IsZero
{
get
{
if (this.Denominator != 0)
{
return false;
}
return this.Numerator == 0;
}
}
/// <summary>
/// Gets a value indicating whether this instance is equal to 1.
/// </summary>
public bool IsOne => (this.Equals(One));
public bool IsOne
{
get
{
if (this.Denominator != 1)
{
return false;
}
return this.Numerator == 1;
}
}
/// <summary>
/// Gets a value indicating whether this instance is equal to negative infinity (-1, 0).
/// </summary>
public bool IsNegativeInfinity => (this.Equals(NegativeInfinity));
public bool IsNegativeInfinity
{
get
{
if (this.Denominator != 0)
{
return false;
}
return this.Numerator == -1;
}
}
/// <summary>
/// Gets a value indicating whether this instance is equal to positive infinity (1, 0).
/// Gets a value indicating whether this instance is equal to positive infinity (+1, 0).
/// </summary>
public bool IsPositiveInfinity => (this.Equals(PositiveInfinity));
public bool IsPositiveInfinity
{
get
{
if (this.Denominator != 0)
{
return false;
}
return this.Numerator == 1;
}
}
/// <summary>
/// Converts a rational number to the nearest double.
@ -226,31 +282,31 @@ namespace ImageProcessorCore
/// <inheritdoc/>
public bool Equals(Rational other)
{
// Standard: a/b = c/d
if (this.Denominator == other.Denominator)
{
return this.Numerator == other.Numerator;
}
else if (this.Numerator == BigInteger.Zero && this.Denominator == BigInteger.Zero)
{
return other.Numerator == BigInteger.Zero && other.Denominator == BigInteger.Zero;
}
else if (other.Numerator == BigInteger.Zero && other.Denominator == BigInteger.Zero)
// Indeterminate
if (this.Numerator == 3 && this.Denominator == 0)
{
return this.Numerator == BigInteger.Zero && this.Denominator == BigInteger.Zero;
return other.Numerator == 3 && other.Denominator == 0;
}
else
if (other.Numerator == 3 && other.Denominator == 0)
{
return (this.Numerator * other.Denominator) == (this.Denominator * other.Numerator);
return this.Numerator == 3 && this.Denominator == 0;
}
// ad = bc
return (this.Numerator * other.Denominator) == (this.Denominator * other.Numerator);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
return ((int)this.Numerator * 397) ^ (int)this.Denominator;
}
return this.GetHashCode(this);
}
/// <inheritdoc/>
@ -268,7 +324,6 @@ namespace ImageProcessorCore
/// </param>
/// <returns></returns>
public string ToString(IFormatProvider provider)
{
if (this.IsIndeterminate)
{
@ -285,6 +340,11 @@ namespace ImageProcessorCore
return "[ NegativeInfinity ]";
}
if (this.IsZero)
{
return "[ Zero ]";
}
if (this.IsInteger)
{
return this.Numerator.ToString(provider);
@ -322,16 +382,16 @@ namespace ImageProcessorCore
return;
}
if (this.Numerator == BigInteger.Zero)
if (this.Numerator == 0)
{
Denominator = BigInteger.One;
this.Denominator = 1;
return;
}
if (this.Numerator == this.Denominator)
{
this.Numerator = BigInteger.One;
this.Denominator = BigInteger.One;
this.Numerator = 1;
this.Denominator = 1;
return;
}
@ -350,36 +410,36 @@ namespace ImageProcessorCore
/// <returns>The <see cref="Rational"/></returns>
internal static Rational Parse(string value)
{
int periodIndex = value.IndexOf(".");
int eIndeix = value.IndexOf("E");
int slashIndex = value.IndexOf("/");
int periodIndex = value.IndexOf(".", StringComparison.Ordinal);
int eIndex = value.IndexOf("E", StringComparison.Ordinal);
int slashIndex = value.IndexOf("/", StringComparison.Ordinal);
// An integer such as 7
if (periodIndex == -1 && eIndeix == -1 && slashIndex == -1)
if (periodIndex == -1 && eIndex == -1 && slashIndex == -1)
{
return new Rational(BigInteger.Parse(value));
}
// A fraction such as 3/7
if (periodIndex == -1 && eIndeix == -1 && slashIndex != -1)
if (periodIndex == -1 && eIndex == -1 && slashIndex != -1)
{
return new Rational(BigInteger.Parse(value.Substring(0, slashIndex)),
BigInteger.Parse(value.Substring(slashIndex + 1)));
}
// No scientific Notation such as 5.997
if (eIndeix == -1)
if (eIndex == -1)
{
BigInteger n = BigInteger.Parse(value.Replace(".", ""));
BigInteger n = BigInteger.Parse(value.Replace(".", string.Empty));
BigInteger d = (BigInteger)Math.Pow(10, value.Length - periodIndex - 1);
return new Rational(n, d);
}
// Scientific notation such as 2.4556E-2
int characteristic = int.Parse(value.Substring(eIndeix + 1));
int characteristic = int.Parse(value.Substring(eIndex + 1));
BigInteger ten = 10;
BigInteger numerator = BigInteger.Parse(value.Substring(0, eIndeix).Replace(".", ""));
BigInteger denominator = new BigInteger(Math.Pow(10, eIndeix - periodIndex - 1));
BigInteger numerator = BigInteger.Parse(value.Substring(0, eIndex).Replace(".", string.Empty));
BigInteger denominator = new BigInteger(Math.Pow(10, eIndex - periodIndex - 1));
BigInteger charPower = BigInteger.Pow(ten, Math.Abs(characteristic));
if (characteristic > 0)
@ -393,5 +453,19 @@ namespace ImageProcessorCore
return new Rational(numerator, denominator);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="rational">
/// The instance of <see cref="Rational"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(Rational rational)
{
return ((rational.Numerator * 397) ^ rational.Denominator).GetHashCode();
}
}
}

4
src/ImageProcessorCore/Profiles/Exif/ExifDataType.cs

@ -5,9 +5,9 @@
namespace ImageProcessorCore
{
///<summary>
/// <summary>
/// Specifies exif data types.
///</summary>
/// </summary>
public enum ExifDataType
{
/// <summary>

5
src/ImageProcessorCore/Profiles/Exif/ExifParts.cs

@ -7,13 +7,12 @@ namespace ImageProcessorCore
{
using System;
///<summary>
/// <summary>
/// Specifies which parts will be written when the profile is added to an image.
///</summary>
/// </summary>
[Flags]
public enum ExifParts
{
/// <summary>
/// None
/// </summary>

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

@ -2,9 +2,9 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
@ -54,7 +54,6 @@ namespace ImageProcessorCore
public ExifProfile(byte[] data)
{
this.Parts = ExifParts.All;
this.BestPrecision = false;
this.data = data;
this.invalidTags = new List<ExifTag>();
}
@ -70,8 +69,6 @@ namespace ImageProcessorCore
Guard.NotNull(other, nameof(other));
this.Parts = other.Parts;
this.BestPrecision = other.BestPrecision;
this.thumbnailLength = other.thumbnailLength;
this.thumbnailOffset = other.thumbnailOffset;
this.invalidTags = new List<ExifTag>(other.invalidTags);
@ -89,17 +86,6 @@ namespace ImageProcessorCore
}
}
/// <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>
/// Gets or sets which parts will be written when the profile is added to an image.
/// </summary>
@ -121,7 +107,7 @@ namespace ImageProcessorCore
{
get
{
InitializeValues();
this.InitializeValues();
return this.values;
}
}
@ -135,7 +121,7 @@ namespace ImageProcessorCore
where T : IPackedVector<TP>
where TP : struct
{
InitializeValues();
this.InitializeValues();
if (this.thumbnailOffset == 0 || this.thumbnailLength == 0)
{
@ -159,7 +145,7 @@ namespace ImageProcessorCore
/// <param name="tag">The tag of the EXIF value.</param>
public ExifValue GetValue(ExifTag tag)
{
foreach (ExifValue exifValue in Values)
foreach (ExifValue exifValue in this.Values)
{
if (exifValue.Tag == tag)
return exifValue;
@ -174,7 +160,7 @@ namespace ImageProcessorCore
/// <param name="tag">The tag of the EXIF value.</param>
public bool RemoveValue(ExifTag tag)
{
InitializeValues();
this.InitializeValues();
for (int i = 0; i < this.values.Count; i++)
{
@ -224,7 +210,7 @@ namespace ImageProcessorCore
return null;
}
ExifWriter writer = new ExifWriter(this.values, this.Parts, this.BestPrecision);
ExifWriter writer = new ExifWriter(this.values, this.Parts);
return writer.GetData();
}

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

@ -2,7 +2,6 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
@ -118,11 +117,11 @@ namespace ImageProcessorCore
private void AddValues(Collection<ExifValue> values, uint index)
{
this.currentIndex = this.startIndex + index;
ushort count = GetShort();
ushort count = this.GetShort();
for (ushort i = 0; i < count; i++)
{
ExifValue value = CreateValue();
ExifValue value = this.CreateValue();
if (value == null)
{
continue;
@ -197,7 +196,7 @@ namespace ImageProcessorCore
return this.ToLong(data);
}
return ToArray(dataType, data, ToLong);
return ToArray(dataType, data, this.ToLong);
case ExifDataType.Rational:
if (numberOfComponents == 1)
{
@ -243,10 +242,10 @@ namespace ImageProcessorCore
case ExifDataType.SingleFloat:
if (numberOfComponents == 1)
{
return ToSingle(data);
return this.ToSingle(data);
}
return ToArray(dataType, data, ToSingle);
return ToArray(dataType, data, this.ToSingle);
case ExifDataType.Undefined:
if (numberOfComponents == 1)
{
@ -261,13 +260,13 @@ namespace ImageProcessorCore
private ExifValue CreateValue()
{
if (RemainingLength < 12)
if (this.RemainingLength < 12)
{
return null;
}
ExifTag tag = ToEnum(this.GetShort(), ExifTag.Unknown);
ExifDataType dataType = ToEnum(this.GetShort(), ExifDataType.Unknown);
ExifTag tag = this.ToEnum(this.GetShort(), ExifTag.Unknown);
ExifDataType dataType = this.ToEnum(this.GetShort(), ExifDataType.Unknown);
object value;
if (dataType == ExifDataType.Unknown)
@ -278,25 +277,25 @@ namespace ImageProcessorCore
uint numberOfComponents = this.GetLong();
uint size = numberOfComponents * ExifValue.GetSize(dataType);
byte[] data = GetBytes(4);
byte[] data = this.GetBytes(4);
if (size > 4)
{
uint oldIndex = this.currentIndex;
this.currentIndex = ToLong(data) + this.startIndex;
if (RemainingLength < size)
this.currentIndex = this.ToLong(data) + this.startIndex;
if (this.RemainingLength < size)
{
this.invalidTags.Add(tag);
this.currentIndex = oldIndex;
return null;
}
value = ConvertValue(dataType, this.GetBytes(size), numberOfComponents);
value = this.ConvertValue(dataType, this.GetBytes(size), numberOfComponents);
this.currentIndex = oldIndex;
}
else
{
value = ConvertValue(dataType, data, numberOfComponents);
value = this.ConvertValue(dataType, data, numberOfComponents);
}
bool isArray = value != null && numberOfComponents > 1;
@ -331,38 +330,38 @@ namespace ImageProcessorCore
private uint GetLong()
{
return ToLong(GetBytes(4));
return this.ToLong(this.GetBytes(4));
}
private ushort GetShort()
{
return ToShort(GetBytes(2));
return this.ToShort(this.GetBytes(2));
}
private string GetString(uint length)
{
return ToString(GetBytes(length));
return ToString(this.GetBytes(length));
}
private void GetThumbnail(uint offset)
{
Collection<ExifValue> values = new Collection<ExifValue>();
AddValues(values, offset);
this.AddValues(values, offset);
foreach (ExifValue value in values)
{
if (value.Tag == ExifTag.JPEGInterchangeFormat && (value.DataType == ExifDataType.Long))
{
ThumbnailOffset = (uint)value.Value + this.startIndex;
this.ThumbnailOffset = (uint)value.Value + this.startIndex;
}
else if (value.Tag == ExifTag.JPEGInterchangeFormatLength && value.DataType == ExifDataType.Long)
{
ThumbnailLength = (uint)value.Value;
this.ThumbnailLength = (uint)value.Value;
}
}
}
private static TDataType[] ToArray<TDataType>(ExifDataType dataType, Byte[] data,
private static TDataType[] ToArray<TDataType>(ExifDataType dataType, byte[] data,
ConverterMethod<TDataType> converter)
{
int dataTypeSize = (int)ExifValue.GetSize(dataType);
@ -388,7 +387,7 @@ namespace ImageProcessorCore
private double ToDouble(byte[] data)
{
if (!ValidateArray(data, 8))
if (!this.ValidateArray(data, 8))
{
return default(double);
}
@ -398,7 +397,7 @@ namespace ImageProcessorCore
private uint ToLong(byte[] data)
{
if (!ValidateArray(data, 4))
if (!this.ValidateArray(data, 4))
{
return default(uint);
}
@ -409,7 +408,7 @@ namespace ImageProcessorCore
private ushort ToShort(byte[] data)
{
if (!ValidateArray(data, 2))
if (!this.ValidateArray(data, 2))
{
return default(ushort);
}
@ -419,7 +418,7 @@ namespace ImageProcessorCore
private float ToSingle(byte[] data)
{
if (!ValidateArray(data, 4))
if (!this.ValidateArray(data, 4))
{
return default(float);
}
@ -441,7 +440,7 @@ namespace ImageProcessorCore
private Rational ToRational(byte[] data)
{
if (!ValidateArray(data, 8, 4))
if (!this.ValidateArray(data, 8, 4))
{
return Rational.Zero;
}
@ -449,24 +448,9 @@ namespace ImageProcessorCore
uint numerator = BitConverter.ToUInt32(data, 0);
uint denominator = BitConverter.ToUInt32(data, 4);
// TODO: investigate the possibility of a Rational struct
return new Rational(numerator, denominator);
}
//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;
//}
private sbyte ToSignedByte(byte[] data)
{
return unchecked((sbyte)data[0]);
@ -474,7 +458,7 @@ namespace ImageProcessorCore
private int ToSignedLong(byte[] data)
{
if (!ValidateArray(data, 4))
if (!this.ValidateArray(data, 4))
{
return default(int);
}
@ -484,7 +468,7 @@ namespace ImageProcessorCore
private Rational ToSignedRational(byte[] data)
{
if (!ValidateArray(data, 8, 4))
if (!this.ValidateArray(data, 8, 4))
{
return Rational.Zero;
}
@ -495,22 +479,9 @@ namespace ImageProcessorCore
return new Rational(numerator, denominator);
}
//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);
// return numerator / (double)denominator;
//}
private short ToSignedShort(byte[] data)
{
if (!ValidateArray(data, 2))
if (!this.ValidateArray(data, 2))
{
return default(short);
}
@ -520,7 +491,7 @@ namespace ImageProcessorCore
private bool ValidateArray(byte[] data, int size)
{
return ValidateArray(data, size, size);
return this.ValidateArray(data, size, size);
}
private bool ValidateArray(byte[] data, int size, int stepSize)

191
src/ImageProcessorCore/Profiles/Exif/ExifValue.cs

@ -14,7 +14,7 @@ namespace ImageProcessorCore
/// </summary>
public sealed class ExifValue : IEquatable<ExifValue>
{
private object value;
private object exifValue;
/// <summary>
/// Initializes a new instance of the <see cref="ExifValue"/> class
@ -26,18 +26,18 @@ namespace ImageProcessorCore
{
Guard.NotNull(other, nameof(other));
DataType = other.DataType;
IsArray = other.IsArray;
Tag = other.Tag;
this.DataType = other.DataType;
this.IsArray = other.IsArray;
this.Tag = other.Tag;
if (!other.IsArray)
{
value = other.value;
this.exifValue = other.exifValue;
}
else
{
Array array = (Array)other.value;
value = array.Clone();
Array array = (Array)other.exifValue;
this.exifValue = array.Clone();
}
}
@ -47,7 +47,6 @@ namespace ImageProcessorCore
public ExifDataType DataType
{
get;
private set;
}
/// <summary>
@ -56,7 +55,6 @@ namespace ImageProcessorCore
public bool IsArray
{
get;
private set;
}
/// <summary>
@ -65,7 +63,6 @@ namespace ImageProcessorCore
public ExifTag Tag
{
get;
private set;
}
/// <summary>
@ -75,12 +72,12 @@ namespace ImageProcessorCore
{
get
{
return this.value;
return this.exifValue;
}
set
{
CheckValue(value);
this.value = value;
this.CheckValue(value);
this.exifValue = value;
}
}
@ -113,9 +110,11 @@ namespace ImageProcessorCore
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj))
{
return true;
}
return Equals(obj as ExifValue);
return this.Equals(obj as ExifValue);
}
///<summary>
@ -125,45 +124,50 @@ namespace ImageProcessorCore
public bool Equals(ExifValue other)
{
if (ReferenceEquals(other, null))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return
Tag == other.Tag &&
DataType == other.DataType &&
object.Equals(this.value, other.value);
this.Tag == other.Tag &&
this.DataType == other.DataType &&
Equals(this.exifValue, other.exifValue);
}
///<summary>
/// Serves as a hash of this type.
///</summary>
/// <inheritdoc/>
public override int GetHashCode()
{
int hashCode = Tag.GetHashCode() ^ DataType.GetHashCode();
return this.value != null ? hashCode ^ this.value.GetHashCode() : hashCode;
return this.GetHashCode(this);
}
///<summary>
/// Returns a string that represents the current value.
///</summary>
/// <inheritdoc/>
public override string ToString()
{
if (this.value == null)
if (this.exifValue == null)
{
return null;
}
if (DataType == ExifDataType.Ascii)
return (string)this.value;
if (this.DataType == ExifDataType.Ascii)
{
return (string)this.exifValue;
}
if (!IsArray)
return ToString(this.value);
if (!this.IsArray)
{
return this.ToString(this.exifValue);
}
StringBuilder sb = new StringBuilder();
foreach (object value in (Array)this.value)
foreach (object value in (Array)this.exifValue)
{
sb.Append(ToString(value));
sb.Append(this.ToString(value));
sb.Append(" ");
}
@ -174,11 +178,15 @@ namespace ImageProcessorCore
{
get
{
if (this.value == null)
if (this.exifValue == null)
{
return false;
}
if (DataType == ExifDataType.Ascii)
return ((string)this.value).Length > 0;
if (this.DataType == ExifDataType.Ascii)
{
return ((string)this.exifValue).Length > 0;
}
return true;
}
@ -188,10 +196,12 @@ namespace ImageProcessorCore
{
get
{
if (this.value == null)
if (this.exifValue == null)
{
return 4;
}
int size = (int)(GetSize(DataType) * NumberOfComponents);
int size = (int)(GetSize(this.DataType) * this.NumberOfComponents);
return size < 4 ? 4 : size;
}
@ -201,11 +211,15 @@ namespace ImageProcessorCore
{
get
{
if (DataType == ExifDataType.Ascii)
return Encoding.UTF8.GetBytes((string)this.value).Length;
if (this.DataType == ExifDataType.Ascii)
{
return Encoding.UTF8.GetBytes((string)this.exifValue).Length;
}
if (IsArray)
return ((Array)this.value).Length;
if (this.IsArray)
{
return ((Array)this.exifValue).Length;
}
return 1;
}
@ -213,28 +227,32 @@ namespace ImageProcessorCore
internal ExifValue(ExifTag tag, ExifDataType dataType, bool isArray)
{
Tag = tag;
DataType = dataType;
IsArray = isArray;
this.Tag = tag;
this.DataType = dataType;
this.IsArray = isArray;
if (dataType == ExifDataType.Ascii)
IsArray = false;
{
this.IsArray = false;
}
}
internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray)
: this(tag, dataType, isArray)
{
this.value = value;
this.exifValue = value;
}
internal static ExifValue Create(ExifTag tag, object value)
{
Guard.IsFalse(tag == ExifTag.Unknown, nameof(tag), "Invalid Tag");
ExifValue exifValue = null;
Type type = value != null ? value.GetType() : null;
ExifValue exifValue;
Type type = value?.GetType();
if (type != null && type.IsArray)
{
type = type.GetElementType();
}
switch (tag)
{
@ -456,7 +474,7 @@ namespace ImageProcessorCore
break;
default:
throw new NotImplementedException();
throw new NotSupportedException();
}
exifValue.Value = value;
@ -484,18 +502,20 @@ namespace ImageProcessorCore
case ExifDataType.SignedRational:
return 8;
default:
throw new NotImplementedException(dataType.ToString());
throw new NotSupportedException(dataType.ToString());
}
}
private void CheckValue(object value)
{
if (value == null)
{
return;
}
Type type = value.GetType();
if (DataType == ExifDataType.Ascii)
if (this.DataType == ExifDataType.Ascii)
{
Guard.IsTrue(type == typeof(string), nameof(value), "Value should be a string.");
return;
@ -503,45 +523,43 @@ namespace ImageProcessorCore
if (type.IsArray)
{
Guard.IsTrue(IsArray, nameof(value), "Value should not be an array.");
Guard.IsTrue(this.IsArray, nameof(value), "Value should not be an array.");
type = type.GetElementType();
}
else
{
Guard.IsFalse(IsArray, nameof(value), "Value should not be an array.");
Guard.IsFalse(this.IsArray, nameof(value), "Value should not be an array.");
}
switch (DataType)
switch (this.DataType)
{
case ExifDataType.Byte:
Guard.IsTrue(type == typeof(byte), nameof(value), $"Value should be a byte{(IsArray ? " array." : ".")}");
Guard.IsTrue(type == typeof(byte), nameof(value), $"Value should be a byte{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.DoubleFloat:
//case ExifDataType.Rational:
//case ExifDataType.SignedRational:
Guard.IsTrue(type == typeof(double), nameof(value), $"Value should be a double{(IsArray ? " array." : ".")}");
Guard.IsTrue(type == typeof(double), nameof(value), $"Value should be a double{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.Rational:
case ExifDataType.SignedRational:
Guard.IsTrue(type == typeof(Rational), nameof(value), $"Value should be a Rational{(IsArray ? " array." : ".")}");
Guard.IsTrue(type == typeof(Rational), nameof(value), $"Value should be a Rational{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.Long:
Guard.IsTrue(type == typeof(uint), nameof(value), $"Value should be an unsigned int{(IsArray ? " array." : ".")}");
Guard.IsTrue(type == typeof(uint), nameof(value), $"Value should be an unsigned int{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.Short:
Guard.IsTrue(type == typeof(ushort), nameof(value), $"Value should be an unsigned short{(IsArray ? " array." : ".")}");
Guard.IsTrue(type == typeof(ushort), nameof(value), $"Value should be an unsigned short{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.SignedByte:
Guard.IsTrue(type == typeof(sbyte), nameof(value), $"Value should be a signed byte{(IsArray ? " array." : ".")}");
Guard.IsTrue(type == typeof(sbyte), nameof(value), $"Value should be a signed byte{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.SignedLong:
Guard.IsTrue(type == typeof(int), nameof(value), $"Value should be an int{(IsArray ? " array." : ".")}");
Guard.IsTrue(type == typeof(int), nameof(value), $"Value should be an int{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.SignedShort:
Guard.IsTrue(type == typeof(short), nameof(value), $"Value should be a short{(IsArray ? " array." : ".")}");
Guard.IsTrue(type == typeof(short), nameof(value), $"Value should be a short{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.SingleFloat:
Guard.IsTrue(type == typeof(float), nameof(value), $"Value should be a float{(IsArray ? " array." : ".")}");
Guard.IsTrue(type == typeof(float), nameof(value), $"Value should be a float{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.Undefined:
Guard.IsTrue(type == typeof(byte), nameof(value), "Value should be a byte array.");
@ -554,22 +572,32 @@ namespace ImageProcessorCore
private static ExifValue CreateNumber(ExifTag tag, Type type, bool isArray)
{
if (type == null || type == typeof(ushort))
{
return new ExifValue(tag, ExifDataType.Short, isArray);
else if (type == typeof(short))
}
if (type == typeof(short))
{
return new ExifValue(tag, ExifDataType.SignedShort, isArray);
else if (type == typeof(uint))
}
if (type == typeof(uint))
{
return new ExifValue(tag, ExifDataType.Long, isArray);
else
return new ExifValue(tag, ExifDataType.SignedLong, isArray);
}
return new ExifValue(tag, ExifDataType.SignedLong, isArray);
}
private string ToString(object value)
{
string description = ExifTagDescriptionAttribute.GetDescription(Tag, value);
string description = ExifTagDescriptionAttribute.GetDescription(this.Tag, value);
if (description != null)
return description;
{
return description;
}
switch (DataType)
switch (this.DataType)
{
case ExifDataType.Ascii:
return (string)value;
@ -580,7 +608,6 @@ namespace ImageProcessorCore
case ExifDataType.Long:
return ((uint)value).ToString(CultureInfo.InvariantCulture);
case ExifDataType.Rational:
//return ((double)value).ToString(CultureInfo.InvariantCulture);
return ((Rational)value).ToString(CultureInfo.InvariantCulture);
case ExifDataType.Short:
return ((ushort)value).ToString(CultureInfo.InvariantCulture);
@ -589,7 +616,6 @@ namespace ImageProcessorCore
case ExifDataType.SignedLong:
return ((int)value).ToString(CultureInfo.InvariantCulture);
case ExifDataType.SignedRational:
//return ((double)value).ToString(CultureInfo.InvariantCulture);
return ((Rational)value).ToString(CultureInfo.InvariantCulture);
case ExifDataType.SignedShort:
return ((short)value).ToString(CultureInfo.InvariantCulture);
@ -598,8 +624,23 @@ namespace ImageProcessorCore
case ExifDataType.Undefined:
return ((byte)value).ToString("X2", CultureInfo.InvariantCulture);
default:
throw new NotImplementedException();
throw new NotSupportedException();
}
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="exif">
/// The instance of <see cref="ExifValue"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(ExifValue exif)
{
int hashCode = exif.Tag.GetHashCode() ^ exif.DataType.GetHashCode();
return hashCode ^ exif.exifValue?.GetHashCode() ?? hashCode;
}
}
}

167
src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs

@ -82,22 +82,19 @@ namespace ImageProcessorCore
private const int StartIndex = 6;
private ExifParts allowedParts;
private bool bestPrecision;
private Collection<ExifValue> values;
private Collection<int> dataOffsets;
private Collection<int> ifdIndexes;
private Collection<int> exifIndexes;
private Collection<int> gpsIndexes;
public ExifWriter(Collection<ExifValue> values, ExifParts allowedParts, bool bestPrecision)
public ExifWriter(Collection<ExifValue> values, ExifParts allowedParts)
{
this.values = values;
this.allowedParts = allowedParts;
this.bestPrecision = bestPrecision;
this.ifdIndexes = GetIndexes(ExifParts.IfdTags, IfdTags);
this.exifIndexes = GetIndexes(ExifParts.ExifTags, ExifTags);
this.gpsIndexes = GetIndexes(ExifParts.GPSTags, GPSTags);
this.ifdIndexes = this.GetIndexes(ExifParts.IfdTags, IfdTags);
this.exifIndexes = this.GetIndexes(ExifParts.ExifTags, ExifTags);
this.gpsIndexes = this.GetIndexes(ExifParts.GPSTags, GPSTags);
}
public byte[] GetData()
@ -107,25 +104,35 @@ namespace ImageProcessorCore
int gpsIndex = -1;
if (this.exifIndexes.Count > 0)
exifIndex = (int)GetIndex(this.ifdIndexes, ExifTag.SubIFDOffset);
{
exifIndex = (int)this.GetIndex(this.ifdIndexes, ExifTag.SubIFDOffset);
}
if (this.gpsIndexes.Count > 0)
gpsIndex = (int)GetIndex(this.ifdIndexes, ExifTag.GPSIFDOffset);
{
gpsIndex = (int)this.GetIndex(this.ifdIndexes, ExifTag.GPSIFDOffset);
}
uint ifdLength = 2 + GetLength(this.ifdIndexes) + 4;
uint exifLength = GetLength(this.exifIndexes);
uint gpsLength = GetLength(this.gpsIndexes);
uint ifdLength = 2 + this.GetLength(this.ifdIndexes) + 4;
uint exifLength = this.GetLength(this.exifIndexes);
uint gpsLength = this.GetLength(this.gpsIndexes);
if (exifLength > 0)
{
exifLength += 2;
}
if (gpsLength > 0)
{
gpsLength += 2;
}
length = ifdLength + exifLength + gpsLength;
if (length == 6)
{
return null;
}
length += 10 + 4 + 2;
@ -146,26 +153,30 @@ namespace ImageProcessorCore
uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength;
if (exifLength > 0)
this.values[exifIndex].Value = (ifdOffset + ifdLength);
{
this.values[exifIndex].Value = ifdOffset + ifdLength;
}
if (gpsLength > 0)
this.values[gpsIndex].Value = (ifdOffset + ifdLength + exifLength);
{
this.values[gpsIndex].Value = ifdOffset + ifdLength + exifLength;
}
i = Write(BitConverter.GetBytes(ifdOffset), result, i);
i = WriteHeaders(this.ifdIndexes, result, i);
i = this.WriteHeaders(this.ifdIndexes, result, i);
i = Write(BitConverter.GetBytes(thumbnailOffset), result, i);
i = WriteData(this.ifdIndexes, result, i);
i = this.WriteData(this.ifdIndexes, result, i);
if (exifLength > 0)
{
i = WriteHeaders(this.exifIndexes, result, i);
i = WriteData(this.exifIndexes, result, i);
i = this.WriteHeaders(this.exifIndexes, result, i);
i = this.WriteData(this.exifIndexes, result, i);
}
if (gpsLength > 0)
{
i = WriteHeaders(this.gpsIndexes, result, i);
i = WriteData(this.gpsIndexes, result, i);
i = this.WriteHeaders(this.gpsIndexes, result, i);
i = this.WriteData(this.gpsIndexes, result, i);
}
Write(BitConverter.GetBytes((ushort)0), result, i);
@ -178,7 +189,9 @@ namespace ImageProcessorCore
foreach (int index in indexes)
{
if (this.values[index].Tag == tag)
{
return index;
}
}
int newIndex = this.values.Count;
@ -190,7 +203,9 @@ namespace ImageProcessorCore
private Collection<int> GetIndexes(ExifParts part, ExifTag[] tags)
{
if (((int)this.allowedParts & (int)part) == 0)
{
return new Collection<int>();
}
Collection<int> result = new Collection<int>();
for (int i = 0; i < this.values.Count; i++)
@ -198,11 +213,15 @@ namespace ImageProcessorCore
ExifValue value = this.values[i];
if (!value.HasValue)
{
continue;
}
int index = Array.IndexOf(tags, value.Tag);
if (index > -1)
{
result.Add(i);
}
}
return result;
@ -217,9 +236,13 @@ namespace ImageProcessorCore
uint valueLength = (uint)this.values[index].Length;
if (valueLength > 4)
{
length += 12 + valueLength;
}
else
{
length += 12;
}
}
return length;
@ -235,11 +258,15 @@ namespace ImageProcessorCore
private int WriteArray(ExifValue value, byte[] destination, int offset)
{
if (value.DataType == ExifDataType.Ascii)
return WriteValue(ExifDataType.Ascii, value.Value, destination, offset);
{
return this.WriteValue(ExifDataType.Ascii, value.Value, destination, offset);
}
int newOffset = offset;
foreach (object obj in (Array)value.Value)
newOffset = WriteValue(value.DataType, obj, destination, newOffset);
{
newOffset = this.WriteValue(value.DataType, obj, destination, newOffset);
}
return newOffset;
}
@ -247,7 +274,9 @@ namespace ImageProcessorCore
private int WriteData(Collection<int> indexes, byte[] destination, int offset)
{
if (this.dataOffsets.Count == 0)
{
return offset;
}
int newOffset = offset;
@ -258,7 +287,7 @@ namespace ImageProcessorCore
if (value.Length > 4)
{
Write(BitConverter.GetBytes(newOffset - StartIndex), destination, this.dataOffsets[i++]);
newOffset = WriteValue(value, destination, newOffset);
newOffset = this.WriteValue(value, destination, newOffset);
}
}
@ -272,7 +301,9 @@ namespace ImageProcessorCore
int newOffset = Write(BitConverter.GetBytes((ushort)indexes.Count), destination, offset);
if (indexes.Count == 0)
{
return newOffset;
}
foreach (int index in indexes)
{
@ -282,9 +313,13 @@ namespace ImageProcessorCore
newOffset = Write(BitConverter.GetBytes((uint)value.NumberOfComponents), destination, newOffset);
if (value.Length > 4)
{
this.dataOffsets.Add(newOffset);
}
else
WriteValue(value, destination, newOffset);
{
this.WriteValue(value, destination, newOffset);
}
newOffset += 4;
}
@ -294,91 +329,21 @@ namespace ImageProcessorCore
private int WriteRational(Rational value, byte[] destination, int offset)
{
// Ensure no overflow
Write(BitConverter.GetBytes((uint)(value.Numerator * (value.ToDouble() < 0.0 ? -1 : 1))), destination, offset);
Write(BitConverter.GetBytes((uint)value.Denominator), destination, offset + 4);
return offset + 8;
}
//private int WriteRational(double value, byte[] destination, int offset)
//{
// uint numerator = 1;
// uint denominator = 1;
// if (double.IsPositiveInfinity(value))
// denominator = 0;
// else if (double.IsNegativeInfinity(value))
// denominator = 0;
// else
// {
// double val = Math.Abs(value);
// double df = numerator / denominator;
// double epsilon = this.bestPrecision ? double.Epsilon : .000001;
// while (Math.Abs(df - val) > epsilon)
// {
// if (df < val)
// numerator++;
// else
// {
// denominator++;
// numerator = (uint)(val * denominator);
// }
// df = numerator / (double)denominator;
// }
// }
// Write(BitConverter.GetBytes(numerator), destination, offset);
// Write(BitConverter.GetBytes(denominator), destination, offset + 4);
// return offset + 8;
//}
private int WriteSignedRational(Rational value, byte[] destination, int offset)
{
// TODO: Check this.
Write(BitConverter.GetBytes((int)value.Numerator), destination, offset);
Write(BitConverter.GetBytes((int)value.Denominator), destination, offset + 4);
return offset + 8;
}
//private int WriteSignedRational(double value, byte[] destination, int offset)
//{
// int numerator = 1;
// int denominator = 1;
// if (double.IsPositiveInfinity(value))
// denominator = 0;
// else if (double.IsNegativeInfinity(value))
// denominator = 0;
// else
// {
// double val = Math.Abs(value);
// double df = numerator / denominator;
// double epsilon = this.bestPrecision ? double.Epsilon : .000001;
// while (Math.Abs(df - val) > epsilon)
// {
// if (df < val)
// numerator++;
// else
// {
// denominator++;
// numerator = (int)(val * denominator);
// }
// df = numerator / (double)denominator;
// }
// }
// Write(BitConverter.GetBytes(numerator * (value < 0.0 ? -1 : 1)), destination, offset);
// Write(BitConverter.GetBytes(denominator), destination, offset + 4);
// return offset + 8;
//}
private int WriteValue(ExifDataType dataType, object value, byte[] destination, int offset)
{
switch (dataType)
@ -396,7 +361,7 @@ namespace ImageProcessorCore
case ExifDataType.Long:
return Write(BitConverter.GetBytes((uint)value), destination, offset);
case ExifDataType.Rational:
return WriteRational((Rational)value, destination, offset);
return this.WriteRational((Rational)value, destination, offset);
case ExifDataType.SignedByte:
destination[offset] = unchecked((byte)((sbyte)value));
return offset + 1;
@ -405,7 +370,7 @@ namespace ImageProcessorCore
case ExifDataType.SignedShort:
return Write(BitConverter.GetBytes((short)value), destination, offset);
case ExifDataType.SignedRational:
return WriteSignedRational((Rational)value, destination, offset);
return this.WriteSignedRational((Rational)value, destination, offset);
case ExifDataType.SingleFloat:
return Write(BitConverter.GetBytes((float)value), destination, offset);
default:
@ -416,9 +381,11 @@ namespace ImageProcessorCore
private int WriteValue(ExifValue value, byte[] destination, int offset)
{
if (value.IsArray && value.DataType != ExifDataType.Ascii)
return WriteArray(value, destination, offset);
else
return WriteValue(value.DataType, value.Value, destination, offset);
{
return this.WriteArray(value, destination, offset);
}
return this.WriteValue(value.DataType, value.Value, destination, offset);
}
}
}

1
tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs

@ -90,7 +90,6 @@ namespace ImageProcessorCore.Tests
profile = GetExifProfile();
profile.SetValue(ExifTag.ExposureTime, exposureTime);
profile.BestPrecision = true;
image.ExifProfile = profile;
image.SaveAsJpeg(memStream);

Loading…
Cancel
Save