Browse Source

Refactored the Rational type.

Former-commit-id: 1272c4c34838ae467fbe63050adb9b6a476fbcc2
Former-commit-id: a0510c5bea4432f5758af7c4058fa359794dc70b
Former-commit-id: 2f5006c4a70827e9f796698303cda409a2266ef2
af/merge-core
dirk 10 years ago
parent
commit
938a6ea5d6
  1. 219
      src/ImageProcessorCore/Numerics/BigRational.cs
  2. 545
      src/ImageProcessorCore/Numerics/Rational.cs
  3. 199
      src/ImageProcessorCore/Numerics/SignedRational.cs
  4. 10
      src/ImageProcessorCore/Profiles/Exif/ExifReader.cs
  5. 10
      src/ImageProcessorCore/Profiles/Exif/ExifValue.cs
  6. 13
      src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs
  7. 64
      tests/ImageProcessorCore.Tests/Numerics/RationalTests.cs
  8. 122
      tests/ImageProcessorCore.Tests/Numerics/SignedRationalTests.cs
  9. 110
      tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs

219
src/ImageProcessorCore/Numerics/BigRational.cs

@ -0,0 +1,219 @@
// <copyright file="Rational.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.Text;
internal struct BigRational : IEquatable<BigRational>
{
private bool IsIndeterminate
{
get
{
if (Denominator != 0)
return false;
return Numerator == 0;
}
}
private bool IsInteger => Denominator == 1;
private bool IsNegativeInfinity
{
get
{
if (Denominator != 0)
return false;
return Numerator == -1;
}
}
private bool IsPositiveInfinity
{
get
{
if (Denominator != 0)
return false;
return Numerator == 1;
}
}
private bool IsZero
{
get
{
if (Denominator != 1)
return false;
return Numerator == 0;
}
}
private static long GreatestCommonDivisor(long a, long b)
{
return b == 0 ? a : GreatestCommonDivisor(b, a % b);
}
private void Simplify()
{
if (IsIndeterminate)
return;
if (IsNegativeInfinity)
return;
if (IsPositiveInfinity)
return;
if (IsInteger)
return;
if (IsZero)
return;
if (Numerator == 0)
{
Denominator = 0;
return;
}
if (Numerator == Denominator)
{
Numerator = 1;
Denominator = 1;
}
long gcd = GreatestCommonDivisor(Math.Abs(Numerator), Math.Abs(Denominator));
if (gcd > 1)
{
Numerator = Numerator / gcd;
Denominator = Denominator / gcd;
}
}
public BigRational(long numerator, long denominator)
: this(numerator, denominator, false)
{
}
public BigRational(long numerator, long denominator, bool simplify)
{
Numerator = numerator;
Denominator = denominator;
if (simplify)
Simplify();
}
public BigRational(double value, bool bestPrecision)
{
if (double.IsNaN(value))
{
Numerator = Denominator = 0;
return;
}
if (double.IsPositiveInfinity(value))
{
Numerator = 1;
Denominator = 0;
return;
}
if (double.IsNegativeInfinity(value))
{
Numerator = -1;
Denominator = 0;
return;
}
Numerator = 1;
Denominator = 1;
double val = Math.Abs(value);
double df = Numerator / Denominator;
double epsilon = bestPrecision ? double.Epsilon : .000001;
while (Math.Abs(df - val) > epsilon)
{
if (df < val)
Numerator++;
else
{
Denominator++;
Numerator = (int)(val * Denominator);
}
df = Numerator / (double)Denominator;
}
if (value < 0.0)
Numerator *= -1;
Simplify();
}
public long Denominator
{
get;
private set;
}
public long Numerator
{
get;
private set;
}
public bool Equals(BigRational other)
{
if (Denominator == other.Denominator)
return Numerator == other.Numerator;
if (Numerator == 0 && Denominator == 0)
return other.Numerator == 0 && other.Denominator == 0;
if (other.Numerator == 0 && other.Denominator == 0)
return Numerator == 0 && Denominator == 0;
return (Numerator * other.Denominator) == (Denominator * other.Numerator);
}
public override int GetHashCode()
{
return ((Numerator * 397) ^ Denominator).GetHashCode();
}
public string ToString(IFormatProvider provider)
{
if (IsIndeterminate)
return "[ Indeterminate ]";
if (IsPositiveInfinity)
return "[ PositiveInfinity ]";
if (IsNegativeInfinity)
return "[ NegativeInfinity ]";
if (IsZero)
return "0";
if (IsInteger)
return Numerator.ToString(provider);
StringBuilder sb = new StringBuilder();
sb.Append(Numerator.ToString(provider));
sb.Append("/");
sb.Append(Denominator.ToString(provider));
return sb.ToString();
}
}
}

