Browse Source

Add Rational struct [skip ci]

Former-commit-id: 42f38934bd4650dfdb7e733bebcdbe11eb57cd1f
Former-commit-id: ff53006f57bd6b40ee863692b41eda42ce33b7b3
Former-commit-id: d1358cebb22a24144a19eef570c23e645a42714a
af/merge-core
James Jackson-South 10 years ago
parent
commit
b2b403ba84
  1. 397
      src/ImageProcessorCore/Numerics/Rational.cs
  2. 39
      src/ImageProcessorCore/Profiles/Exif/ExifReader.cs
  3. 14
      src/ImageProcessorCore/Profiles/Exif/ExifValue.cs
  4. 141
      src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs
  5. 1
      src/ImageProcessorCore/project.json
  6. 122
      tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs
  7. 1
      tests/ImageProcessorCore.Tests/TestImages.cs
  8. 3
      tests/ImageProcessorCore.Tests/TestImages/Formats/Jpg/exif.jpg

397
src/ImageProcessorCore/Numerics/Rational.cs

@ -0,0 +1,397 @@
// <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;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
/// <summary>
/// Represents a number that can be expressed as a fraction
/// </summary>
/// <remarks>
/// This is a very simplified implimentation of a rational number designed for use with
/// metadata only.
/// </remarks>
public struct Rational : IEquatable<Rational>
{
/// <summary>
/// Represents a rational object that is not a number.
/// </summary>
public static Rational Indeterminate = new Rational(0, 0);
/// <summary>
/// Represents a rational object that is equal to 0.
/// </summary>
public static Rational Zero = new Rational(0, 1);
/// <summary>
/// Represents a rational object that is equal to 1.
/// </summary>
public static Rational One = new Rational(1, 1);
/// <summary>
/// Represents a Rational object that is equal to negative infinity (-1, 0).
/// </summary>
public static readonly Rational NegativeInfinity = new Rational(-1, 0);
/// <summary>
/// Represents a Rational object that is equal to positive infinity (+1, 0).
/// </summary>
public static readonly Rational PositiveInfinity = new Rational(1, 0);
/// <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()
{
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(BigInteger numerator, BigInteger denominator)
: this()
{
this.Numerator = numerator;
this.Denominator = denominator;
this.Simplify();
}
/// <summary>
/// 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()
{
this.Numerator = value;
this.Denominator = BigInteger.One;
this.Simplify();
}
/// <summary>
/// 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;
}
else if (double.IsPositiveInfinity(value))
{
this = PositiveInfinity;
return;
}
else if (double.IsNegativeInfinity(value))
{
this = NegativeInfinity;
return;
}
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 => (this.Equals(Indeterminate));
/// <summary>
/// Gets a value indicating whether this instance is an integer.
/// </summary>
public bool IsInteger => (this.Denominator == 1);
/// <summary>
/// Gets a value indicating whether this instance is equal to 0
/// </summary>
public bool IsZero => (this.Equals(Zero));
/// <summary>
/// Gets a value indicating whether this instance is equal to 1.
/// </summary>
public bool IsOne => (this.Equals(One));
/// <summary>
/// Gets a value indicating whether this instance is equal to negative infinity (-1, 0).
/// </summary>
public bool IsNegativeInfinity => (this.Equals(NegativeInfinity));
/// <summary>
/// Gets a value indicating whether this instance is equal to positive infinity (1, 0).
/// </summary>
public bool IsPositiveInfinity => (this.Equals(PositiveInfinity));
/// <summary>
/// Converts a rational number to the nearest double.
/// </summary>
/// <returns>
/// The <see cref="double"/>.
/// </returns>
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);
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Rational)
{
return this.Equals((Rational)obj);
}
return false;
}
/// <inheritdoc/>
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);
}
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
return ((int)this.Numerator * 397) ^ (int)this.Denominator;
}
}
/// <inheritdoc/>
public override string ToString()
{
return this.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)
{
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();
}
/// <summary>
/// Simplifies the rational.
/// </summary>
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;
}
}
/// <summary>
/// Converts the string representation of a number into its rational value
/// </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)
{
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);
}
}
}

39
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))

14
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:

141
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:

1
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",

122
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<ArgumentException>(() => { 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<ArgumentException>(() => { 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<ArgumentException>(() => { 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);

1
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"; } }

3
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
Loading…
Cancel
Save