diff --git a/src/Shared/AssemblyInfo.Common.cs b/src/Shared/AssemblyInfo.Common.cs
index 081b01994..b140dd917 100644
--- a/src/Shared/AssemblyInfo.Common.cs
+++ b/src/Shared/AssemblyInfo.Common.cs
@@ -10,11 +10,11 @@ using System.Runtime.CompilerServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyDescription("A cross-platform library for processing of image files; written in C#")]
+[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Scott Williams")]
-[assembly: AssemblyProduct("SixLabors.Shapes")]
-[assembly: AssemblyCopyright("Copyright (c) Scott Williams and contributors.")]
+[assembly: AssemblyProduct("SixLabors.Primitives")]
+[assembly: AssemblyCopyright("Copyright (c) Six Labors and contributors.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
@@ -34,5 +34,4 @@ using System.Runtime.CompilerServices;
[assembly: AssemblyInformationalVersion("1.0.0.0")]
// Ensure the internals can be tested.
-[assembly: InternalsVisibleTo("SixLabors.Shapes.Tests")]
-[assembly: InternalsVisibleTo("SixLabors.Shapes.Benchmarks")]
+[assembly: InternalsVisibleTo("SixLabors.Primitives.Tests")]
diff --git a/src/SixLabors.Primitives/ApproximateFloatComparer.cs b/src/SixLabors.Primitives/ApproximateFloatComparer.cs
new file mode 100644
index 000000000..0d854a110
--- /dev/null
+++ b/src/SixLabors.Primitives/ApproximateFloatComparer.cs
@@ -0,0 +1,210 @@
+//
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace SixLabors.Primitives
+{
+ internal struct ApproximateFloatComparer
+ : IEqualityComparer,
+ IEqualityComparer,
+ IEqualityComparer,
+ IEqualityComparer,
+ IEqualityComparer,
+ IEqualityComparer,
+ IEqualityComparer
+
+ {
+ private readonly float tolerance;
+ const float defaultTolerance = 1e-5f;
+
+ public ApproximateFloatComparer(float tolerance = defaultTolerance)
+ {
+ this.tolerance = tolerance;
+ }
+
+ public static bool Equal(float x, float y, float tolerance)
+ {
+ float d = x - y;
+
+ return d > -tolerance && d < tolerance;
+ }
+
+ public static bool Equal(float x, float y)
+ {
+ return Equal(x, y, defaultTolerance);
+ }
+
+ public bool Equals(float x, float y)
+ {
+ return Equal(x, y, this.tolerance);
+ }
+
+ public int GetHashCode(float obj)
+ {
+ var diff = obj % this.tolerance;// how different from tollerance are we?
+ return (obj - diff).GetHashCode();
+ }
+
+ public bool Equals(Vector4 a, Vector4 b)
+ {
+ return this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y) && this.Equals(a.Z, b.Z) && this.Equals(a.W, b.W);
+ }
+
+ public int GetHashCode(Vector4 obj)
+ {
+ int hash = GetHashCode(obj.X);
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.Y));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.Z));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.W));
+ return hash;
+ }
+
+ public bool Equals(Vector2 a, Vector2 b)
+ {
+ return this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y);
+ }
+
+ public int GetHashCode(Vector2 obj)
+ {
+ int hash = GetHashCode(obj.X);
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.Y));
+ return hash;
+ }
+
+ public bool Equals(Vector3 a, Vector3 b)
+ {
+ return this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y) && this.Equals(a.Z, b.Z);
+ }
+
+ public int GetHashCode(Vector3 obj)
+ {
+ int hash = GetHashCode(obj.X);
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.Y));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.Z));
+ return hash;
+ }
+
+ public static bool Equal(Matrix3x2 a, Matrix3x2 b, float tolerance)
+ {
+ return Equal(a.M11, b.M11, tolerance) &&
+ Equal(a.M12, b.M12, tolerance) &&
+ Equal(a.M21, b.M21, tolerance) &&
+ Equal(a.M22, b.M22, tolerance) &&
+ Equal(a.M31, b.M31, tolerance) &&
+ Equal(a.M32, b.M32, tolerance);
+ }
+
+ public static bool Equal(Matrix3x2 a, Matrix3x2 b)
+ {
+ return Equal(a, b, defaultTolerance);
+ }
+
+ public bool Equals(Matrix3x2 a, Matrix3x2 b)
+ {
+ return Equal(a, b, this.tolerance);
+ }
+
+ public int GetHashCode(Matrix3x2 obj)
+ {
+ int hash = GetHashCode(obj.M11);
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M11));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M12));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M21));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M22));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M31));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M32));
+ return hash;
+ }
+
+
+ public static bool Equal(Matrix4x4 a, Matrix4x4 b, float tolerance)
+ {
+ return
+ Equal(a.M11, b.M11, tolerance) &&
+ Equal(a.M12, b.M12, tolerance) &&
+ Equal(a.M13, b.M13, tolerance) &&
+ Equal(a.M14, b.M14, tolerance) &&
+
+ Equal(a.M21, b.M21, tolerance) &&
+ Equal(a.M22, b.M22, tolerance) &&
+ Equal(a.M23, b.M23, tolerance) &&
+ Equal(a.M24, b.M24, tolerance) &&
+
+ Equal(a.M31, b.M31, tolerance) &&
+ Equal(a.M32, b.M32, tolerance) &&
+ Equal(a.M33, b.M33, tolerance) &&
+ Equal(a.M34, b.M34, tolerance) &&
+
+ Equal(a.M41, b.M41, tolerance) &&
+ Equal(a.M42, b.M42, tolerance) &&
+ Equal(a.M43, b.M43, tolerance) &&
+ Equal(a.M44, b.M44, tolerance);
+ }
+
+ public static bool Equal(Matrix4x4 a, Matrix4x4 b)
+ {
+ return Equal(a, b, defaultTolerance);
+ }
+
+ public bool Equals(Matrix4x4 a, Matrix4x4 b)
+ {
+ return Equal(a, b, this.tolerance);
+ }
+
+
+ public int GetHashCode(Matrix4x4 obj)
+ {
+ int hash = GetHashCode(obj.M11);
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M12));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M13));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M14));
+
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M21));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M22));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M23));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M24));
+
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M31));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M32));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M33));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M34));
+
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M41));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M42));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M43));
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.M44));
+ return hash;
+ }
+
+
+ public static bool Equal(PointF a, PointF b, float tolerance)
+ {
+ return
+ Equal(a.X, b.X, tolerance) &&
+ Equal(a.Y, b.Y, tolerance);
+ }
+
+ public static bool Equal(PointF a, PointF b)
+ {
+ return Equal(a, b, defaultTolerance);
+ }
+
+ public bool Equals(PointF a, PointF b)
+ {
+ return Equal(a, b, this.tolerance);
+ }
+
+
+ public int GetHashCode(PointF obj)
+ {
+ int hash = GetHashCode(obj.X);
+ hash = HashHelpers.Combine(hash, GetHashCode(obj.Y));
+ return hash;
+ }
+ }
+}
diff --git a/src/SixLabors.Primitives/Ellipse.cs b/src/SixLabors.Primitives/Ellipse.cs
index d5611bb23..e1890df27 100644
--- a/src/SixLabors.Primitives/Ellipse.cs
+++ b/src/SixLabors.Primitives/Ellipse.cs
@@ -171,13 +171,10 @@ namespace SixLabors.Primitives
///
private int GetHashCode(Ellipse ellipse)
{
- unchecked
- {
- int hashCode = ellipse.center.GetHashCode();
- hashCode = (hashCode * 397) ^ ellipse.RadiusX.GetHashCode();
- hashCode = (hashCode * 397) ^ ellipse.RadiusY.GetHashCode();
- return hashCode;
- }
+ int hashCode = ellipse.center.GetHashCode();
+ hashCode = HashHelpers.Combine(hashCode, ellipse.RadiusX.GetHashCode());
+ hashCode = HashHelpers.Combine(hashCode, ellipse.RadiusY.GetHashCode());
+ return hashCode;
}
}
}
diff --git a/src/SixLabors.Primitives/HashHelpers.cs b/src/SixLabors.Primitives/HashHelpers.cs
new file mode 100644
index 000000000..d622d308c
--- /dev/null
+++ b/src/SixLabors.Primitives/HashHelpers.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SixLabors.Primitives
+{
+ // lifted from coreFX repo
+ internal static class HashHelpers
+ {
+ public static readonly int RandomSeed = Guid.NewGuid().GetHashCode();
+
+ public static int Combine(int h1, int h2)
+ {
+ unchecked
+ {
+ // RyuJIT optimizes this to use the ROL instruction
+ // Related GitHub pull request: dotnet/coreclr#1830
+ uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
+ return ((int)rol5 + h1) ^ h2;
+ }
+ }
+ }
+}
diff --git a/src/SixLabors.Primitives/LongRational.cs b/src/SixLabors.Primitives/LongRational.cs
index d33484338..4f01936ee 100644
--- a/src/SixLabors.Primitives/LongRational.cs
+++ b/src/SixLabors.Primitives/LongRational.cs
@@ -347,9 +347,6 @@ namespace SixLabors.Primitives
///
/// A 32-bit signed integer that is the hash code for this instance.
///
- private int GetHashCode(LongRational rational)
- {
- return ((rational.Numerator * 397) ^ rational.Denominator).GetHashCode();
- }
+ private int GetHashCode(LongRational rational) => HashHelpers.Combine(rational.Numerator.GetHashCode(), rational.Denominator.GetHashCode());
}
}
\ No newline at end of file
diff --git a/src/SixLabors.Primitives/MathF.cs b/src/SixLabors.Primitives/MathF.cs
index a15b7fb20..eeaca821b 100644
--- a/src/SixLabors.Primitives/MathF.cs
+++ b/src/SixLabors.Primitives/MathF.cs
@@ -94,7 +94,7 @@ namespace SixLabors.Primitives
/// The representing the degree as radians.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float DegreeToRadian(float degree)
+ public static float ToRadians(float degree)
{
return degree * (PI / 180F);
}
@@ -181,7 +181,7 @@ namespace SixLabors.Primitives
/// The representing the degree as radians.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static float RadianToDegree(float radian)
+ public static float ToDegree(float radian)
{
return radian / (PI / 180F);
}
diff --git a/src/SixLabors.Primitives/Matrix.cs b/src/SixLabors.Primitives/Matrix.cs
index 6da436cfb..aa97c3ed7 100644
--- a/src/SixLabors.Primitives/Matrix.cs
+++ b/src/SixLabors.Primitives/Matrix.cs
@@ -32,9 +32,43 @@ namespace SixLabors.Primitives
public bool IsIdentity => this.backingMatrix.IsIdentity;
///
- /// Gets or sets the translation component of this matrix.
+ /// Gets or Sets the translation component of this matrix.
///
- public Vector2 Translation => this.backingMatrix.Translation;
+ public PointF Translation
+ {
+ get => this.backingMatrix.Translation;
+ set => this.backingMatrix.Translation = value;
+ }
+
+ ///
+ /// The first element of the first row
+ ///
+ public float M11 { get => this.backingMatrix.M11; set => this.backingMatrix.M11 = value; }
+
+ ///
+ /// The second element of the first row
+ ///
+ public float M12 { get => this.backingMatrix.M12; set => this.backingMatrix.M12 = value; }
+
+ ///
+ /// The first element of the second row
+ ///
+ public float M21 { get => this.backingMatrix.M21; set => this.backingMatrix.M21 = value; }
+
+ ///
+ /// The second element of the second row
+ ///
+ public float M22 { get => this.backingMatrix.M22; set => this.backingMatrix.M22 = value; }
+
+ ///
+ /// The first element of the third row
+ ///
+ public float M31 { get => this.backingMatrix.M31; set => this.backingMatrix.M31 = value; }
+
+ ///
+ /// The second element of the third row
+ ///
+ public float M32 { get => this.backingMatrix.M32; set => this.backingMatrix.M32 = value; }
///
/// Constructs a Matrix3x2 from the given components.
@@ -122,7 +156,7 @@ namespace SixLabors.Primitives
/// The X angle, in degrees.
/// The Y angle, in degrees.
/// A skew matrix.
- public static Matrix CreateSkewDegrees(float degreesX, float degreesY) => Matrix3x2.CreateSkew(MathF.DegreeToRadian(degreesX), MathF.DegreeToRadian(degreesY));
+ public static Matrix CreateSkewDegrees(float degreesX, float degreesY) => Matrix3x2.CreateSkew(MathF.ToRadians(degreesX), MathF.ToRadians(degreesY));
///
/// Creates a skew matrix from the given angles in radians and a center point.
@@ -140,7 +174,7 @@ namespace SixLabors.Primitives
/// The Y angle, in degrees.
/// The center point.
/// A skew matrix.
- public static Matrix CreateSkewDegrees(float degreesX, float degreesY, PointF centerPoint) => Matrix3x2.CreateSkew(MathF.DegreeToRadian(degreesX), MathF.DegreeToRadian(degreesY), centerPoint);
+ public static Matrix CreateSkewDegrees(float degreesX, float degreesY, PointF centerPoint) => Matrix3x2.CreateSkew(MathF.ToRadians(degreesX), MathF.ToRadians(degreesY), centerPoint);
///
/// Creates a rotation matrix using the given rotation in radians.
@@ -154,7 +188,7 @@ namespace SixLabors.Primitives
///
/// The amount of rotation, in degrees.
/// A rotation matrix.
- public static Matrix CreateRotationDegrees(float degrees) => System.Numerics.Matrix3x2.CreateRotation(MathF.DegreeToRadian(degrees));
+ public static Matrix CreateRotationDegrees(float degrees) => System.Numerics.Matrix3x2.CreateRotation(MathF.ToRadians(degrees));
///
/// Creates a rotation matrix using the given rotation in radians and a center point.
@@ -170,7 +204,7 @@ namespace SixLabors.Primitives
/// The amount of rotation, in degrees.
/// The center point.
/// A rotation matrix.
- public static Matrix CreateRotationDegrees(float degrees, PointF centerPoint) => Matrix3x2.CreateRotation(MathF.DegreeToRadian(degrees), centerPoint);
+ public static Matrix CreateRotationDegrees(float degrees, PointF centerPoint) => Matrix3x2.CreateRotation(MathF.ToRadians(degrees), centerPoint);
///
/// Calculates the determinant for this matrix.
@@ -356,5 +390,10 @@ namespace SixLabors.Primitives
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Matrix(Matrix3x2 matrix) => new Matrix { backingMatrix = matrix };
+
+ internal static Matrix CreateScale(Vector2 scale, object zero)
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/src/SixLabors.Primitives/Point.cs b/src/SixLabors.Primitives/Point.cs
index 243cb8eb3..206d0545a 100644
--- a/src/SixLabors.Primitives/Point.cs
+++ b/src/SixLabors.Primitives/Point.cs
@@ -25,6 +25,11 @@ namespace SixLabors.Primitives
///
public static readonly Point Empty = default(Point);
+ ///
+ /// Represents a that has X and Y values set to zero.
+ ///
+ public static readonly Point Zero = new Point(0, 0);
+
///
/// Initializes a new instance of the struct.
///
@@ -95,6 +100,13 @@ namespace SixLabors.Primitives
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Size(Point point) => new Size(point.X, point.Y);
+ ///
+ /// Negates the given point by multiplying all values by -1.
+ ///
+ /// The source point.
+ /// The negated point.
+ public static Point operator -(Point value) => new Point(-value.X, -value.Y);
+
///
/// Translates a by a given .
///
@@ -252,6 +264,17 @@ namespace SixLabors.Primitives
private static short LowInt16(int n) => unchecked((short)(n & 0xffff));
- private int GetHashCode(Point point) => point.X ^ point.Y;
+ private int GetHashCode(Point point) => HashHelpers.Combine(point.X.GetHashCode(), point.Y.GetHashCode());
+
+ ///
+ /// Transforms a point by the given matrix.
+ ///
+ /// The source point
+ /// The transformation matrix.
+ ///
+ public static PointF Transform(Point position, Matrix matrix)
+ {
+ return Vector2.Transform(position, matrix);
+ }
}
}
\ No newline at end of file
diff --git a/src/SixLabors.Primitives/PointF.cs b/src/SixLabors.Primitives/PointF.cs
index a49a1996b..8df9ef7bf 100644
--- a/src/SixLabors.Primitives/PointF.cs
+++ b/src/SixLabors.Primitives/PointF.cs
@@ -25,6 +25,11 @@ namespace SixLabors.Primitives
///
public static readonly PointF Empty = default(PointF);
+ ///
+ /// Represents a that has X and Y values set to zero.
+ ///
+ public static readonly PointF Zero = new PointF(0, 0);
+
///
/// Initializes a new instance of the struct.
///
@@ -93,6 +98,13 @@ namespace SixLabors.Primitives
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Point(PointF point) => Point.Truncate(point);
+ ///
+ /// Negates the given point by multiplying all values by -1.
+ ///
+ /// The source point.
+ /// The negated point.
+ public static PointF operator -(PointF value) => new PointF(-value.X, -value.Y);
+
///
/// Translates a by a given .
///
@@ -104,6 +116,26 @@ namespace SixLabors.Primitives
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF operator +(PointF point, SizeF size) => Add(point, size);
+ ///
+ /// Translates a by the negative of a given .
+ ///
+ /// The point on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static PointF operator -(PointF point, PointF size) => Subtract(point, size);
+
+ ///
+ /// Translates a by a given .
+ ///
+ /// The point on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ ///
+ /// The
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static PointF operator +(PointF point, PointF size) => Add(point, size);
+
///
/// Translates a by the negative of a given .
///
@@ -144,7 +176,7 @@ namespace SixLabors.Primitives
public static bool operator !=(PointF left, PointF right) => !left.Equals(right);
///
- /// Translates a by the negative of a given .
+ /// Translates a by the given .
///
/// The point on the left hand of the operand.
/// The size on the right hand of the operand.
@@ -152,6 +184,15 @@ namespace SixLabors.Primitives
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Add(PointF point, SizeF size) => new PointF(point.X + size.Width, point.Y + size.Height);
+ ///
+ /// Translates a by the given .
+ ///
+ /// The point on the left hand of the operand.
+ /// The point on the right hand of the operand.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static PointF Add(PointF point, PointF pointb) => new PointF(point.X + pointb.X, point.Y + pointb.Y);
+
///
/// Translates a by the negative of a given .
///
@@ -161,6 +202,15 @@ namespace SixLabors.Primitives
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Subtract(PointF point, SizeF size) => new PointF(point.X - size.Width, point.Y - size.Height);
+ ///
+ /// Translates a by the negative of a given .
+ ///
+ /// The point on the left hand of the operand.
+ /// The point on the right hand of the operand.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static PointF Subtract(PointF point, PointF pointb) => new PointF(point.X - pointb.X, point.Y - pointb.Y);
+
///
/// Rotates a point around the given rotation matrix.
///
@@ -228,6 +278,17 @@ namespace SixLabors.Primitives
///
/// A 32-bit signed integer that is the hash code for this instance.
///
- private int GetHashCode(PointF point) => point.X.GetHashCode() ^ point.Y.GetHashCode();
+ private int GetHashCode(PointF point) => HashHelpers.Combine(point.X.GetHashCode(), point.Y.GetHashCode());
+
+ ///
+ /// Transforms a point by the given matrix.
+ ///
+ /// The source point
+ /// The transformation matrix.
+ ///
+ public static PointF Transform(PointF position, Matrix matrix)
+ {
+ return Vector2.Transform(position, matrix);
+ }
}
}
\ No newline at end of file
diff --git a/src/SixLabors.Primitives/Rectangle.cs b/src/SixLabors.Primitives/Rectangle.cs
index 9c9a0599e..173ee5d1b 100644
--- a/src/SixLabors.Primitives/Rectangle.cs
+++ b/src/SixLabors.Primitives/Rectangle.cs
@@ -454,14 +454,24 @@ namespace SixLabors.Primitives
private int GetHashCode(Rectangle rectangle)
{
- unchecked
- {
- int hashCode = rectangle.X;
- hashCode = (hashCode * 397) ^ rectangle.Y;
- hashCode = (hashCode * 397) ^ rectangle.Width;
- hashCode = (hashCode * 397) ^ rectangle.Height;
- return hashCode;
- }
+ int hashCode = rectangle.X.GetHashCode();
+ hashCode = HashHelpers.Combine(hashCode, rectangle.Y.GetHashCode());
+ hashCode = HashHelpers.Combine(hashCode, rectangle.Width.GetHashCode());
+ hashCode = HashHelpers.Combine(hashCode, rectangle.Height.GetHashCode());
+ return hashCode;
+ }
+
+ ///
+ /// Transforms a rectangle by the given matrix.
+ ///
+ /// The source rectangle
+ /// The transformation matrix.
+ ///
+ public static RectangleF Transform(Rectangle rectangle, Matrix matrix)
+ {
+ PointF bottomRight = Point.Transform(new Point(rectangle.Right, rectangle.Bottom), matrix);
+ PointF topLeft = Point.Transform(rectangle.Location, matrix);
+ return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
}
}
}
\ No newline at end of file
diff --git a/src/SixLabors.Primitives/RectangleF.cs b/src/SixLabors.Primitives/RectangleF.cs
index c7d8b0ebb..d74fb796f 100644
--- a/src/SixLabors.Primitives/RectangleF.cs
+++ b/src/SixLabors.Primitives/RectangleF.cs
@@ -387,14 +387,24 @@ namespace SixLabors.Primitives
private int GetHashCode(RectangleF rectangle)
{
- unchecked
- {
- int hashCode = rectangle.X.GetHashCode();
- hashCode = (hashCode * 397) ^ rectangle.Y.GetHashCode();
- hashCode = (hashCode * 397) ^ rectangle.Width.GetHashCode();
- hashCode = (hashCode * 397) ^ rectangle.Height.GetHashCode();
- return hashCode;
- }
+ int hashCode = rectangle.X.GetHashCode();
+ hashCode = HashHelpers.Combine(hashCode, rectangle.Y.GetHashCode());
+ hashCode = HashHelpers.Combine(hashCode, rectangle.Width.GetHashCode());
+ hashCode = HashHelpers.Combine(hashCode, rectangle.Height.GetHashCode());
+ return hashCode;
+ }
+
+ ///
+ /// Transforms a rectangle by the given matrix.
+ ///
+ /// The source rectangle
+ /// The transformation matrix.
+ ///
+ public static RectangleF Transform(RectangleF rectangle, Matrix matrix)
+ {
+ PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
+ PointF topLeft = PointF.Transform(rectangle.Location, matrix);
+ return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
}
}
}
\ No newline at end of file
diff --git a/src/SixLabors.Primitives/Size.cs b/src/SixLabors.Primitives/Size.cs
index 1605232b1..fb306f128 100644
--- a/src/SixLabors.Primitives/Size.cs
+++ b/src/SixLabors.Primitives/Size.cs
@@ -7,6 +7,7 @@ namespace SixLabors.Primitives
{
using System;
using System.ComponentModel;
+ using System.Numerics;
using System.Runtime.CompilerServices;
///
@@ -22,6 +23,11 @@ namespace SixLabors.Primitives
/// Represents a that has Width and Height values set to zero.
///
public static readonly Size Empty = default(Size);
+ ///
+ /// Represents a that has Width and Height values set to zero.
+ ///
+ public static readonly Size Zero = new Size(0, 0);
+
///
/// Initializes a new instance of the struct.
@@ -220,6 +226,19 @@ namespace SixLabors.Primitives
///
/// A 32-bit signed integer that is the hash code for this instance.
///
- private int GetHashCode(Size size) => size.Width ^ size.Height;
+ private int GetHashCode(Size size) => HashHelpers.Combine(size.Width.GetHashCode(), size.Height.GetHashCode());
+
+ ///
+ /// Transforms a size by the given matrix.
+ ///
+ /// The source size
+ /// The transformation matrix.
+ ///
+ public static SizeF Transform(Size size, Matrix matrix)
+ {
+ var v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix);
+
+ return new SizeF(v.X, v.Y);
+ }
}
}
\ No newline at end of file
diff --git a/src/SixLabors.Primitives/SizeF.cs b/src/SixLabors.Primitives/SizeF.cs
index a11d2e443..b822740cd 100644
--- a/src/SixLabors.Primitives/SizeF.cs
+++ b/src/SixLabors.Primitives/SizeF.cs
@@ -24,6 +24,11 @@ namespace SixLabors.Primitives
///
public static readonly SizeF Empty = default(SizeF);
+ ///
+ /// Represents a that has Width and Height values set to zero.
+ ///
+ public static readonly SizeF Zero = new SizeF(0, 0);
+
///
/// Initializes a new instance of the struct.
///
@@ -175,7 +180,7 @@ namespace SixLabors.Primitives
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(SizeF other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height);
- private int GetHashCode(SizeF size) => size.Width.GetHashCode() ^ size.Height.GetHashCode();
+ private int GetHashCode(SizeF size) => HashHelpers.Combine(size.Width.GetHashCode(), size.Height.GetHashCode());
///
/// Creates a with the coordinates of the specified .
@@ -186,5 +191,18 @@ namespace SixLabors.Primitives
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Vector2(SizeF point) => new Vector2(point.Width, point.Height);
+
+ ///
+ /// Transforms a size by the given matrix.
+ ///
+ /// The source size
+ /// The transformation matrix.
+ ///
+ public static SizeF Transform(SizeF size, Matrix matrix)
+ {
+ var v = Vector2.Transform(new Vector2(size.Width, size.Height), matrix);
+
+ return new SizeF(v.X, v.Y);
+ }
}
}
\ No newline at end of file
diff --git a/tests/CodeCoverage/.gitignore b/tests/CodeCoverage/.gitignore
new file mode 100644
index 000000000..861c19344
--- /dev/null
+++ b/tests/CodeCoverage/.gitignore
@@ -0,0 +1 @@
+/OpenCover.4.6.519
diff --git a/tests/CodeCoverage/CodeCoverage.cmd b/tests/CodeCoverage/CodeCoverage.cmd
index 498e55b36..2547eace1 100644
--- a/tests/CodeCoverage/CodeCoverage.cmd
+++ b/tests/CodeCoverage/CodeCoverage.cmd
@@ -12,7 +12,7 @@ dotnet build SixLabors.Primitives.sln --no-incremental -c debug /p:codecov=true
rem The -threshold options prevents this taking ages...
rem tests\CodeCoverage\OpenCover.4.6.519\tools\OpenCover.Console.exe -target:"dotnet.exe" -targetargs:"test tests\SixLabors.Shapes.Tests\SixLabors.Shapes.Tests.csproj --no-build -c Release /p:codecov=true" -threshold:10 -register:user -filter:"+[SixLabors.Shapes*]*" -excludebyattribute:*.ExcludeFromCodeCoverage* -hideskipped:All -returntargetcode -output:.\SixLabors.Shapes.Coverage.xml
-tests\CodeCoverage\OpenCover.4.6.519\tools\OpenCover.Console.exe -target:"dotnet.exe" -targetargs:"test tests\SixLabors.Primitives.Tests\SixLabors.Primitives.Tests.csproj --no-build -c debug" -searchdirs:"tests\SixLabors.Shapes.Tests\bin\Release\netcoreapp1.1" -register:user -output:.\SixLabors.Shapes.Coverage.xml -hideskipped:All -returntargetcode -oldStyle -filter:"+[SixLabors.Primitives*]*"
+tests\CodeCoverage\OpenCover.4.6.519\tools\OpenCover.Console.exe -target:"dotnet.exe" -targetargs:"test tests\SixLabors.Primitives.Tests\SixLabors.Primitives.Tests.csproj --no-build -c debug" -searchdirs:"tests\SixLabors.Primitives.Tests\bin\Release\netcoreapp1.1" -register:user -output:.\SixLabors.Primitives.Coverage.xml -hideskipped:All -returntargetcode -oldStyle -filter:"+[SixLabors.Primitives*]*"
if %errorlevel% neq 0 exit /b %errorlevel%
diff --git a/tests/SixLabors.Primitives.Tests/MatrixTests.cs b/tests/SixLabors.Primitives.Tests/MatrixTests.cs
new file mode 100644
index 000000000..c228f7493
--- /dev/null
+++ b/tests/SixLabors.Primitives.Tests/MatrixTests.cs
@@ -0,0 +1,1015 @@
+//
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace SixLabors.Primitives.Tests
+{
+ using System.Globalization;
+ using System.Numerics;
+ using Xunit;
+
+ ///
+ /// Tests the struct.
+ ///
+ public class MatrixTests
+ {
+
+ [Fact]
+ public void ImplicitCastMatrixToMatrix3x2()
+ {
+ Matrix matrix = new Matrix(1, 2, 3, 4, 5, 6);
+
+ Matrix3x2 convertedMatrix = matrix;
+ Assert.Equal(1, convertedMatrix.M11);
+ Assert.Equal(2, convertedMatrix.M12);
+ Assert.Equal(3, convertedMatrix.M21);
+ Assert.Equal(4, convertedMatrix.M22);
+ Assert.Equal(5, convertedMatrix.M31);
+ Assert.Equal(6, convertedMatrix.M32);
+ }
+
+ [Fact]
+ public void ImplicitCastMatrix3x2ToMatrix()
+ {
+ Matrix3x2 matrix = new Matrix3x2(1, 2, 3, 4, 5, 6);
+
+ Matrix convertedMatrix = matrix;
+ Assert.Equal(1, convertedMatrix.M11);
+ Assert.Equal(2, convertedMatrix.M12);
+ Assert.Equal(3, convertedMatrix.M21);
+ Assert.Equal(4, convertedMatrix.M22);
+ Assert.Equal(5, convertedMatrix.M31);
+ Assert.Equal(6, convertedMatrix.M32);
+ }
+
+ /// matrix test mostly tken directly from CoreFX
+ ///
+ static Matrix GenerateMatrixNumberFrom1To6()
+ {
+ Matrix a = new Matrix();
+ a.M11 = 1.0f;
+ a.M12 = 2.0f;
+ a.M21 = 3.0f;
+ a.M22 = 4.0f;
+ a.M31 = 5.0f;
+ a.M32 = 6.0f;
+ return a;
+ }
+
+ static Matrix GenerateTestMatrix()
+ {
+ Matrix m = Matrix.CreateRotation(MathF.ToRadians(30.0f));
+ m.Translation = new Vector2(111.0f, 222.0f);
+ return m;
+ }
+
+ // A test for Identity
+ [Fact]
+ public void MatrixIdentityTest()
+ {
+ Matrix val = new Matrix();
+ val.M11 = val.M22 = 1.0f;
+
+ Assert.True(ApproximateFloatComparer.Equal(val, Matrix.Identity), "Matrix.Indentity was not set correctly.");
+ }
+
+ // A test for Determinant
+ [Fact]
+ public void MatrixDeterminantTest()
+ {
+ Matrix target = Matrix.CreateRotation(MathF.ToRadians(30.0f));
+
+ float val = 1.0f;
+ float det = target.GetDeterminant();
+
+ Assert.True(ApproximateFloatComparer.Equal(val, det), "Matrix.Determinant was not set correctly.");
+ }
+
+ // A test for Determinant
+ // Determinant test |A| = 1 / |A'|
+ [Fact]
+ public void MatrixDeterminantTest1()
+ {
+ Matrix a = new Matrix();
+ a.M11 = 5.0f;
+ a.M12 = 2.0f;
+ a.M21 = 12.0f;
+ a.M22 = 6.8f;
+ a.M31 = 6.5f;
+ a.M32 = 1.0f;
+ Matrix i;
+ Assert.True(Matrix.Invert(a, out i));
+
+ float detA = a.GetDeterminant();
+ float detI = i.GetDeterminant();
+ float t = 1.0f / detI;
+
+ // only accurate to 3 precision
+ Assert.True(System.Math.Abs(detA - t) < 1e-3, "Matrix.Determinant was not set correctly.");
+
+ // sanity check against 4x4 version
+ Assert.Equal(new Matrix4x4(a).GetDeterminant(), detA);
+ Assert.Equal(new Matrix4x4(i).GetDeterminant(), detI);
+ }
+
+ // A test for Invert (Matrix)
+ [Fact]
+ public void MatrixInvertTest()
+ {
+ Matrix mtx = Matrix.CreateRotation(MathF.ToRadians(30.0f));
+
+ Matrix expected = new Matrix();
+ expected.M11 = 0.8660254f;
+ expected.M12 = -0.5f;
+
+ expected.M21 = 0.5f;
+ expected.M22 = 0.8660254f;
+
+ expected.M31 = 0;
+ expected.M32 = 0;
+
+ Matrix actual;
+
+ Assert.True(Matrix.Invert(mtx, out actual));
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual), "Matrix.Invert did not return the expected value.");
+
+ Matrix i = mtx * actual;
+ Assert.True(ApproximateFloatComparer.Equal(i, Matrix.Identity), "Matrix.Invert did not return the expected value.");
+ }
+
+ // A test for Invert (Matrix)
+ [Fact]
+ public void MatrixInvertIdentityTest()
+ {
+ Matrix mtx = Matrix.Identity;
+
+ Matrix actual;
+ Assert.True(Matrix.Invert(mtx, out actual));
+
+ Assert.True(ApproximateFloatComparer.Equal(actual, Matrix.Identity));
+ }
+
+ // A test for Invert (Matrix)
+ [Fact]
+ public void MatrixInvertTranslationTest()
+ {
+ Matrix mtx = Matrix.CreateTranslation(23, 42);
+
+ Matrix actual;
+ Assert.True(Matrix.Invert(mtx, out actual));
+
+ Matrix i = mtx * actual;
+ Assert.True(ApproximateFloatComparer.Equal(i, Matrix.Identity));
+ }
+
+ // A test for Invert (Matrix)
+ [Fact]
+ public void MatrixInvertRotationTest()
+ {
+ Matrix mtx = Matrix.CreateRotation(2);
+
+ Matrix actual;
+ Assert.True(Matrix.Invert(mtx, out actual));
+
+ Matrix i = mtx * actual;
+ Assert.True(ApproximateFloatComparer.Equal(i, Matrix.Identity));
+ }
+
+ // A test for Invert (Matrix)
+ [Fact]
+ public void MatrixInvertScaleTest()
+ {
+ Matrix mtx = Matrix.CreateScale(23, -42);
+
+ Matrix actual;
+ Assert.True(Matrix.Invert(mtx, out actual));
+
+ Matrix i = mtx * actual;
+ Assert.True(ApproximateFloatComparer.Equal(i, Matrix.Identity));
+ }
+
+ // A test for Invert (Matrix)
+ [Fact]
+ public void MatrixInvertAffineTest()
+ {
+ Matrix mtx = Matrix.CreateRotation(2) *
+ Matrix.CreateScale(23, -42) *
+ Matrix.CreateTranslation(17, 53);
+
+ Matrix actual;
+ Assert.True(Matrix.Invert(mtx, out actual));
+
+ Matrix i = mtx * actual;
+ Assert.True(ApproximateFloatComparer.Equal(i, Matrix.Identity));
+ }
+
+ // A test for CreateRotation (float)
+ [Fact]
+ public void MatrixCreateRotationTest()
+ {
+ float radians = MathF.ToRadians(50.0f);
+
+ Matrix expected = new Matrix();
+ expected.M11 = 0.642787635f;
+ expected.M12 = 0.766044438f;
+ expected.M21 = -0.766044438f;
+ expected.M22 = 0.642787635f;
+
+ Matrix actual;
+ actual = Matrix.CreateRotation(radians);
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual), "Matrix.CreateRotation did not return the expected value.");
+ }
+
+ // A test for CreateRotation (float, Vector2f)
+ [Fact]
+ public void MatrixCreateRotationCenterTest()
+ {
+ float radians = MathF.ToRadians(30.0f);
+ Vector2 center = new Vector2(23, 42);
+
+ Matrix rotateAroundZero = Matrix.CreateRotation(radians, Vector2.Zero);
+ Matrix rotateAroundZeroExpected = Matrix.CreateRotation(radians);
+ Assert.True(ApproximateFloatComparer.Equal(rotateAroundZero, rotateAroundZeroExpected));
+
+ Matrix rotateAroundCenter = Matrix.CreateRotation(radians, center);
+ Matrix rotateAroundCenterExpected = Matrix.CreateTranslation(-center) * Matrix.CreateRotation(radians) * Matrix.CreateTranslation(center);
+ Assert.True(ApproximateFloatComparer.Equal(rotateAroundCenter, rotateAroundCenterExpected));
+ }
+
+ // A test for CreateRotation (float)
+ [Fact]
+ public void MatrixCreateRotationRightAngleTest()
+ {
+ // 90 degree rotations must be exact!
+ Matrix actual = Matrix.CreateRotation(0);
+ Assert.Equal(new Matrix(1, 0, 0, 1, 0, 0), actual);
+
+ actual = Matrix.CreateRotation(MathF.PI / 2);
+ Assert.Equal(new Matrix(0, 1, -1, 0, 0, 0), actual);
+
+ actual = Matrix.CreateRotation(MathF.PI);
+ Assert.Equal(new Matrix(-1, 0, 0, -1, 0, 0), actual);
+
+ actual = Matrix.CreateRotation(MathF.PI * 3 / 2);
+ Assert.Equal(new Matrix(0, -1, 1, 0, 0, 0), actual);
+
+ actual = Matrix.CreateRotation(MathF.PI * 2);
+ Assert.Equal(new Matrix(1, 0, 0, 1, 0, 0), actual);
+
+ actual = Matrix.CreateRotation(MathF.PI * 5 / 2);
+ Assert.Equal(new Matrix(0, 1, -1, 0, 0, 0), actual);
+
+ actual = Matrix.CreateRotation(-MathF.PI / 2);
+ Assert.Equal(new Matrix(0, -1, 1, 0, 0, 0), actual);
+
+ // But merely close-to-90 rotations should not be excessively clamped.
+ float delta = MathF.ToRadians(0.01f);
+
+ actual = Matrix.CreateRotation(MathF.PI + delta);
+ Assert.False(ApproximateFloatComparer.Equal(new Matrix(-1, 0, 0, -1, 0, 0), actual));
+
+ actual = Matrix.CreateRotation(MathF.PI - delta);
+ Assert.False(ApproximateFloatComparer.Equal(new Matrix(-1, 0, 0, -1, 0, 0), actual));
+ }
+
+ // A test for CreateRotation (float, Vector2f)
+ [Fact]
+ public void MatrixCreateRotationRightAngleCenterTest()
+ {
+ Vector2 center = new Vector2(3, 7);
+
+ // 90 degree rotations must be exact!
+ Matrix actual = Matrix.CreateRotation(0, center);
+ Assert.Equal(new Matrix(1, 0, 0, 1, 0, 0), actual);
+
+ actual = Matrix.CreateRotation(MathF.PI / 2, center);
+ Assert.Equal(new Matrix(0, 1, -1, 0, 10, 4), actual);
+
+ actual = Matrix.CreateRotation(MathF.PI, center);
+ Assert.Equal(new Matrix(-1, 0, 0, -1, 6, 14), actual);
+
+ actual = Matrix.CreateRotation(MathF.PI * 3 / 2, center);
+ Assert.Equal(new Matrix(0, -1, 1, 0, -4, 10), actual);
+
+ actual = Matrix.CreateRotation(MathF.PI * 2, center);
+ Assert.Equal(new Matrix(1, 0, 0, 1, 0, 0), actual);
+
+ actual = Matrix.CreateRotation(MathF.PI * 5 / 2, center);
+ Assert.Equal(new Matrix(0, 1, -1, 0, 10, 4), actual);
+
+ actual = Matrix.CreateRotation(-MathF.PI / 2, center);
+ Assert.Equal(new Matrix(0, -1, 1, 0, -4, 10), actual);
+
+ // But merely close-to-90 rotations should not be excessively clamped.
+ float delta = MathF.ToRadians(0.01f);
+
+ actual = Matrix.CreateRotation(MathF.PI + delta, center);
+ Assert.False(ApproximateFloatComparer.Equal(new Matrix(-1, 0, 0, -1, 6, 14), actual));
+
+ actual = Matrix.CreateRotation(MathF.PI - delta, center);
+ Assert.False(ApproximateFloatComparer.Equal(new Matrix(-1, 0, 0, -1, 6, 14), actual));
+ }
+
+ // A test for Invert (Matrix)
+ // Non invertible matrix - determinant is zero - singular matrix
+ [Fact]
+ public void MatrixInvertTest1()
+ {
+ Matrix a = new Matrix();
+ a.M11 = 0.0f;
+ a.M12 = 2.0f;
+ a.M21 = 0.0f;
+ a.M22 = 4.0f;
+ a.M31 = 5.0f;
+ a.M32 = 6.0f;
+
+ float detA = a.GetDeterminant();
+ Assert.True(ApproximateFloatComparer.Equal(detA, 0.0f), "Matrix.Invert did not return the expected value.");
+
+ Matrix actual;
+ Assert.False(Matrix.Invert(a, out actual));
+
+ // all the elements in Actual is NaN
+ Assert.True(
+ float.IsNaN(actual.M11) && float.IsNaN(actual.M12) &&
+ float.IsNaN(actual.M21) && float.IsNaN(actual.M22) &&
+ float.IsNaN(actual.M31) && float.IsNaN(actual.M32)
+ , "Matrix.Invert did not return the expected value.");
+ }
+
+ // A test for Lerp (Matrix, Matrix, float)
+ [Fact]
+ public void MatrixLerpTest()
+ {
+ Matrix a = new Matrix();
+ a.M11 = 11.0f;
+ a.M12 = 12.0f;
+ a.M21 = 21.0f;
+ a.M22 = 22.0f;
+ a.M31 = 31.0f;
+ a.M32 = 32.0f;
+
+ Matrix b = GenerateMatrixNumberFrom1To6();
+
+ float t = 0.5f;
+
+ Matrix expected = new Matrix();
+ expected.M11 = a.M11 + (b.M11 - a.M11) * t;
+ expected.M12 = a.M12 + (b.M12 - a.M12) * t;
+
+ expected.M21 = a.M21 + (b.M21 - a.M21) * t;
+ expected.M22 = a.M22 + (b.M22 - a.M22) * t;
+
+ expected.M31 = a.M31 + (b.M31 - a.M31) * t;
+ expected.M32 = a.M32 + (b.M32 - a.M32) * t;
+
+ Matrix actual;
+ actual = Matrix.Lerp(a, b, t);
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual), "Matrix.Lerp did not return the expected value.");
+ }
+
+ // A test for operator - (Matrix)
+ [Fact]
+ public void MatrixUnaryNegationTest()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+
+ Matrix expected = new Matrix();
+ expected.M11 = -1.0f;
+ expected.M12 = -2.0f;
+ expected.M21 = -3.0f;
+ expected.M22 = -4.0f;
+ expected.M31 = -5.0f;
+ expected.M32 = -6.0f;
+
+ Matrix actual = -a;
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual), "Matrix.operator - did not return the expected value.");
+ }
+
+ // A test for operator - (Matrix, Matrix)
+ [Fact]
+ public void MatrixSubtractionTest()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix b = GenerateMatrixNumberFrom1To6();
+ Matrix expected = new Matrix();
+
+ Matrix actual = a - b;
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual), "Matrix.operator - did not return the expected value.");
+ }
+
+ // A test for operator * (Matrix, Matrix)
+ [Fact]
+ public void MatrixMultiplyTest1()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix b = GenerateMatrixNumberFrom1To6();
+
+ Matrix expected = new Matrix();
+ expected.M11 = a.M11 * b.M11 + a.M12 * b.M21;
+ expected.M12 = a.M11 * b.M12 + a.M12 * b.M22;
+
+ expected.M21 = a.M21 * b.M11 + a.M22 * b.M21;
+ expected.M22 = a.M21 * b.M12 + a.M22 * b.M22;
+
+ expected.M31 = a.M31 * b.M11 + a.M32 * b.M21 + b.M31;
+ expected.M32 = a.M31 * b.M12 + a.M32 * b.M22 + b.M32;
+
+ Matrix actual = a * b;
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual), "Matrix.operator * did not return the expected value.");
+
+ // Sanity check by comparison with 4x4 multiply.
+ a = Matrix.CreateRotation(MathF.ToRadians(30)) * Matrix.CreateTranslation(23, 42);
+ b = Matrix.CreateScale(3, 7) * Matrix.CreateTranslation(666, -1);
+
+ actual = a * b;
+
+ Matrix4x4 a44 = new Matrix4x4(a);
+ Matrix4x4 b44 = new Matrix4x4(b);
+ Matrix4x4 expected44 = a44 * b44;
+ Matrix4x4 actual44 = new Matrix4x4(actual);
+
+ Assert.True(ApproximateFloatComparer.Equal(expected44, actual44), "Matrix.operator * did not return the expected value.");
+ }
+
+ // A test for operator * (Matrix, Matrix)
+ // Multiply with identity matrix
+ [Fact]
+ public void MatrixMultiplyTest4()
+ {
+ Matrix a = new Matrix();
+ a.M11 = 1.0f;
+ a.M12 = 2.0f;
+ a.M21 = 5.0f;
+ a.M22 = -6.0f;
+ a.M31 = 9.0f;
+ a.M32 = 10.0f;
+
+ Matrix b = new Matrix();
+ b = Matrix.Identity;
+
+ Matrix expected = a;
+ Matrix actual = a * b;
+
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual), "Matrix.operator * did not return the expected value.");
+ }
+
+ // A test for operator + (Matrix, Matrix)
+ [Fact]
+ public void MatrixAdditionTest()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix b = GenerateMatrixNumberFrom1To6();
+
+ Matrix expected = new Matrix();
+ expected.M11 = a.M11 + b.M11;
+ expected.M12 = a.M12 + b.M12;
+ expected.M21 = a.M21 + b.M21;
+ expected.M22 = a.M22 + b.M22;
+ expected.M31 = a.M31 + b.M31;
+ expected.M32 = a.M32 + b.M32;
+
+ Matrix actual;
+
+ actual = a + b;
+
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual), "Matrix.operator + did not return the expected value.");
+ }
+
+ // A test for ToString ()
+ [Fact]
+ public void MatrixToStringTest()
+ {
+ Matrix a = new Matrix();
+ a.M11 = 11.0f;
+ a.M12 = -12.0f;
+ a.M21 = 21.0f;
+ a.M22 = 22.0f;
+ a.M31 = 31.0f;
+ a.M32 = 32.0f;
+
+ string expected = "{ {M11:11 M12:-12} " +
+ "{M21:21 M22:22} " +
+ "{M31:31 M32:32} }";
+ string actual;
+
+ actual = a.ToString();
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for Add (Matrix, Matrix)
+ [Fact]
+ public void MatrixAddTest()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix b = GenerateMatrixNumberFrom1To6();
+
+ Matrix expected = new Matrix();
+ expected.M11 = a.M11 + b.M11;
+ expected.M12 = a.M12 + b.M12;
+ expected.M21 = a.M21 + b.M21;
+ expected.M22 = a.M22 + b.M22;
+ expected.M31 = a.M31 + b.M31;
+ expected.M32 = a.M32 + b.M32;
+
+ Matrix actual;
+
+ actual = Matrix.Add(a, b);
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for Equals (object)
+ [Fact]
+ public void MatrixEqualsTest()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix b = GenerateMatrixNumberFrom1To6();
+
+ // case 1: compare between same values
+ object obj = b;
+
+ bool expected = true;
+ bool actual = a.Equals(obj);
+ Assert.Equal(expected, actual);
+
+ // case 2: compare between different values
+ b.M11 = 11.0f;
+ obj = b;
+ expected = false;
+ actual = a.Equals(obj);
+ Assert.Equal(expected, actual);
+
+ // case 3: compare between different types.
+ obj = new Vector4();
+ expected = false;
+ actual = a.Equals(obj);
+ Assert.Equal(expected, actual);
+
+ // case 3: compare against null.
+ obj = null;
+ expected = false;
+ actual = a.Equals(obj);
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for GetHashCode ()
+ [Fact]
+ public void MatrixGetHashCodeTest()
+ {
+ Matrix target = GenerateMatrixNumberFrom1To6();
+ int expected = unchecked(target.M11.GetHashCode() + target.M12.GetHashCode() +
+ target.M21.GetHashCode() + target.M22.GetHashCode() +
+ target.M31.GetHashCode() + target.M32.GetHashCode());
+ int actual;
+
+ actual = target.GetHashCode();
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for Multiply (Matrix, Matrix)
+ [Fact]
+ public void MatrixMultiplyTest3()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix b = GenerateMatrixNumberFrom1To6();
+
+ Matrix expected = new Matrix();
+ expected.M11 = a.M11 * b.M11 + a.M12 * b.M21;
+ expected.M12 = a.M11 * b.M12 + a.M12 * b.M22;
+
+ expected.M21 = a.M21 * b.M11 + a.M22 * b.M21;
+ expected.M22 = a.M21 * b.M12 + a.M22 * b.M22;
+
+ expected.M31 = a.M31 * b.M11 + a.M32 * b.M21 + b.M31;
+ expected.M32 = a.M31 * b.M12 + a.M32 * b.M22 + b.M32;
+ Matrix actual;
+ actual = Matrix.Multiply(a, b);
+
+ Assert.Equal(expected, actual);
+
+ // Sanity check by comparison with 4x4 multiply.
+ a = Matrix.CreateRotation(MathF.ToRadians(30)) * Matrix.CreateTranslation(23, 42);
+ b = Matrix.CreateScale(3, 7) * Matrix.CreateTranslation(666, -1);
+
+ actual = Matrix.Multiply(a, b);
+
+ Matrix4x4 a44 = new Matrix4x4(a);
+ Matrix4x4 b44 = new Matrix4x4(b);
+ Matrix4x4 expected44 = Matrix4x4.Multiply(a44, b44);
+ Matrix4x4 actual44 = new Matrix4x4(actual);
+
+ Assert.True(ApproximateFloatComparer.Equal(expected44, actual44), "Matrix.Multiply did not return the expected value.");
+ }
+
+ // A test for Multiply (Matrix, float)
+ [Fact]
+ public void MatrixMultiplyTest5()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix expected = new Matrix(3, 6, 9, 12, 15, 18);
+ Matrix actual = Matrix.Multiply(a, 3);
+
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for Multiply (Matrix, float)
+ [Fact]
+ public void MatrixMultiplyTest6()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix expected = new Matrix(3, 6, 9, 12, 15, 18);
+ Matrix actual = a * 3;
+
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for Negate (Matrix)
+ [Fact]
+ public void MatrixNegateTest()
+ {
+ Matrix m = GenerateMatrixNumberFrom1To6();
+
+ Matrix expected = new Matrix();
+ expected.M11 = -1.0f;
+ expected.M12 = -2.0f;
+ expected.M21 = -3.0f;
+ expected.M22 = -4.0f;
+ expected.M31 = -5.0f;
+ expected.M32 = -6.0f;
+ Matrix actual;
+
+ actual = Matrix.Negate(m);
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for operator != (Matrix, Matrix)
+ [Fact]
+ public void MatrixInequalityTest()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix b = GenerateMatrixNumberFrom1To6();
+
+ // case 1: compare between same values
+ bool expected = false;
+ bool actual = a != b;
+ Assert.Equal(expected, actual);
+
+ // case 2: compare between different values
+ b.M11 = 11.0f;
+ expected = true;
+ actual = a != b;
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for operator == (Matrix, Matrix)
+ [Fact]
+ public void MatrixEqualityTest()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix b = GenerateMatrixNumberFrom1To6();
+
+ // case 1: compare between same values
+ bool expected = true;
+ bool actual = a == b;
+ Assert.Equal(expected, actual);
+
+ // case 2: compare between different values
+ b.M11 = 11.0f;
+ expected = false;
+ actual = a == b;
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for Subtract (Matrix, Matrix)
+ [Fact]
+ public void MatrixSubtractTest()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix b = GenerateMatrixNumberFrom1To6();
+ Matrix expected = new Matrix();
+ Matrix actual;
+
+ actual = Matrix.Subtract(a, b);
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for CreateScale (Vector2f)
+ [Fact]
+ public void MatrixCreateScaleTest1()
+ {
+ SizeF scales = new SizeF(2.0f, 3.0f);
+ Matrix expected = new Matrix(
+ 2.0f, 0.0f,
+ 0.0f, 3.0f,
+ 0.0f, 0.0f);
+ Matrix actual = Matrix.CreateScale(scales);
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for CreateScale (Vector2f, Vector2f)
+ [Fact]
+ public void MatrixCreateScaleCenterTest1()
+ {
+ SizeF scale = new SizeF(3, 4);
+ PointF center = new PointF(23, 42);
+
+ Matrix scaleAroundZero = Matrix.CreateScale(scale, PointF.Zero);
+ Matrix scaleAroundZeroExpected = Matrix.CreateScale(scale);
+ Assert.True(ApproximateFloatComparer.Equal(scaleAroundZero, scaleAroundZeroExpected));
+
+ Matrix scaleAroundCenter = Matrix.CreateScale(scale, center);
+ Matrix scaleAroundCenterExpected = Matrix.CreateTranslation(-center) * Matrix.CreateScale(scale) * Matrix.CreateTranslation(center);
+ Assert.True(ApproximateFloatComparer.Equal(scaleAroundCenter, scaleAroundCenterExpected));
+ }
+
+ // A test for CreateScale (float)
+ [Fact]
+ public void MatrixCreateScaleTest2()
+ {
+ float scale = 2.0f;
+ Matrix expected = new Matrix(
+ 2.0f, 0.0f,
+ 0.0f, 2.0f,
+ 0.0f, 0.0f);
+ Matrix actual = Matrix.CreateScale(scale);
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for CreateScale (float, Vector2f)
+ [Fact]
+ public void MatrixCreateScaleCenterTest2()
+ {
+ float scale = 5;
+ PointF center = new PointF(23, 42);
+
+ Matrix scaleAroundZero = Matrix.CreateScale(scale, PointF.Zero);
+ Matrix scaleAroundZeroExpected = Matrix.CreateScale(scale);
+ Assert.True(ApproximateFloatComparer.Equal(scaleAroundZero, scaleAroundZeroExpected));
+
+ Matrix scaleAroundCenter = Matrix.CreateScale(scale, center);
+ Matrix scaleAroundCenterExpected = Matrix.CreateTranslation(-center) * Matrix.CreateScale(scale) * Matrix.CreateTranslation(center);
+ Assert.True(ApproximateFloatComparer.Equal(scaleAroundCenter, scaleAroundCenterExpected));
+ }
+
+ // A test for CreateScale (float, float)
+ [Fact]
+ public void MatrixCreateScaleTest3()
+ {
+ float xScale = 2.0f;
+ float yScale = 3.0f;
+ Matrix expected = new Matrix(
+ 2.0f, 0.0f,
+ 0.0f, 3.0f,
+ 0.0f, 0.0f);
+ Matrix actual = Matrix.CreateScale(xScale, yScale);
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for CreateScale (float, float, Vector2f)
+ [Fact]
+ public void MatrixCreateScaleCenterTest3()
+ {
+ SizeF scale = new SizeF(3, 4);
+ PointF center = new PointF(23, 42);
+
+ Matrix scaleAroundZero = Matrix.CreateScale(scale.Width, scale.Height, Vector2.Zero);
+ Matrix scaleAroundZeroExpected = Matrix.CreateScale(scale.Width, scale.Height);
+ Assert.True(ApproximateFloatComparer.Equal(scaleAroundZero, scaleAroundZeroExpected));
+
+ Matrix scaleAroundCenter = Matrix.CreateScale(scale.Width, scale.Height, center);
+ Matrix scaleAroundCenterExpected = Matrix.CreateTranslation(-center) * Matrix.CreateScale(scale.Width, scale.Height) * Matrix.CreateTranslation(center);
+ Assert.True(ApproximateFloatComparer.Equal(scaleAroundCenter, scaleAroundCenterExpected));
+ }
+
+ // A test for CreateTranslation (Vector2f)
+ [Fact]
+ public void MatrixCreateTranslationTest1()
+ {
+ PointF position = new PointF(2.0f, 3.0f);
+ Matrix expected = new Matrix(
+ 1.0f, 0.0f,
+ 0.0f, 1.0f,
+ 2.0f, 3.0f);
+
+ Matrix actual = Matrix.CreateTranslation(position);
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for CreateTranslation (float, float)
+ [Fact]
+ public void MatrixCreateTranslationTest2()
+ {
+ float xPosition = 2.0f;
+ float yPosition = 3.0f;
+
+ Matrix expected = new Matrix(
+ 1.0f, 0.0f,
+ 0.0f, 1.0f,
+ 2.0f, 3.0f);
+
+ Matrix actual = Matrix.CreateTranslation(xPosition, yPosition);
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for Translation
+ [Fact]
+ public void MatrixTranslationTest()
+ {
+ Matrix a = GenerateTestMatrix();
+ Matrix b = a;
+
+ // Transformed vector that has same semantics of property must be same.
+ PointF val = new PointF(a.M31, a.M32);
+ Assert.Equal(val, a.Translation);
+
+ // Set value and get value must be same.
+ val = new PointF(1.0f, 2.0f);
+ a.Translation = val;
+ Assert.Equal(val, a.Translation);
+
+ // Make sure it only modifies expected value of matrix.
+ Assert.True(
+ a.M11 == b.M11 && a.M12 == b.M12 &&
+ a.M21 == b.M21 && a.M22 == b.M22 &&
+ a.M31 != b.M31 && a.M32 != b.M32,
+ "Matrix.Translation modified unexpected value of matrix.");
+ }
+
+ // A test for Equals (Matrix)
+ [Fact]
+ public void MatrixEqualsTest1()
+ {
+ Matrix a = GenerateMatrixNumberFrom1To6();
+ Matrix b = GenerateMatrixNumberFrom1To6();
+
+ // case 1: compare between same values
+ bool expected = true;
+ bool actual = a.Equals(b);
+ Assert.Equal(expected, actual);
+
+ // case 2: compare between different values
+ b.M11 = 11.0f;
+ expected = false;
+ actual = a.Equals(b);
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for CreateSkew (float, float)
+ [Fact]
+ public void MatrixCreateSkewIdentityTest()
+ {
+ Matrix expected = Matrix.Identity;
+ Matrix actual = Matrix.CreateSkew(0, 0);
+ Assert.Equal(expected, actual);
+ }
+
+ // A test for CreateSkew (float, float)
+ [Fact]
+ public void MatrixCreateSkewXTest()
+ {
+ Matrix expected = new Matrix(1, 0, -0.414213562373095f, 1, 0, 0);
+ Matrix actual = Matrix.CreateSkew(-MathF.PI / 8, 0);
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual));
+
+ expected = new Matrix(1, 0, 0.414213562373095f, 1, 0, 0);
+ actual = Matrix.CreateSkew(MathF.PI / 8, 0);
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual));
+
+ PointF result = PointF.Transform(new PointF(0, 0), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new PointF(0, 0), result));
+
+ result = PointF.Transform(new Vector2(0, 1), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new PointF(0.414213568f, 1), result));
+ result = PointF.Transform(new PointF(0, -1), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new PointF(-0.414213568f, -1), result));
+
+ result = PointF.Transform(new PointF(3, 10), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new PointF(7.14213568f, 10), result));
+ }
+
+ // A test for CreateSkew (float, float)
+ [Fact]
+ public void MatrixCreateSkewYTest()
+ {
+ Matrix expected = new Matrix(1, -0.414213562373095f, 0, 1, 0, 0);
+ Matrix actual = Matrix.CreateSkew(0, -MathF.PI / 8);
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual));
+
+ expected = new Matrix(1, 0.414213562373095f, 0, 1, 0, 0);
+ actual = Matrix.CreateSkew(0, MathF.PI / 8);
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual));
+
+ Vector2 result = Vector2.Transform(new Vector2(0, 0), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new Vector2(0, 0), result));
+
+ result = Vector2.Transform(new Vector2(1, 0), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new Vector2(1, 0.414213568f), result));
+
+ result = Vector2.Transform(new Vector2(-1, 0), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new Vector2(-1, -0.414213568f), result));
+
+ result = Vector2.Transform(new Vector2(10, 3), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new Vector2(10, 7.14213568f), result));
+ }
+
+ // A test for CreateSkew (float, float)
+ [Fact]
+ public void MatrixCreateSkewXYTest()
+ {
+ Matrix expected = new Matrix(1, -0.414213562373095f, 1, 1, 0, 0);
+ Matrix actual = Matrix.CreateSkew(MathF.PI / 4, -MathF.PI / 8);
+ Assert.True(ApproximateFloatComparer.Equal(expected, actual));
+
+ Vector2 result = Vector2.Transform(new Vector2(0, 0), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new Vector2(0, 0), result));
+
+ result = Vector2.Transform(new Vector2(1, 0), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new Vector2(1, -0.414213562373095f), result));
+
+ result = Vector2.Transform(new Vector2(0, 1), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new Vector2(1, 1), result));
+
+ result = Vector2.Transform(new Vector2(1, 1), actual);
+ Assert.True(ApproximateFloatComparer.Equal(new Vector2(2, 0.585786437626905f), result));
+ }
+
+ // A test for CreateSkew (float, float, Vector2f)
+ [Fact]
+ public void MatrixCreateSkewCenterTest()
+ {
+ float skewX = 1, skewY = 2;
+ Vector2 center = new Vector2(23, 42);
+
+ Matrix skewAroundZero = Matrix.CreateSkew(skewX, skewY, Vector2.Zero);
+ Matrix skewAroundZeroExpected = Matrix.CreateSkew(skewX, skewY);
+ Assert.True(ApproximateFloatComparer.Equal(skewAroundZero, skewAroundZeroExpected));
+
+ Matrix skewAroundCenter = Matrix.CreateSkew(skewX, skewY, center);
+ Matrix skewAroundCenterExpected = Matrix.CreateTranslation(-center) * Matrix.CreateSkew(skewX, skewY) * Matrix.CreateTranslation(center);
+ Assert.True(ApproximateFloatComparer.Equal(skewAroundCenter, skewAroundCenterExpected));
+ }
+
+ // A test for IsIdentity
+ [Fact]
+ public void MatrixIsIdentityTest()
+ {
+ Assert.True(Matrix.Identity.IsIdentity);
+ Assert.True(new Matrix(1, 0, 0, 1, 0, 0).IsIdentity);
+ Assert.False(new Matrix(0, 0, 0, 1, 0, 0).IsIdentity);
+ Assert.False(new Matrix(1, 1, 0, 1, 0, 0).IsIdentity);
+ Assert.False(new Matrix(1, 0, 1, 1, 0, 0).IsIdentity);
+ Assert.False(new Matrix(1, 0, 0, 0, 0, 0).IsIdentity);
+ Assert.False(new Matrix(1, 0, 0, 1, 1, 0).IsIdentity);
+ Assert.False(new Matrix(1, 0, 0, 1, 0, 1).IsIdentity);
+ }
+
+ // A test for Matrix comparison involving NaN values
+ [Fact]
+ public void MatrixEqualsNanTest()
+ {
+ Matrix a = new Matrix(float.NaN, 0, 0, 0, 0, 0);
+ Matrix b = new Matrix(0, float.NaN, 0, 0, 0, 0);
+ Matrix c = new Matrix(0, 0, float.NaN, 0, 0, 0);
+ Matrix d = new Matrix(0, 0, 0, float.NaN, 0, 0);
+ Matrix e = new Matrix(0, 0, 0, 0, float.NaN, 0);
+ Matrix f = new Matrix(0, 0, 0, 0, 0, float.NaN);
+
+ Assert.False(a == new Matrix());
+ Assert.False(b == new Matrix());
+ Assert.False(c == new Matrix());
+ Assert.False(d == new Matrix());
+ Assert.False(e == new Matrix());
+ Assert.False(f == new Matrix());
+
+ Assert.True(a != new Matrix());
+ Assert.True(b != new Matrix());
+ Assert.True(c != new Matrix());
+ Assert.True(d != new Matrix());
+ Assert.True(e != new Matrix());
+ Assert.True(f != new Matrix());
+
+ Assert.False(a.Equals(new Matrix()));
+ Assert.False(b.Equals(new Matrix()));
+ Assert.False(c.Equals(new Matrix()));
+ Assert.False(d.Equals(new Matrix()));
+ Assert.False(e.Equals(new Matrix()));
+ Assert.False(f.Equals(new Matrix()));
+
+ Assert.False(a.IsIdentity);
+ Assert.False(b.IsIdentity);
+ Assert.False(c.IsIdentity);
+ Assert.False(d.IsIdentity);
+ Assert.False(e.IsIdentity);
+ Assert.False(f.IsIdentity);
+
+ // Counterintuitive result - IEEE rules for NaN comparison are weird!
+ Assert.False(a.Equals(a));
+ Assert.False(b.Equals(b));
+ Assert.False(c.Equals(c));
+ Assert.False(d.Equals(d));
+ Assert.False(e.Equals(e));
+ Assert.False(f.Equals(f));
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/SixLabors.Primitives.Tests/PointFTests.cs b/tests/SixLabors.Primitives.Tests/PointFTests.cs
index b6dd64511..28429f057 100644
--- a/tests/SixLabors.Primitives.Tests/PointFTests.cs
+++ b/tests/SixLabors.Primitives.Tests/PointFTests.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace SixLabors.Primitives.Tests.Numerics
+namespace SixLabors.Primitives.Tests
{
using System;
using System.Globalization;
diff --git a/tests/SixLabors.Primitives.Tests/SixLabors.Primitives.Tests.csproj b/tests/SixLabors.Primitives.Tests/SixLabors.Primitives.Tests.csproj
index a300a0b06..2dd674fdc 100644
--- a/tests/SixLabors.Primitives.Tests/SixLabors.Primitives.Tests.csproj
+++ b/tests/SixLabors.Primitives.Tests/SixLabors.Primitives.Tests.csproj
@@ -3,7 +3,7 @@
0.0.0
netcoreapp1.1
- SixLabors.Shapes.Tests
+ SixLabors.Primitives.Tests
SixLabors.Shapes.Tests
true
false
@@ -17,7 +17,6 @@
-