545
src/ImageProcessorCore/Numerics/Rational.cs

@ -7,11 +7,9 @@ namespace ImageProcessorCore
{
using System;
using System.Globalization;
using System.Numerics;
using System.Text;
/// <summary>
/// Represents a number that can be expressed as a fraction
/// 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.
@ -19,532 +17,183 @@ namespace ImageProcessorCore
public struct Rational : IEquatable<Rational>
{
/// <summary>
/// Represents a rational object that is not a number; NaN (0, 0)
/// Initializes a new instance of the <see cref="Rational"/> struct.
/// </summary>
public static Rational Indeterminate = new Rational(BigInteger.Zero);
/// <summary>
/// Represents a rational object that is equal to 0 (0, 1)
/// </summary>
public static Rational Zero = new Rational(BigInteger.Zero);
/// <summary>
/// Represents a rational object that is equal to 1 (1, 1)
/// </summary>
public static Rational One = new Rational(BigInteger.One);
/// <summary>
/// Represents a Rational object that is equal to negative infinity (-1, 0)
/// </summary>
public static readonly Rational NegativeInfinity = new Rational(BigInteger.MinusOne, BigInteger.Zero);
/// <summary>
/// Represents a Rational object that is equal to positive infinity (1, 0)
/// </summary>
public static readonly Rational PositiveInfinity = new Rational(BigInteger.One, BigInteger.Zero);
/// <summary>
/// The maximum number of decimal places
/// </summary>
private const int DoubleMaxScale = 308;
/// <summary>
/// The maximum precision (numbers after the decimal point)
/// </summary>
private static readonly BigInteger DoublePrecision = BigInteger.Pow(10, DoubleMaxScale);
/// <summary>
/// Represents double.MaxValue
/// </summary>
private static readonly BigInteger DoubleMaxValue = (BigInteger)double.MaxValue;
/// <summary>
/// Represents double.MinValue
/// </summary>
private static readonly BigInteger DoubleMinValue = (BigInteger)double.MinValue;
/// <summary>
/// Initializes a new instance of the <see cref="Rational"/> struct.
/// </summary>
/// <param name="numerator">
/// The number above the line in a vulgar fraction showing how many of the parts
/// indicated by the denominator are taken.
/// </param>
/// <param name="denominator">
/// The number below the line in a vulgar fraction; a divisor.
/// </param>
public Rational(uint numerator, uint denominator)
: this()
{
this.Numerator = numerator;
this.Denominator = denominator;
this.Simplify();
}
/// <summary>
/// Initializes a new instance of the <see cref="Rational"/> struct.
/// </summary>
/// <param name="numerator">
/// The number above the line in a vulgar fraction showing how many of the parts
/// indicated by the denominator are taken.
/// </param>
/// <param name="denominator">
/// The number below the line in a vulgar fraction; a divisor.
/// </param>
public Rational(int numerator, int denominator)
: this()
///<param name="value">The <see cref="double"/> to convert to an instance of this type.</param>
public Rational(double value)
: this(value, false)
{
this.Numerator = numerator;
this.Denominator = denominator;
this.Simplify();
}
/// <summary>
/// Initializes a new instance of the <see cref="Rational"/> struct.
/// Initializes a new instance of the <see cref="Rational"/> struct.
/// </summary>
/// <param name="numerator">
/// The number above the line in a vulgar fraction showing how many of the parts
/// indicated by the denominator are taken.
/// </param>
/// <param name="denominator">
/// The number below the line in a vulgar fraction; a divisor.
/// </param>
public Rational(BigInteger numerator, BigInteger denominator)
: this()
///<param name="value">The <see cref="double"/> to convert to an instance of this type.</param>
///<param name="bestPrecision">Specifies if the instance should be created with the best precision possible.</param>
public Rational(double value, bool bestPrecision)
{
this.Numerator = numerator;
this.Denominator = denominator;
BigRational rational = new BigRational(Math.Abs(value), bestPrecision);
this.Simplify();
Numerator = (uint)rational.Numerator;
Denominator = (uint)rational.Denominator;
}
/// <summary>
/// Initializes a new instance of the <see cref="Rational"/> struct.
/// Initializes a new instance of the <see cref="Rational"/> struct.
/// </summary>
/// <param name="value">The big integer to create the rational from.</param>
public Rational(BigInteger value)
: this()
/// <param name="value">The integer to create the rational from.</param>
public Rational(uint value)
: this(value, 1)
{
this.Numerator = value;
this.Denominator = BigInteger.One;
this.Simplify();
}
/// <summary>
/// Initializes a new instance of the <see cref="Rational"/> struct.
/// Initializes a new instance of the <see cref="Rational"/> struct.
/// </summary>
/// <param name="value">The double to create the rational from.</param>
public Rational(double value)
: this()
{
if (double.IsNaN(value))
{
this = Indeterminate;
return;
}
if (double.IsPositiveInfinity(value))
{
this = PositiveInfinity;
return;
}
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));
}
/// <summary>
/// Gets the numerator of a number.
/// </summary>
public BigInteger Numerator { get; private set; }
/// <summary>
/// Gets the denominator of a number.
/// </summary>
public BigInteger Denominator { get; private set; }
/// <summary>
/// Gets a value indicating whether this instance is indeterminate.
/// </summary>
public bool IsIndeterminate
/// <param name="numerator">The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken.</param>
/// <param name="denominator">The number below the line in a vulgar fraction; a divisor.</param>
public Rational(uint numerator, uint denominator)
: this(numerator, denominator, true)
{
get
{
if (this.Denominator != BigInteger.Zero)
{
return false;
}
return this.Numerator == BigInteger.Zero;
}
}
/// <summary>
/// Gets a value indicating whether this instance is an integer (n, 1)
/// Initializes a new instance of the <see cref="Rational"/> struct.
/// </summary>
public bool IsInteger => this.Denominator == BigInteger.One;
/// <summary>
/// Gets a value indicating whether this instance is equal to 0 (0, 1)
/// </summary>
public bool IsZero
/// <param name="numerator">The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken.</param>
/// <param name="denominator">The number below the line in a vulgar fraction; a divisor.</param>
/// <param name="simplify">Specified if the rational should be simplified.</param>
public Rational(uint numerator, uint denominator, bool simplify)
{
get
{
if (this.Denominator != BigInteger.One)
{
return false;
}
BigRational rational = new BigRational(numerator, denominator, simplify);
return this.Numerator == BigInteger.Zero;
}
Numerator = (uint)rational.Numerator;
Denominator = (uint)rational.Denominator;
}
/// <summary>
/// Gets a value indicating whether this instance is equal to 1 (1, 1)
/// Determines whether the specified <see cref="Rational"/> instances are considered equal.
/// </summary>
public bool IsOne
/// <param name="left">The first <see cref="Rational"/> to compare.</param>
/// <param name="right"> The second <see cref="Rational"/> to compare.</param>
/// <returns></returns>
public static bool operator ==(Rational left, Rational right)
{
get
{
if (this.Denominator != BigInteger.One)
{
return false;
}
return this.Numerator == BigInteger.One;
}
return Equals(left, right);
}
/// <summary>
/// Gets a value indicating whether this instance is equal to negative infinity (-1, 0)
/// Determines whether the specified <see cref="Rational"/> instances are not considered equal.
/// </summary>
public bool IsNegativeInfinity
/// <param name="left">The first <see cref="Rational"/> to compare.</param>
/// <param name="right"> The second <see cref="Rational"/> to compare.</param>
/// <returns></returns>
public static bool operator !=(Rational left, Rational right)
{
get
{
if (this.Denominator != BigInteger.Zero)
{
return false;
}
return this.Numerator == BigInteger.MinusOne;
}
return !Equals(left, right);
}
/// <summary>
/// Gets a value indicating whether this instance is equal to positive infinity (1, 0)
/// Gets the numerator of a number.
/// </summary>
public bool IsPositiveInfinity
public uint Numerator
{
get
{
if (this.Denominator != BigInteger.Zero)
{
return false;
}
return this.Numerator == BigInteger.One;
}
get;
private set;
}
/// <summary>
/// Converts a rational number to the nearest double.
/// Gets the denominator of a number.
/// </summary>
/// <returns>
/// The <see cref="double"/>.
/// </returns>
public double ToDouble()
public uint Denominator
{
// Shortcut return values
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;
}
// The Double value type represents a double-precision 64-bit number with
// values ranging from -1.79769313486232e308 to +1.79769313486232e308
// values that do not fit into this range are returned as +/-Infinity
if (SafeCastToDouble(this.Numerator) && SafeCastToDouble(this.Denominator))
{
return (double)this.Numerator / (double)this.Denominator;
}
// Scale the numerator to preserve the fraction part through the integer division
// We could probably adjust this to make it less precise if need be.
BigInteger denormalized = (this.Numerator * DoublePrecision) / this.Denominator;
if (denormalized.IsZero)
{
// underflow to -+0
return (this.Numerator.Sign < 0) ? BitConverter.Int64BitsToDouble(unchecked((long)0x8000000000000000)) : 0d;
}
double result = 0;
bool isDouble = false;
int scale = DoubleMaxScale;
while (scale > 0)
{
if (!isDouble)
{
if (SafeCastToDouble(denormalized))
{
result = (double)denormalized;
isDouble = true;
}
else
{
denormalized = denormalized / 10;
}
}
result = result / 10;
scale--;
}
if (!isDouble)
{
return (this.Numerator.Sign < 0) ? double.NegativeInfinity : double.PositiveInfinity;
}
else
{
return result;
}
get;
private set;
}
/// <inheritdoc/>
///<summary>
/// Determines whether the specified <see cref="object"/> is equal to this <see cref="Rational"/>.
///</summary>
///<param name="obj">The <see cref="object"/> to compare this <see cref="Rational"/> with.</param>
public override bool Equals(object obj)
{
if (obj is Rational)
{
return this.Equals((Rational)obj);
}
return Equals((Rational)obj);
return false;
}
/// <inheritdoc/>
///<summary>
/// Determines whether the specified <see cref="Rational"/> is equal to this <see cref="Rational"/>.
///</summary>
///<param name="other">The <see cref="Rational"/> to compare this <see cref="Rational"/> with.</param>
public bool Equals(Rational other)
{
// Standard: a/b = c/d
if (this.Denominator == other.Denominator)
{
return this.Numerator == other.Numerator;
}
// Indeterminate
if (this.Numerator == BigInteger.Zero && this.Denominator == BigInteger.Zero)
{
return other.Numerator == BigInteger.Zero && other.Denominator == BigInteger.Zero;
}
if (other.Numerator == BigInteger.Zero && other.Denominator == BigInteger.Zero)
{
return this.Numerator == BigInteger.Zero && this.Denominator == BigInteger.Zero;
}
BigRational left = new BigRational(Numerator, Denominator);
BigRational right = new BigRational(other.Numerator, other.Denominator);
// ad = bc
return (this.Numerator * other.Denominator) == (this.Denominator * other.Numerator);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
return left.Equals(right);
}
/// <inheritdoc/>
public override string ToString()
///<summary>
/// Converts the specified <see cref="double"/> to an instance of this type.
///</summary>
///<param name="value">The <see cref="double"/> to convert to an instance of this type.</param>
public static Rational FromDouble(double value)
{
return this.ToString(CultureInfo.InvariantCulture);
return new Rational(value, false);
}
/// <summary>
/// Converts the numeric value of this instance to its equivalent string representation using
/// the specified culture-specific format information.
/// </summary>
/// <param name="provider">
/// An object that supplies culture-specific formatting information.
/// </param>
/// <returns></returns>
public string ToString(IFormatProvider provider)
///<summary>
/// Converts the specified <see cref="double"/> to an instance of this type.
///</summary>
///<param name="value">The <see cref="double"/> to convert to an instance of this type.</param>
///<param name="bestPrecision">Specifies if the instance should be created with the best precision possible.</param>
public static Rational FromDouble(double value, bool bestPrecision)
{
if (this.IsIndeterminate)
{
return "[ Indeterminate ]";
}
if (this.IsPositiveInfinity)
{
return "[ PositiveInfinity ]";
}
if (this.IsNegativeInfinity)
{
return "[ NegativeInfinity ]";
}
if (this.IsZero)
{
return "0";
}
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();
return new Rational(value, bestPrecision);
}
/// <summary>
/// Simplifies the rational.
/// </summary>
private void Simplify()
///<summary>
/// Serves as a hash of this type.
///</summary>
public override int GetHashCode()
{
if (this.IsIndeterminate)
{
return;
}
if (this.IsNegativeInfinity)
{
return;
}
if (this.IsPositiveInfinity)
{
return;
}
if (this.IsInteger)
{
return;
}
if (this.Numerator == BigInteger.Zero)
{
this.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;
}
BigRational self = new BigRational(Numerator, Denominator);
return self.GetHashCode();
}
/// <summary>
/// Converts the string representation of a number into its rational value
/// Converts a rational number to the nearest <see cref="double"/>.
/// </summary>
/// <param name="value">A string that contains a number to convert.</param>
/// <returns>The <see cref="Rational"/></returns>
internal static Rational Parse(string value)
/// <returns>
/// The <see cref="double"/>.
/// </returns>
public double ToDouble()
{
int periodIndex = value.IndexOf(".", StringComparison.Ordinal);
int eIndex = value.IndexOf("E", StringComparison.Ordinal);
int slashIndex = value.IndexOf("/", StringComparison.Ordinal);
// An integer such as 3
if (periodIndex == -1 && eIndex == -1 && slashIndex == -1)
{
return new Rational(BigInteger.Parse(value));
}
// A fraction such as 22/7
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 3.14159
if (eIndex == -1)
{
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 3.14159E-2
int characteristic = int.Parse(value.Substring(eIndex + 1));
BigInteger ten = 10;
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)
{
numerator = numerator * charPower;
}
else
{
denominator = denominator * charPower;
}
return new Rational(numerator, denominator);
return Numerator / (double)Denominator;
}
/// <summary>
/// Returns a value indicating whether the given big integer can be
/// safely cast to a double.
/// Converts the numeric value of this instance to its equivalent string representation.
/// </summary>
/// <param name="value">The value to test.</param>
/// <returns><c>true</c> if the value can be safely cast</returns>
private static bool SafeCastToDouble(BigInteger value)
public override string ToString()
{
return DoubleMinValue <= value && value <= DoubleMaxValue;
return ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Returns the hash code for this instance.
/// Converts the numeric value of this instance to its equivalent string representation using
/// the specified culture-specific format information.
/// </summary>
/// <param name="rational">
/// The instance of <see cref="Rational"/> to return the hash code for.
/// <param name="provider">
/// An object that supplies culture-specific formatting information.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(Rational rational)
/// <returns></returns>
public string ToString(IFormatProvider provider)
{
return ((rational.Numerator * 397) ^ rational.Denominator).GetHashCode();
BigRational rational = new BigRational(Numerator, Denominator);
return rational.ToString(provider);
}
}
}

