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