diff --git a/src/ImageProcessorCore/Numerics/Rational.cs b/src/ImageProcessorCore/Numerics/Rational.cs
new file mode 100644
index 000000000..fd578dc7c
--- /dev/null
+++ b/src/ImageProcessorCore/Numerics/Rational.cs
@@ -0,0 +1,397 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.Globalization;
+ using System.Numerics;
+ using System.Runtime.InteropServices;
+ using System.Text;
+
+ ///
+ /// Represents a number that can be expressed as a fraction
+ ///
+ ///
+ /// This is a very simplified implimentation of a rational number designed for use with
+ /// metadata only.
+ ///
+ public struct Rational : IEquatable
+ {
+ ///
+ /// Represents a rational object that is not a number.
+ ///
+ public static Rational Indeterminate = new Rational(0, 0);
+
+ ///
+ /// Represents a rational object that is equal to 0.
+ ///
+ public static Rational Zero = new Rational(0, 1);
+
+ ///
+ /// Represents a rational object that is equal to 1.
+ ///
+ public static Rational One = new Rational(1, 1);
+
+ ///
+ /// Represents a Rational object that is equal to negative infinity (-1, 0).
+ ///
+ public static readonly Rational NegativeInfinity = new Rational(-1, 0);
+
+ ///
+ /// Represents a Rational object that is equal to positive infinity (+1, 0).
+ ///
+ public static readonly Rational PositiveInfinity = new Rational(1, 0);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///
+ /// The number above the line in a vulgar fraction showing how many of the parts
+ /// indicated by the denominator are taken.
+ ///
+ ///
+ /// The number below the line in a vulgar fraction; a divisor.
+ ///
+ public Rational(uint numerator, uint denominator)
+ : this()
+ {
+ this.Numerator = numerator;
+ this.Denominator = denominator;
+
+ this.Simplify();
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///
+ /// The number above the line in a vulgar fraction showing how many of the parts
+ /// indicated by the denominator are taken.
+ ///
+ ///
+ /// The number below the line in a vulgar fraction; a divisor.
+ ///
+ public Rational(int numerator, int denominator)
+ : this()
+ {
+ this.Numerator = numerator;
+ this.Denominator = denominator;
+
+ this.Simplify();
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///
+ /// The number above the line in a vulgar fraction showing how many of the parts
+ /// indicated by the denominator are taken.
+ ///
+ ///
+ /// The number below the line in a vulgar fraction; a divisor.
+ ///
+ public Rational(BigInteger numerator, BigInteger denominator)
+ : this()
+ {
+ this.Numerator = numerator;
+ this.Denominator = denominator;
+
+ this.Simplify();
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The big integer to create the rational from.
+ public Rational(BigInteger value)
+ : this()
+ {
+ this.Numerator = value;
+ this.Denominator = BigInteger.One;
+
+ this.Simplify();
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The double to create the rational from.
+ public Rational(double value)
+ : this()
+ {
+ if (double.IsNaN(value))
+ {
+ this = Indeterminate;
+ return;
+ }
+ else if (double.IsPositiveInfinity(value))
+ {
+ this = PositiveInfinity;
+ return;
+ }
+ else if (double.IsNegativeInfinity(value))
+ {
+ this = NegativeInfinity;
+ return;
+ }
+
+ this = Parse(value.ToString("R", CultureInfo.InvariantCulture));
+ }
+
+ ///
+ /// Gets the numerator of a number.
+ ///
+ public BigInteger Numerator { get; private set; }
+
+ ///
+ /// Gets the denominator of a number.
+ ///
+ public BigInteger Denominator { get; private set; }
+
+ ///
+ /// Gets a value indicating whether this instance is indeterminate.
+ ///
+ public bool IsIndeterminate => (this.Equals(Indeterminate));
+
+ ///
+ /// Gets a value indicating whether this instance is an integer.
+ ///
+ public bool IsInteger => (this.Denominator == 1);
+
+ ///
+ /// Gets a value indicating whether this instance is equal to 0
+ ///
+ public bool IsZero => (this.Equals(Zero));
+
+ ///
+ /// Gets a value indicating whether this instance is equal to 1.
+ ///
+ public bool IsOne => (this.Equals(One));
+
+ ///
+ /// Gets a value indicating whether this instance is equal to negative infinity (-1, 0).
+ ///
+ public bool IsNegativeInfinity => (this.Equals(NegativeInfinity));
+
+ ///
+ /// Gets a value indicating whether this instance is equal to positive infinity (1, 0).
+ ///
+ public bool IsPositiveInfinity => (this.Equals(PositiveInfinity));
+
+ ///
+ /// Converts a rational number to the nearest double.
+ ///
+ ///
+ /// The .
+ ///
+ public double ToDouble()
+ {
+ if (this.IsIndeterminate)
+ {
+ return double.NaN;
+ }
+
+ if (this.IsPositiveInfinity)
+ {
+ return double.PositiveInfinity;
+ }
+
+ if (this.IsNegativeInfinity)
+ {
+ return double.NegativeInfinity;
+ }
+
+ if (this.IsInteger)
+ {
+ return (double)this.Numerator;
+ }
+
+ return (double)(this.Numerator / this.Denominator);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is Rational)
+ {
+ return this.Equals((Rational)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ public bool Equals(Rational other)
+ {
+ 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)
+ {
+ return this.Numerator == BigInteger.Zero && this.Denominator == BigInteger.Zero;
+ }
+ else
+ {
+ return (this.Numerator * other.Denominator) == (this.Denominator * other.Numerator);
+ }
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return ((int)this.Numerator * 397) ^ (int)this.Denominator;
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ return this.ToString(CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Converts the numeric value of this instance to its equivalent string representation using
+ /// the specified culture-specific format information.
+ ///
+ ///
+ /// An object that supplies culture-specific formatting information.
+ ///
+ ///
+ public string ToString(IFormatProvider provider)
+
+ {
+ if (this.IsIndeterminate)
+ {
+ return "[ Indeterminate ]";
+ }
+
+ if (this.IsPositiveInfinity)
+ {
+ return "[ PositiveInfinity ]";
+ }
+
+ if (this.IsNegativeInfinity)
+ {
+ return "[ NegativeInfinity ]";
+ }
+
+ if (this.IsInteger)
+ {
+ return this.Numerator.ToString(provider);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.Append(this.Numerator.ToString("R", provider));
+ sb.Append("/");
+ sb.Append(this.Denominator.ToString("R", provider));
+ return sb.ToString();
+ }
+
+ ///
+ /// Simplifies the rational.
+ ///
+ private void Simplify()
+ {
+ if (this.IsIndeterminate)
+ {
+ return;
+ }
+
+ if (this.IsNegativeInfinity)
+ {
+ return;
+ }
+
+ if (this.IsPositiveInfinity)
+ {
+ return;
+ }
+
+ if (this.IsInteger)
+ {
+ return;
+ }
+
+ if (this.Numerator == BigInteger.Zero)
+ {
+ Denominator = BigInteger.One;
+ return;
+ }
+
+ if (this.Numerator == this.Denominator)
+ {
+ this.Numerator = BigInteger.One;
+ this.Denominator = BigInteger.One;
+ return;
+ }
+
+ BigInteger gcd = BigInteger.GreatestCommonDivisor(this.Numerator, this.Denominator);
+ if (gcd > BigInteger.One)
+ {
+ this.Numerator = this.Numerator / gcd;
+ this.Denominator = this.Denominator / gcd;
+ }
+ }
+
+ ///
+ /// Converts the string representation of a number into its rational value
+ ///
+ /// A string that contains a number to convert.
+ /// The
+ internal static Rational Parse(string value)
+ {
+ int periodIndex = value.IndexOf(".");
+ int eIndeix = value.IndexOf("E");
+ int slashIndex = value.IndexOf("/");
+
+ // An integer such as 7
+ if (periodIndex == -1 && eIndeix == -1 && slashIndex == -1)
+ {
+ return new Rational(BigInteger.Parse(value));
+ }
+
+ // A fraction such as 3/7
+ if (periodIndex == -1 && eIndeix == -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)
+ {
+ BigInteger n = BigInteger.Parse(value.Replace(".", ""));
+ 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));
+ BigInteger ten = 10;
+ BigInteger numerator = BigInteger.Parse(value.Substring(0, eIndeix).Replace(".", ""));
+ BigInteger denominator = new BigInteger(Math.Pow(10, eIndeix - periodIndex - 1));
+ BigInteger charPower = BigInteger.Pow(ten, Math.Abs(characteristic));
+
+ if (characteristic > 0)
+ {
+ numerator = numerator * charPower;
+ }
+ else
+ {
+ denominator = denominator * charPower;
+ }
+
+ return new Rational(numerator, denominator);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs b/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs
index ef601596e..1ebb9c9e9 100644
--- a/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs
@@ -439,20 +439,34 @@ namespace ImageProcessorCore
return result;
}
- private double ToRational(byte[] data)
+ private Rational ToRational(byte[] data)
{
if (!ValidateArray(data, 8, 4))
{
- return default(double);
+ return Rational.Zero;
}
uint numerator = BitConverter.ToUInt32(data, 0);
uint denominator = BitConverter.ToUInt32(data, 4);
// TODO: investigate the possibility of a Rational struct
- return numerator / (double)denominator;
+ 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]);
@@ -468,19 +482,32 @@ namespace ImageProcessorCore
return BitConverter.ToInt32(data, 0);
}
- private double ToSignedRational(byte[] data)
+ private Rational ToSignedRational(byte[] data)
{
if (!ValidateArray(data, 8, 4))
{
- return default(double);
+ return Rational.Zero;
}
int numerator = BitConverter.ToInt32(data, 0);
int denominator = BitConverter.ToInt32(data, 4);
- return numerator / (double)denominator;
+ 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))
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifValue.cs b/src/ImageProcessorCore/Profiles/Exif/ExifValue.cs
index 86b1d3b36..9609ff366 100644
--- a/src/ImageProcessorCore/Profiles/Exif/ExifValue.cs
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifValue.cs
@@ -517,9 +517,13 @@ namespace ImageProcessorCore
Guard.IsTrue(type == typeof(byte), nameof(value), $"Value should be a byte{(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." : ".")}");
+ break;
case ExifDataType.Rational:
case ExifDataType.SignedRational:
- Guard.IsTrue(type == typeof(double), nameof(value), $"Value should be a double{(IsArray ? " array." : ".")}");
+ Guard.IsTrue(type == typeof(Rational), nameof(value), $"Value should be a Rational{(IsArray ? " array." : ".")}");
break;
case ExifDataType.Long:
Guard.IsTrue(type == typeof(uint), nameof(value), $"Value should be an unsigned int{(IsArray ? " array." : ".")}");
@@ -543,7 +547,7 @@ namespace ImageProcessorCore
Guard.IsTrue(type == typeof(byte), nameof(value), "Value should be a byte array.");
break;
default:
- throw new NotImplementedException();
+ throw new NotSupportedException();
}
}
@@ -576,7 +580,8 @@ namespace ImageProcessorCore
case ExifDataType.Long:
return ((uint)value).ToString(CultureInfo.InvariantCulture);
case ExifDataType.Rational:
- return ((double)value).ToString(CultureInfo.InvariantCulture);
+ //return ((double)value).ToString(CultureInfo.InvariantCulture);
+ return ((Rational)value).ToString(CultureInfo.InvariantCulture);
case ExifDataType.Short:
return ((ushort)value).ToString(CultureInfo.InvariantCulture);
case ExifDataType.SignedByte:
@@ -584,7 +589,8 @@ namespace ImageProcessorCore
case ExifDataType.SignedLong:
return ((int)value).ToString(CultureInfo.InvariantCulture);
case ExifDataType.SignedRational:
- return ((double)value).ToString(CultureInfo.InvariantCulture);
+ //return ((double)value).ToString(CultureInfo.InvariantCulture);
+ return ((Rational)value).ToString(CultureInfo.InvariantCulture);
case ExifDataType.SignedShort:
return ((short)value).ToString(CultureInfo.InvariantCulture);
case ExifDataType.SingleFloat:
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs b/src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs
index cee38cda5..f41668a58 100644
--- a/src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs
@@ -292,76 +292,93 @@ namespace ImageProcessorCore
return newOffset;
}
- private int WriteRational(double value, byte[] destination, int offset)
+ private int WriteRational(Rational 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);
+ 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 WriteSignedRational(double value, byte[] destination, int offset)
+ //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)
{
- 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);
+ // 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)
@@ -379,7 +396,7 @@ namespace ImageProcessorCore
case ExifDataType.Long:
return Write(BitConverter.GetBytes((uint)value), destination, offset);
case ExifDataType.Rational:
- return WriteRational((double)value, destination, offset);
+ return WriteRational((Rational)value, destination, offset);
case ExifDataType.SignedByte:
destination[offset] = unchecked((byte)((sbyte)value));
return offset + 1;
@@ -388,7 +405,7 @@ namespace ImageProcessorCore
case ExifDataType.SignedShort:
return Write(BitConverter.GetBytes((short)value), destination, offset);
case ExifDataType.SignedRational:
- return WriteSignedRational((double)value, destination, offset);
+ return WriteSignedRational((Rational)value, destination, offset);
case ExifDataType.SingleFloat:
return Write(BitConverter.GetBytes((float)value), destination, offset);
default:
diff --git a/src/ImageProcessorCore/project.json b/src/ImageProcessorCore/project.json
index 85d2f9723..633b9ac81 100644
--- a/src/ImageProcessorCore/project.json
+++ b/src/ImageProcessorCore/project.json
@@ -28,6 +28,7 @@
"System.Resources.ResourceManager": "4.0.1",
"System.Runtime.Extensions": "4.1.0",
"System.Runtime.InteropServices": "4.1.0",
+ "System.Runtime.Numerics": "4.0.1",
"System.Text.Encoding.Extensions": "4.0.11",
"System.Threading": "4.0.11",
"System.Threading.Tasks": "4.0.11",
diff --git a/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs
index 908d49b59..31307f2cf 100644
--- a/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs
+++ b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs
@@ -64,7 +64,8 @@ namespace ImageProcessorCore.Tests
{
using (MemoryStream memStream = new MemoryStream())
{
- double exposureTime = 1.0 / 1600;
+ // double exposureTime = 1.0 / 1600;
+ Rational exposureTime = new Rational(1, 1600);
ExifProfile profile = GetExifProfile();
@@ -83,7 +84,7 @@ namespace ImageProcessorCore.Tests
ExifValue value = profile.GetValue(ExifTag.ExposureTime);
Assert.NotNull(value);
- Assert.NotEqual(exposureTime, value.Value);
+ Assert.Equal(exposureTime, value.Value);
memStream.Position = 0;
profile = GetExifProfile();
@@ -111,33 +112,108 @@ namespace ImageProcessorCore.Tests
using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan))
{
Image image = new Image(stream);
- image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, double.PositiveInfinity);
+ image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, Rational.PositiveInfinity);
image = WriteAndRead(image);
ExifValue value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
Assert.NotNull(value);
- Assert.Equal(double.PositiveInfinity, value.Value);
+ Assert.Equal(Rational.PositiveInfinity, value.Value);
- image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, double.NegativeInfinity);
+ image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, Rational.NegativeInfinity);
image = WriteAndRead(image);
value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
Assert.NotNull(value);
- Assert.Equal(double.NegativeInfinity, value.Value);
+ Assert.Equal(Rational.NegativeInfinity, value.Value);
- image.ExifProfile.SetValue(ExifTag.FlashEnergy, double.NegativeInfinity);
+ image.ExifProfile.SetValue(ExifTag.FlashEnergy, Rational.NegativeInfinity);
image = WriteAndRead(image);
value = image.ExifProfile.GetValue(ExifTag.FlashEnergy);
Assert.NotNull(value);
- Assert.Equal(double.PositiveInfinity, value.Value);
+ Assert.Equal(Rational.PositiveInfinity, value.Value);
}
}
+ //[Fact]
+ //public void WriteFraction()
+ //{
+ // using (MemoryStream memStream = new MemoryStream())
+ // {
+ // double exposureTime = 1.0 / 1600;
+
+ // ExifProfile profile = GetExifProfile();
+
+ // profile.SetValue(ExifTag.ExposureTime, exposureTime);
+
+ // Image image = new Image(1, 1);
+ // image.ExifProfile = profile;
+
+ // image.SaveAsJpeg(memStream);
+
+ // memStream.Position = 0;
+ // image = new Image(memStream);
+
+ // profile = image.ExifProfile;
+ // Assert.NotNull(profile);
+
+ // ExifValue value = profile.GetValue(ExifTag.ExposureTime);
+ // Assert.NotNull(value);
+ // Assert.NotEqual(exposureTime, value.Value);
+
+ // memStream.Position = 0;
+ // profile = GetExifProfile();
+
+ // profile.SetValue(ExifTag.ExposureTime, exposureTime);
+ // profile.BestPrecision = true;
+ // image.ExifProfile = profile;
+
+ // image.SaveAsJpeg(memStream);
+
+ // memStream.Position = 0;
+ // image = new Image(memStream);
+
+ // profile = image.ExifProfile;
+ // Assert.NotNull(profile);
+
+ // value = profile.GetValue(ExifTag.ExposureTime);
+ // TestValue(value, exposureTime);
+ // }
+ //}
+
+ //[Fact]
+ //public void ReadWriteInfinity()
+ //{
+ // using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan))
+ // {
+ // Image image = new Image(stream);
+ // image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, double.PositiveInfinity);
+
+ // image = WriteAndRead(image);
+ // ExifValue value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
+ // Assert.NotNull(value);
+ // Assert.Equal(double.PositiveInfinity, value.Value);
+
+ // image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, double.NegativeInfinity);
+
+ // image = WriteAndRead(image);
+ // value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
+ // Assert.NotNull(value);
+ // Assert.Equal(double.NegativeInfinity, value.Value);
+
+ // image.ExifProfile.SetValue(ExifTag.FlashEnergy, double.NegativeInfinity);
+
+ // image = WriteAndRead(image);
+ // value = image.ExifProfile.GetValue(ExifTag.FlashEnergy);
+ // Assert.NotNull(value);
+ // Assert.Equal(double.PositiveInfinity, value.Value);
+ // }
+ //}
+
[Fact]
public void SetValue()
{
- double[] latitude = new double[] { 12.3, 4.56, 789.0 };
+ Rational[] latitude = new Rational[] { new Rational(12.3), new Rational(4.56), new Rational(789.0) };
using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan))
{
@@ -149,17 +225,18 @@ namespace ImageProcessorCore.Tests
Assert.Throws(() => { value.Value = 15; });
- image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, 75.55);
+ image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new Rational(75.55));
value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue);
- TestValue(value, 75.55);
+
+ TestValue(value, new Rational(7555, 100));
Assert.Throws(() => { value.Value = 75; });
- image.ExifProfile.SetValue(ExifTag.XResolution, 150.0);
+ image.ExifProfile.SetValue(ExifTag.XResolution, new Rational(150.0));
value = image.ExifProfile.GetValue(ExifTag.XResolution);
- TestValue(value, 150.0);
+ TestValue(value, new Rational(150, 1));
Assert.Throws(() => { value.Value = "ImageProcessorCore"; });
@@ -182,10 +259,10 @@ namespace ImageProcessorCore.Tests
TestValue(value, "ImageProcessorCore");
value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue);
- TestValue(value, 75.55);
+ TestValue(value, new Rational(75.55));
value = image.ExifProfile.GetValue(ExifTag.XResolution);
- TestValue(value, 150.0);
+ TestValue(value, new Rational(150.0));
value = image.ExifProfile.GetValue(ExifTag.ReferenceBlackWhite);
Assert.Null(value);
@@ -277,7 +354,7 @@ namespace ImageProcessorCore.Tests
Assert.Equal("Windows Photo Editor 10.0.10011.16384", value.ToString());
if (value.Tag == ExifTag.XResolution)
- Assert.Equal(300.0, value.Value);
+ Assert.Equal(new Rational(300.0), value.Value);
if (value.Tag == ExifTag.PixelXDimension)
Assert.Equal(2338U, value.Value);
@@ -290,6 +367,19 @@ namespace ImageProcessorCore.Tests
Assert.Equal(expected, value.Value);
}
+ private static void TestValue(ExifValue value, Rational expected)
+ {
+ Assert.NotNull(value);
+ Assert.Equal(expected, value.Value);
+ }
+
+ private static void TestValue(ExifValue value, Rational[] expected)
+ {
+ Assert.NotNull(value);
+
+ Assert.Equal(expected, (ICollection)value.Value);
+ }
+
private static void TestValue(ExifValue value, double expected)
{
Assert.NotNull(value);
diff --git a/tests/ImageProcessorCore.Tests/TestImages.cs b/tests/ImageProcessorCore.Tests/TestImages.cs
index f8379d9f0..61c5f25cd 100644
--- a/tests/ImageProcessorCore.Tests/TestImages.cs
+++ b/tests/ImageProcessorCore.Tests/TestImages.cs
@@ -25,6 +25,7 @@ namespace ImageProcessorCore.Tests
{
private static readonly string folder = "TestImages/Formats/Jpg/";
+ public static string Exif { get { return folder + "exif.jpeg"; } }
public static string Floorplan { get { return folder + "Floorplan.jpeg"; } }
public static string Calliphora { get { return folder + "Calliphora.jpg"; } }
public static string Turtle { get { return folder + "turtle.jpg"; } }
diff --git a/tests/ImageProcessorCore.Tests/TestImages/Formats/Jpg/exif.jpg b/tests/ImageProcessorCore.Tests/TestImages/Formats/Jpg/exif.jpg
new file mode 100644
index 000000000..cba862660
--- /dev/null
+++ b/tests/ImageProcessorCore.Tests/TestImages/Formats/Jpg/exif.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8a9d04b92d0de5836c59ede8ae421235488e4031e893e07b1fe7e4b78f6a9901
+size 32764