199
src/ImageProcessorCore/Numerics/SignedRational.cs

@ -0,0 +1,199 @@
// <copyright file="Rational.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.Globalization;
/// <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.
/// </remarks>
public struct SignedRational : IEquatable<SignedRational>
{
/// <summary>
/// Initializes a new instance of the <see cref="SignedRational"/> struct.
/// </summary>
///<param name="value">The <see cref="double"/> to convert to an instance of this type.</param>
public SignedRational(double value)
: this(value, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SignedRational"/> struct.
/// </summary>
///<param name="value">The <see cref="double"/> to convert to an instance of this type.</param>
///<param name="bestPrecision">Specifies if the instance should be created with the best precision possible.</param>
public SignedRational(double value, bool bestPrecision)
{
BigRational rational = new BigRational(value, bestPrecision);
Numerator = (int)rational.Numerator;
Denominator = (int)rational.Denominator;
}
/// <summary>
/// Initializes a new instance of the <see cref="SignedRational"/> struct.
/// </summary>
/// <param name="value">The integer to create the rational from.</param>
public SignedRational(int value)
: this(value, 1)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SignedRational"/> struct.
/// </summary>
/// <param name="numerator">The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken.</param>
/// <param name="denominator">The number below the line in a vulgar fraction; a divisor.</param>
public SignedRational(int numerator, int denominator)
: this(numerator, denominator, true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SignedRational"/> struct.
/// </summary>
/// <param name="numerator">The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken.</param>
/// <param name="denominator">The number below the line in a vulgar fraction; a divisor.</param>
/// <param name="simplify">Specified if the rational should be simplified.</param>
public SignedRational(int numerator, int denominator, bool simplify)
{
BigRational rational = new BigRational(numerator, denominator, simplify);
Numerator = (int)rational.Numerator;
Denominator = (int)rational.Denominator;
}
/// <summary>
/// Determines whether the specified <see cref="SignedRational"/> instances are considered equal.
/// </summary>
/// <param name="left">The first <see cref="SignedRational"/> to compare.</param>
/// <param name="right"> The second <see cref="SignedRational"/> to compare.</param>
/// <returns></returns>
public static bool operator ==(SignedRational left, SignedRational right)
{
return Equals(left, right);
}
/// <summary>
/// Determines whether the specified <see cref="SignedRational"/> instances are not considered equal.
/// </summary>
/// <param name="left">The first <see cref="SignedRational"/> to compare.</param>
/// <param name="right"> The second <see cref="SignedRational"/> to compare.</param>
/// <returns></returns>
public static bool operator !=(SignedRational left, SignedRational right)
{
return !Equals(left, right);
}
/// <summary>
/// Gets the numerator of a number.
/// </summary>
public int Numerator
{
get;
private set;
}
/// <summary>
/// Gets the denominator of a number.
/// </summary>
public int Denominator
{
get;
private set;
}
///<summary>
/// Determines whether the specified <see cref="object"/> is equal to this <see cref="SignedRational"/>.
///</summary>
///<param name="obj">The <see cref="object"/> to compare this <see cref="SignedRational"/> with.</param>
public override bool Equals(object obj)
{
if (obj is SignedRational)
return Equals((SignedRational)obj);
return false;
}
///<summary>
/// Determines whether the specified <see cref="SignedRational"/> is equal to this <see cref="SignedRational"/>.
///</summary>
///<param name="other">The <see cref="SignedRational"/> to compare this <see cref="SignedRational"/> with.</param>
public bool Equals(SignedRational other)
{
BigRational left = new BigRational(Numerator, Denominator);
BigRational right = new BigRational(other.Numerator, other.Denominator);
return left.Equals(right);
}
///<summary>
/// Converts the specified <see cref="double"/> to an instance of this type.
///</summary>
///<param name="value">The <see cref="double"/> to convert to an instance of this type.</param>
public static SignedRational FromDouble(double value)
{
return new SignedRational(value, false);
}
///<summary>
/// Converts the specified <see cref="double"/> to an instance of this type.
///</summary>
///<param name="value">The <see cref="double"/> to convert to an instance of this type.</param>
///<param name="bestPrecision">Specifies if the instance should be created with the best precision possible.</param>
public static SignedRational FromDouble(double value, bool bestPrecision)
{
return new SignedRational(value, bestPrecision);
}
///<summary>
/// Serves as a hash of this type.
///</summary>
public override int GetHashCode()
{
BigRational self = new BigRational(Numerator, Denominator);
return self.GetHashCode();
}
/// <summary>
/// Converts a rational number to the nearest <see cref="double"/>.
/// </summary>
/// <returns>
/// The <see cref="double"/>.
/// </returns>
public double ToDouble()
{
return Numerator / (double)Denominator;
}
/// <summary>
/// Converts the numeric value of this instance to its equivalent string representation.
/// </summary>
public override string ToString()
{
return ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Converts the numeric value of this instance to its equivalent string representation using
/// the specified culture-specific format information.
/// </summary>
/// <param name="provider">
/// An object that supplies culture-specific formatting information.
/// </param>
/// <returns></returns>
public string ToString(IFormatProvider provider)
{
BigRational rational = new BigRational(Numerator, Denominator);
return rational.ToString(provider);
}
}
}

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

@ -442,13 +442,13 @@ namespace ImageProcessorCore
{
if (!this.ValidateArray(data, 8, 4))
{
return Rational.Zero;
return new Rational();
}
uint numerator = BitConverter.ToUInt32(data, 0);
uint denominator = BitConverter.ToUInt32(data, 4);
return new Rational(numerator, denominator);
return new Rational(numerator, denominator, false);
}
private sbyte ToSignedByte(byte[] data)
@ -466,17 +466,17 @@ namespace ImageProcessorCore
return BitConverter.ToInt32(data, 0);
}
private Rational ToSignedRational(byte[] data)
private SignedRational ToSignedRational(byte[] data)
{
if (!this.ValidateArray(data, 8, 4))
{
return Rational.Zero;
return new SignedRational();
}
int numerator = BitConverter.ToInt32(data, 0);
int denominator = BitConverter.ToInt32(data, 4);
return new Rational(numerator, denominator);
return new SignedRational(numerator, denominator, false);
}
private short ToSignedShort(byte[] data)

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

@ -539,13 +539,12 @@ namespace ImageProcessorCore
case ExifDataType.DoubleFloat:
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{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.Long:
Guard.IsTrue(type == typeof(uint), nameof(value), $"Value should be an unsigned int{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.Rational:
Guard.IsTrue(type == typeof(Rational), nameof(value), $"Value should be a Rational{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.Short:
Guard.IsTrue(type == typeof(ushort), nameof(value), $"Value should be an unsigned short{(this.IsArray ? " array." : ".")}");
break;
@ -555,6 +554,9 @@ namespace ImageProcessorCore
case ExifDataType.SignedLong:
Guard.IsTrue(type == typeof(int), nameof(value), $"Value should be an int{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.SignedRational:
Guard.IsTrue(type == typeof(SignedRational), nameof(value), $"Value should be a SignedRational{(this.IsArray ? " array." : ".")}");
break;
case ExifDataType.SignedShort:
Guard.IsTrue(type == typeof(short), nameof(value), $"Value should be a short{(this.IsArray ? " array." : ".")}");
break;

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

@ -329,17 +329,16 @@ 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);
Write(BitConverter.GetBytes(value.Numerator), destination, offset);
Write(BitConverter.GetBytes(value.Denominator), destination, offset + 4);
return offset + 8;
}
private int WriteSignedRational(Rational value, byte[] destination, int offset)
private int WriteSignedRational(SignedRational value, byte[] destination, int offset)
{
Write(BitConverter.GetBytes((int)value.Numerator), destination, offset);
Write(BitConverter.GetBytes((int)value.Denominator), destination, offset + 4);
Write(BitConverter.GetBytes(value.Numerator), destination, offset);
Write(BitConverter.GetBytes(value.Denominator), destination, offset + 4);
return offset + 8;
}
@ -370,7 +369,7 @@ namespace ImageProcessorCore
case ExifDataType.SignedShort:
return Write(BitConverter.GetBytes((short)value), destination, offset);
case ExifDataType.SignedRational:
return this.WriteSignedRational((Rational)value, destination, offset);
return this.WriteSignedRational((SignedRational)value, destination, offset);
case ExifDataType.SingleFloat:
return Write(BitConverter.GetBytes((float)value), destination, offset);
default:

64
tests/ImageProcessorCore.Tests/Numerics/RationalTests.cs

@ -22,6 +22,7 @@ namespace ImageProcessorCore.Tests
Rational r2 = new Rational(3, 2);
Assert.Equal(r1, r2);
Assert.True(r1 == r2);
Rational r3 = new Rational(7.55);
Rational r4 = new Rational(755, 100);
@ -41,6 +42,7 @@ namespace ImageProcessorCore.Tests
Rational second = new Rational(100, 100);
Assert.NotEqual(first, second);
Assert.True(first != second);
}
/// <summary>
@ -49,9 +51,65 @@ namespace ImageProcessorCore.Tests
[Fact]
public void ConstructorAssignsProperties()
{
Rational first = new Rational(4, 5);
Assert.Equal(4, first.Numerator);
Assert.Equal(5, first.Denominator);
Rational rational = new Rational(7, 55);
Assert.Equal(7U, rational.Numerator);
Assert.Equal(55U, rational.Denominator);
rational = new Rational(755, 100);
Assert.Equal(151U, rational.Numerator);
Assert.Equal(20U, rational.Denominator);
rational = new Rational(755, 100, false);
Assert.Equal(755U, rational.Numerator);
Assert.Equal(100U, rational.Denominator);
rational = new Rational(-7.55);
Assert.Equal(151U, rational.Numerator);
Assert.Equal(20U, rational.Denominator);
rational = new Rational(7);
Assert.Equal(7U, rational.Numerator);
Assert.Equal(1U, rational.Denominator);
}
[Fact]
public void Fraction()
{
Rational first = new Rational(1.0 / 1600);
Rational second = new Rational(1.0 / 1600, true);
Assert.False(first.Equals(second));
}
[Fact]
public void ToDouble()
{
Rational rational = new Rational(0, 0);
Assert.Equal(double.NaN, rational.ToDouble());
rational = new Rational(2, 0);
Assert.Equal(double.PositiveInfinity, rational.ToDouble());
}
[Fact]
public void ToStringRepresention()
{
Rational rational = new Rational(0, 0);
Assert.Equal("[ Indeterminate ]", rational.ToString());
rational = new Rational(double.PositiveInfinity);
Assert.Equal("[ PositiveInfinity ]", rational.ToString());
rational = new Rational(double.NegativeInfinity);
Assert.Equal("[ PositiveInfinity ]", rational.ToString());
rational = new Rational(0, 1);
Assert.Equal("0", rational.ToString());
rational = new Rational(2, 1);
Assert.Equal("2", rational.ToString());
rational = new Rational(1, 2);
Assert.Equal("1/2", rational.ToString());
}
}
}

122
tests/ImageProcessorCore.Tests/Numerics/SignedRationalTests.cs

@ -0,0 +1,122 @@
// <copyright file="RationalTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Tests
{
using Xunit;
/// <summary>
/// Tests the <see cref="SignedRational"/> struct.
/// </summary>
public class SignedRationalTests
{
/// <summary>
/// Tests the equality operators for equality.
/// </summary>
[Fact]
public void AreEqual()
{
SignedRational r1 = new SignedRational(3, 2);
SignedRational r2 = new SignedRational(3, 2);
Assert.Equal(r1, r2);
Assert.True(r1 == r2);
SignedRational r3 = new SignedRational(7.55);
SignedRational r4 = new SignedRational(755, 100);
SignedRational r5 = new SignedRational(151, 20);
Assert.Equal(r3, r4);
Assert.Equal(r4, r5);
}
/// <summary>
/// Tests the equality operators for inequality.
/// </summary>
[Fact]
public void AreNotEqual()
{
SignedRational first = new SignedRational(0, 100);
SignedRational second = new SignedRational(100, 100);
Assert.NotEqual(first, second);
Assert.True(first != second);
}
/// <summary>
/// Tests whether the Rational constructor correctly assign properties.
/// </summary>
[Fact]
public void ConstructorAssignsProperties()
{
SignedRational rational = new SignedRational(7, -55);
Assert.Equal(7, rational.Numerator);
Assert.Equal(-55, rational.Denominator);
rational = new SignedRational(-755, 100);
Assert.Equal(-151, rational.Numerator);
Assert.Equal(20, rational.Denominator);
rational = new SignedRational(-755, -100, false);
Assert.Equal(-755, rational.Numerator);
Assert.Equal(-100, rational.Denominator);
rational = new SignedRational(-151, -20);
Assert.Equal(-151, rational.Numerator);
Assert.Equal(-20, rational.Denominator);
rational = new SignedRational(-7.55);
Assert.Equal(-151, rational.Numerator);
Assert.Equal(20, rational.Denominator);
rational = new SignedRational(7);
Assert.Equal(7, rational.Numerator);
Assert.Equal(1, rational.Denominator);
}
[Fact]
public void Fraction()
{
SignedRational first = new SignedRational(1.0 / 1600);
SignedRational second = new SignedRational(1.0 / 1600, true);
Assert.False(first.Equals(second));
}
[Fact]
public void ToDouble()
{
SignedRational rational = new SignedRational(0, 0);
Assert.Equal(double.NaN, rational.ToDouble());
rational = new SignedRational(2, 0);
Assert.Equal(double.PositiveInfinity, rational.ToDouble());
rational = new SignedRational(-2, 0);
Assert.Equal(double.NegativeInfinity, rational.ToDouble());
}
[Fact]
public void ToStringRepresention()
{
SignedRational rational = new SignedRational(0, 0);
Assert.Equal("[ Indeterminate ]", rational.ToString());
rational = new SignedRational(double.PositiveInfinity);
Assert.Equal("[ PositiveInfinity ]", rational.ToString());
rational = new SignedRational(double.NegativeInfinity);
Assert.Equal("[ NegativeInfinity ]", rational.ToString());
rational = new SignedRational(0, 1);
Assert.Equal("0", rational.ToString());
rational = new SignedRational(2, 1);
Assert.Equal("2", rational.ToString());
rational = new SignedRational(1, 2);
Assert.Equal("1/2", rational.ToString());
}
}
}

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

@ -64,12 +64,11 @@ namespace ImageProcessorCore.Tests
{
using (MemoryStream memStream = new MemoryStream())
{
// double exposureTime = 1.0 / 1600;
Rational exposureTime = new Rational(1, 1600);
double exposureTime = 1.0 / 1600;
ExifProfile profile = GetExifProfile();
profile.SetValue(ExifTag.ExposureTime, exposureTime);
profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime));
Image image = new Image(1, 1);
image.ExifProfile = profile;
@ -84,12 +83,12 @@ namespace ImageProcessorCore.Tests
ExifValue value = profile.GetValue(ExifTag.ExposureTime);
Assert.NotNull(value);
Assert.Equal(exposureTime, value.Value);
Assert.NotEqual(exposureTime, ((Rational)value.Value).ToDouble());
memStream.Position = 0;
profile = GetExifProfile();
profile.SetValue(ExifTag.ExposureTime, exposureTime);
profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true));
image.ExifProfile = profile;
image.SaveAsJpeg(memStream);
@ -101,7 +100,7 @@ namespace ImageProcessorCore.Tests
Assert.NotNull(profile);
value = profile.GetValue(ExifTag.ExposureTime);
TestValue(value, exposureTime);
Assert.Equal(exposureTime, ((Rational)value.Value).ToDouble());
}
}
@ -111,104 +110,29 @@ namespace ImageProcessorCore.Tests
using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan))
{
Image image = new Image(stream);
image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, Rational.PositiveInfinity);
image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity));
image = WriteAndRead(image);
ExifValue value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
Assert.NotNull(value);
Assert.Equal(Rational.PositiveInfinity, value.Value);
Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value);
image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, Rational.NegativeInfinity);
image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity));
image = WriteAndRead(image);
value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
Assert.NotNull(value);
Assert.Equal(Rational.NegativeInfinity, value.Value);
Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value);
image.ExifProfile.SetValue(ExifTag.FlashEnergy, Rational.NegativeInfinity);
image.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity));
image = WriteAndRead(image);
value = image.ExifProfile.GetValue(ExifTag.FlashEnergy);
Assert.NotNull(value);
Assert.Equal(Rational.PositiveInfinity, value.Value);
Assert.Equal(new Rational(double.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()
{
@ -224,11 +148,11 @@ namespace ImageProcessorCore.Tests
Assert.Throws<ArgumentException>(() => { value.Value = 15; });
image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new Rational(75.55));
image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55));
value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue);
TestValue(value, new Rational(7555, 100));
TestValue(value, new SignedRational(7555, 100));
Assert.Throws<ArgumentException>(() => { value.Value = 75; });
@ -258,7 +182,7 @@ namespace ImageProcessorCore.Tests
TestValue(value, "ImageProcessorCore");
value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue);
TestValue(value, new Rational(75.55));
TestValue(value, new SignedRational(75.55));
value = image.ExifProfile.GetValue(ExifTag.XResolution);
TestValue(value, new Rational(150.0));
@ -372,6 +296,12 @@ namespace ImageProcessorCore.Tests
Assert.Equal(expected, value.Value);
}
private static void TestValue(ExifValue value, SignedRational expected)
{
Assert.NotNull(value);
Assert.Equal(expected, value.Value);
}
private static void TestValue(ExifValue value, Rational[] expected)
{
Assert.NotNull(value);

Loading…
Cancel
Save