diff --git a/src/ImageSharp/Numerics/Matrix3x2Extensions.cs b/src/ImageSharp/Numerics/Matrix3x2Extensions.cs
new file mode 100644
index 0000000000..f424624020
--- /dev/null
+++ b/src/ImageSharp/Numerics/Matrix3x2Extensions.cs
@@ -0,0 +1,72 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Extension methods for the struct
+ ///
+ public static class Matrix3x2Extensions
+ {
+ ///
+ /// Creates a rotation matrix for the given rotation in degrees and a center point.
+ ///
+ /// The angle in degrees
+ /// The center point
+ /// The rotation
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Matrix3x2 CreateRotation(float degree, Point centerPoint)
+ {
+ float radian = MathF.DegreeToRadian(degree);
+ return Matrix3x2.CreateRotation(radian, new Vector2(centerPoint.X, centerPoint.Y));
+ }
+
+ ///
+ /// Creates a rotation matrix for the given rotation in degrees and a center point.
+ ///
+ /// The angle in degrees
+ /// The center point
+ /// The rotation
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Matrix3x2 CreateRotation(float degree, PointF centerPoint)
+ {
+ float radian = MathF.DegreeToRadian(degree);
+ return Matrix3x2.CreateRotation(radian, centerPoint);
+ }
+
+ ///
+ /// Creates a skew matrix for the given angle in degrees and a center point.
+ ///
+ /// The x-angle in degrees
+ /// The y-angle in degrees
+ /// The center point
+ /// The rotation
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Matrix3x2 CreateSkew(float degreesX, float degreesY, Point centerPoint)
+ {
+ float radiansX = MathF.DegreeToRadian(degreesX);
+ float radiansY = MathF.DegreeToRadian(degreesY);
+ return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(centerPoint.X, centerPoint.Y));
+ }
+
+ ///
+ /// Creates a skew matrix for the given angle in degrees and a center point.
+ ///
+ /// The x-angle in degrees
+ /// The y-angle in degrees
+ /// The rotation
+ /// The center point
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Matrix3x2 CreateSkew(float degreesX, float degreesY, PointF centerPoint)
+ {
+ float radiansX = MathF.DegreeToRadian(degreesX);
+ float radiansY = MathF.DegreeToRadian(degreesY);
+ return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(centerPoint.X, centerPoint.Y));
+ }
+ }
+}
diff --git a/src/ImageSharp/Numerics/Point.cs b/src/ImageSharp/Numerics/Point.cs
index 8d523895fb..7c607885f5 100644
--- a/src/ImageSharp/Numerics/Point.cs
+++ b/src/ImageSharp/Numerics/Point.cs
@@ -25,6 +25,17 @@ namespace ImageSharp
///
public static readonly Point Empty = default(Point);
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The horizontal and vertical position of the point.
+ public Point(int value)
+ : this()
+ {
+ this.X = LowInt16(value);
+ this.Y = HighInt16(value);
+ }
+
///
/// Initializes a new instance of the struct.
///
@@ -38,15 +49,13 @@ namespace ImageSharp
}
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct from the given .
///
- ///
- /// The vector representing the width and height.
- ///
- public Point(Vector2 vector)
+ /// The size
+ public Point(Size size)
{
- this.X = (int)Math.Round(vector.X);
- this.Y = (int)Math.Round(vector.Y);
+ this.X = size.Width;
+ this.Y = size.Height;
}
///
@@ -66,42 +75,66 @@ namespace ImageSharp
public bool IsEmpty => this.Equals(Empty);
///
- /// Computes the sum of adding two points.
+ /// Creates a with the coordinates of the specified .
///
- /// The point on the left hand of the operand.
- /// The point on the right hand of the operand.
- ///
- /// The
- ///
+ /// The point
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator PointF(Point point)
+ {
+ return new PointF(point.X, point.Y);
+ }
+
+ ///
+ /// Creates a with the coordinates of the specified .
+ ///
+ /// The point
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Point operator +(Point left, Point right)
+ public static implicit operator Vector2(Point point)
{
- return new Point(left.X + right.X, left.Y + right.Y);
+ return new Vector2(point.X, point.Y);
}
///
- /// Computes the difference left by subtracting one point from another.
+ /// Creates a with the coordinates of the specified .
///
- /// The point on the left hand of the operand.
- /// The point on the right hand of the operand.
+ /// The point
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static explicit operator Size(Point point)
+ {
+ return new Size(point.X, point.Y);
+ }
+
+ ///
+ /// 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 Point operator -(Point left, Point right)
+ public static Point operator +(Point point, Size size)
+ {
+ return 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 Point operator -(Point point, Size size)
{
- return new Point(left.X - right.X, left.Y - right.Y);
+ return Subtract(point, size);
}
///
/// Compares two objects for equality.
///
- ///
- /// The on the left side of the operand.
- ///
- ///
- /// The on the right side of the operand.
- ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
///
/// True if the current left is equal to the parameter; otherwise, false.
///
@@ -114,12 +147,8 @@ namespace ImageSharp
///
/// Compares two objects for inequality.
///
- ///
- /// The on the left side of the operand.
- ///
- ///
- /// The on the right side of the operand.
- ///
+ /// The on the left side of the operand.
+ /// The on the right side of the operand.
///
/// True if the current left is unequal to the parameter; otherwise, false.
///
@@ -130,76 +159,84 @@ namespace ImageSharp
}
///
- /// Creates a rotation matrix for the given point and angle.
+ /// Translates a by the negative of a given .
///
- /// The origin point to rotate around
- /// Rotation in degrees
- /// The rotation
- public static Matrix3x2 CreateRotation(Point origin, float degrees)
+ /// The point on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Point Add(Point point, Size size)
{
- float radians = MathF.DegreeToRadian(degrees);
- return Matrix3x2.CreateRotation(radians, new Vector2(origin.X, origin.Y));
+ return new Point(point.X + size.Width, point.Y + size.Height);
}
///
- /// Rotates a point around a given a rotation matrix.
+ /// Translates a by the negative of a given .
///
- /// The point to rotate
- /// Rotation matrix used
- /// The rotated
- public static Point Rotate(Point point, Matrix3x2 rotation)
+ /// The point on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Point Subtract(Point point, Size size)
{
- return new Point(Vector2.Transform(new Vector2(point.X, point.Y), rotation));
+ return new Point(point.X - size.Width, point.Y - size.Height);
}
///
- /// Rotates a point around a given origin by the specified angle in degrees.
+ /// Converts a to a by performing a ceiling operation on all the coordinates.
///
- /// The point to rotate
- /// The center point to rotate around.
- /// The angle in degrees.
- /// The rotated
- public static Point Rotate(Point point, Point origin, float degrees)
+ /// The point
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Point Ceiling(PointF point)
{
- return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateRotation(origin, degrees)));
+ return new Point((int)MathF.Ceiling(point.X), (int)MathF.Ceiling(point.Y));
}
///
- /// Creates a skew matrix for the given point and angle.
+ /// Converts a to a by performing a round operation on all the coordinates.
///
- /// The origin point to rotate around
- /// The x-angle in degrees.
- /// The y-angle in degrees.
- /// The rotation
- public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY)
+ /// The point
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Point Round(PointF point)
{
- float radiansX = MathF.DegreeToRadian(degreesX);
- float radiansY = MathF.DegreeToRadian(degreesY);
- return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(origin.X, origin.Y));
+ return new Point((int)MathF.Round(point.X), (int)MathF.Round(point.Y));
}
///
- /// Skews a point using a given a skew matrix.
+ /// Converts a to a by performing a round operation on all the coordinates.
+ ///
+ /// The vector
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Point Round(Vector2 vector)
+ {
+ return new Point((int)MathF.Round(vector.X), (int)MathF.Round(vector.Y));
+ }
+
+ ///
+ /// Rotates a point around the given rotation matrix.
///
/// The point to rotate
- /// Rotation matrix used
+ /// Rotation matrix used
/// The rotated
- public static Point Skew(Point point, Matrix3x2 skew)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Point Rotate(Point point, Matrix3x2 rotation)
{
- return new Point(Vector2.Transform(new Vector2(point.X, point.Y), skew));
+ return Round(Vector2.Transform(new Vector2(point.X, point.Y), rotation));
}
///
- /// Skews a point around a given origin by the specified angles in degrees.
+ /// Skews a point using the given skew matrix.
///
- /// The point to skew.
- /// The center point to rotate around.
- /// The x-angle in degrees.
- /// The y-angle in degrees.
- /// The skewed
- public static Point Skew(Point point, Point origin, float degreesX, float degreesY)
+ /// The point to rotate
+ /// Rotation matrix used
+ /// The rotated
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Point Skew(Point point, Matrix3x2 skew)
{
- return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateSkew(origin, degreesX, degreesY)));
+ return Round(Vector2.Transform(new Vector2(point.X, point.Y), skew));
}
///
@@ -216,6 +253,7 @@ namespace ImageSharp
///
/// The amount to offset the x-coordinate.
/// The amount to offset the y-coordinate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(int dx, int dy)
{
this.X += dx;
@@ -225,10 +263,11 @@ namespace ImageSharp
///
/// Translates this by the specified amount.
///
- /// The used offset this .
- public void Offset(Point p)
+ /// The used offset this .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Offset(Point point)
{
- this.Offset(p.X, p.Y);
+ this.Offset(point.X, point.Y);
}
///
@@ -260,23 +299,22 @@ namespace ImageSharp
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Point other)
{
return this.X == other.X && this.Y == other.Y;
}
- ///
- /// Returns the hash code for this instance.
- ///
- ///
- /// The instance of to return the hash code for.
- ///
- ///
- /// A 32-bit signed integer that is the hash code for this instance.
- ///
+ private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff));
+
+ private static short LowInt16(int n) => unchecked((short)(n & 0xffff));
+
private int GetHashCode(Point point)
{
- return point.X ^ point.Y;
+ unchecked
+ {
+ return point.X ^ point.Y;
+ }
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Numerics/PointF.cs b/src/ImageSharp/Numerics/PointF.cs
new file mode 100644
index 0000000000..1b76badb68
--- /dev/null
+++ b/src/ImageSharp/Numerics/PointF.cs
@@ -0,0 +1,289 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Represents an ordered pair of single precision floating point x- and y-coordinates that defines a point in
+ /// a two-dimensional plane.
+ ///
+ ///
+ /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
+ /// as it avoids the need to create new values for modification operations.
+ ///
+ public struct PointF : IEquatable
+ {
+ ///
+ /// Represents a that has X and Y values set to zero.
+ ///
+ public static readonly PointF Empty = default(PointF);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The horizontal position of the point.
+ /// The vertical position of the point.
+ public PointF(float x, float y)
+ : this()
+ {
+ this.X = x;
+ this.Y = y;
+ }
+
+ ///
+ /// Initializes a new instance of the struct from the given .
+ ///
+ /// The size
+ public PointF(SizeF size)
+ {
+ this.X = size.Width;
+ this.Y = size.Height;
+ }
+
+ ///
+ /// Gets or sets the x-coordinate of this .
+ ///
+ public float X { get; set; }
+
+ ///
+ /// Gets or sets the y-coordinate of this .
+ ///
+ public float Y { get; set; }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ /// Creates a with the coordinates of the specified .
+ ///
+ /// The vector.
+ ///
+ /// The .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator PointF(Vector2 vector)
+ {
+ return new PointF(vector.X, vector.Y);
+ }
+
+ ///
+ /// Creates a with the coordinates of the specified .
+ ///
+ /// The point.
+ ///
+ /// The .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Vector2(PointF point)
+ {
+ return new Vector2(point.X, point.Y);
+ }
+
+ ///
+ /// Creates a with the coordinates of the specified by truncating each of the coordinates.
+ ///
+ /// The point.
+ ///
+ /// The .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static explicit operator Point(PointF point)
+ {
+ return new Point(unchecked((int)MathF.Round(point.X)), unchecked((int)MathF.Round(point.Y)));
+ }
+
+ ///
+ /// 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, SizeF size)
+ {
+ return 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, SizeF size)
+ {
+ return Subtract(point, size);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(PointF left, PointF right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for inequality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(PointF left, PointF right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ /// 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 Add(PointF point, SizeF size)
+ {
+ return 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 size on the right hand of the operand.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static PointF Subtract(PointF point, SizeF size)
+ {
+ return new PointF(point.X - size.Width, point.Y - size.Height);
+ }
+
+ ///
+ /// Rotates a point around the given rotation matrix.
+ ///
+ /// The point to rotate
+ /// Rotation matrix used
+ /// The rotated
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static PointF Rotate(PointF point, Matrix3x2 rotation)
+ {
+ return Vector2.Transform(new Vector2(point.X, point.Y), rotation);
+ }
+
+ ///
+ /// Skews a point using the given skew matrix.
+ ///
+ /// The point to rotate
+ /// Rotation matrix used
+ /// The rotated
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static PointF Skew(PointF point, Matrix3x2 skew)
+ {
+ return Vector2.Transform(new Vector2(point.X, point.Y), skew);
+ }
+
+ ///
+ /// Translates this by the specified amount.
+ ///
+ /// The amount to offset the x-coordinate.
+ /// The amount to offset the y-coordinate.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Offset(float dx, float dy)
+ {
+ this.X += dx;
+ this.Y += dy;
+ }
+
+ ///
+ /// Translates this by the specified amount.
+ ///
+ /// The used offset this .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Offset(PointF point)
+ {
+ this.Offset(point.X, point.Y);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.GetHashCode(this);
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "PointF [ Empty ]";
+ }
+
+ return $"PointF [ X={this.X}, Y={this.Y} ]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is PointF)
+ {
+ return this.Equals((PointF)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(PointF other)
+ {
+ return this.X.Equals(other.X) && this.Y.Equals(other.Y);
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// The instance of to return the hash code for.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ private int GetHashCode(PointF point)
+ {
+ unchecked
+ {
+ return point.X.GetHashCode() ^ point.Y.GetHashCode();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Numerics/Size.cs b/src/ImageSharp/Numerics/Size.cs
index bae645ac8e..c462473076 100644
--- a/src/ImageSharp/Numerics/Size.cs
+++ b/src/ImageSharp/Numerics/Size.cs
@@ -23,6 +23,17 @@ namespace ImageSharp
///
public static readonly Size Empty = default(Size);
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The width and height of the size
+ public Size(int value)
+ : this()
+ {
+ this.Width = value;
+ this.Height = value;
+ }
+
///
/// Initializes a new instance of the struct.
///
@@ -34,6 +45,27 @@ namespace ImageSharp
this.Height = height;
}
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The size
+ public Size(Size size)
+ : this()
+ {
+ this.Width = size.Width;
+ this.Height = size.Height;
+ }
+
+ ///
+ /// Initializes a new instance of the struct from the given .
+ ///
+ /// The point
+ public Size(Point point)
+ {
+ this.Width = point.X;
+ this.Height = point.Y;
+ }
+
///
/// Gets or sets the width of this .
///
@@ -50,6 +82,26 @@ namespace ImageSharp
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
+ ///
+ /// Creates a with the dimensions of the specified .
+ ///
+ /// The point
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator SizeF(Size size)
+ {
+ return new SizeF(size.Width, size.Height);
+ }
+
+ ///
+ /// Converts the given into a .
+ ///
+ /// The size
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static explicit operator Point(Size size)
+ {
+ return new Point(size.Width, size.Height);
+ }
+
///
/// Computes the sum of adding two sizes.
///
@@ -61,7 +113,7 @@ namespace ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size operator +(Size left, Size right)
{
- return new Size(left.Width + right.Width, left.Height + right.Height);
+ return Add(left, right);
}
///
@@ -75,7 +127,7 @@ namespace ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size operator -(Size left, Size right)
{
- return new Size(left.Width - right.Width, left.Height - right.Height);
+ return Subtract(left, right);
}
///
@@ -114,6 +166,52 @@ namespace ImageSharp
return !left.Equals(right);
}
+ ///
+ /// Performs vector addition of two objects.
+ ///
+ /// The size on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Size Add(Size left, Size right)
+ {
+ return new Size(left.Width + right.Width, left.Height + right.Height);
+ }
+
+ ///
+ /// Contracts a by another
+ ///
+ /// The size on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Size Subtract(Size left, Size right)
+ {
+ return new Size(left.Width - right.Width, left.Height - right.Height);
+ }
+
+ ///
+ /// Converts a to a by performing a ceiling operation on all the dimensions.
+ ///
+ /// The size
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Size Ceiling(SizeF size)
+ {
+ return new Size((int)MathF.Ceiling(size.Width), (int)MathF.Ceiling(size.Height));
+ }
+
+ ///
+ /// Converts a to a by performing a round operation on all the dimensions.
+ ///
+ /// The size
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Size Round(SizeF size)
+ {
+ return new Size((int)MathF.Round(size.Width), (int)MathF.Round(size.Height));
+ }
+
///
public override int GetHashCode()
{
@@ -132,7 +230,6 @@ namespace ImageSharp
}
///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Size)
@@ -144,6 +241,7 @@ namespace ImageSharp
}
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Size other)
{
return this.Width == other.Width && this.Height == other.Height;
@@ -160,7 +258,10 @@ namespace ImageSharp
///
private int GetHashCode(Size size)
{
- return size.Width ^ size.Height;
+ unchecked
+ {
+ return size.Width ^ size.Height;
+ }
}
}
}
diff --git a/src/ImageSharp/Numerics/SizeF.cs b/src/ImageSharp/Numerics/SizeF.cs
new file mode 100644
index 0000000000..f6b852cfa2
--- /dev/null
+++ b/src/ImageSharp/Numerics/SizeF.cs
@@ -0,0 +1,229 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.ComponentModel;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Stores an ordered pair of single precision floating points, which specify a height and width.
+ ///
+ ///
+ /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
+ /// as it avoids the need to create new values for modification operations.
+ ///
+ public struct SizeF : IEquatable
+ {
+ ///
+ /// Represents a that has Width and Height values set to zero.
+ ///
+ public static readonly SizeF Empty = default(SizeF);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The width of the size.
+ /// The height of the size.
+ public SizeF(float width, float height)
+ {
+ this.Width = width;
+ this.Height = height;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The size
+ public SizeF(SizeF size)
+ : this()
+ {
+ this.Width = size.Width;
+ this.Height = size.Height;
+ }
+
+ ///
+ /// Initializes a new instance of the struct from the given .
+ ///
+ /// The point
+ public SizeF(PointF point)
+ {
+ this.Width = point.X;
+ this.Height = point.Y;
+ }
+
+ ///
+ /// Gets or sets the width of this .
+ ///
+ public float Width { get; set; }
+
+ ///
+ /// Gets or sets the height of this .
+ ///
+ public float Height { get; set; }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.Equals(Empty);
+
+ ///
+ /// Creates a with the dimensions of the specified by truncating each of the dimensions.
+ ///
+ /// The size.
+ ///
+ /// The .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static explicit operator Size(SizeF size)
+ {
+ return new Size(unchecked((int)size.Width), unchecked((int)size.Height));
+ }
+
+ ///
+ /// Converts the given into a .
+ ///
+ /// The size
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static explicit operator PointF(SizeF size)
+ {
+ return new PointF(size.Width, size.Height);
+ }
+
+ ///
+ /// Computes the sum of adding two sizes.
+ ///
+ /// The size on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ ///
+ /// The
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SizeF operator +(SizeF left, SizeF right)
+ {
+ return Add(left, right);
+ }
+
+ ///
+ /// Computes the difference left by subtracting one size from another.
+ ///
+ /// The size on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ ///
+ /// The
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SizeF operator -(SizeF left, SizeF right)
+ {
+ return Subtract(left, right);
+ }
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ /// The size on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(SizeF left, SizeF right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for inequality.
+ ///
+ /// The size on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(SizeF left, SizeF right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ /// Performs vector addition of two objects.
+ ///
+ /// The size on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SizeF Add(SizeF left, SizeF right)
+ {
+ return new SizeF(left.Width + right.Width, left.Height + right.Height);
+ }
+
+ ///
+ /// Contracts a by another
+ ///
+ /// The size on the left hand of the operand.
+ /// The size on the right hand of the operand.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SizeF Subtract(SizeF left, SizeF right)
+ {
+ return new SizeF(left.Width - right.Width, left.Height - right.Height);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.GetHashCode(this);
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "SizeF [ Empty ]";
+ }
+
+ return $"SizeF [ Width={this.Width}, Height={this.Height} ]";
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is SizeF)
+ {
+ return this.Equals((SizeF)obj);
+ }
+
+ return false;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(SizeF other)
+ {
+ return this.Width.Equals(other.Width) && this.Height.Equals(other.Height);
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// The instance of to return the hash code for.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ private int GetHashCode(SizeF size)
+ {
+ unchecked
+ {
+ return size.Width.GetHashCode() ^ size.Height.GetHashCode();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
index 43dbb53eac..15d7ca641a 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
@@ -78,7 +78,7 @@ namespace ImageSharp.Processing.Processors
return;
}
- this.processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle);
+ this.processMatrix = Matrix3x2Extensions.CreateRotation(-this.Angle, new Point(0, 0));
if (this.Expand)
{
this.CreateNewCanvas(sourceRectangle, this.processMatrix);
diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
index 7807d0dfcc..264cf4a531 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
@@ -73,7 +73,7 @@ namespace ImageSharp.Processing.Processors
///
protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle)
{
- this.processMatrix = Point.CreateSkew(new Point(0, 0), -this.AngleX, -this.AngleY);
+ this.processMatrix = Matrix3x2Extensions.CreateSkew(-this.AngleX, -this.AngleY, new Point(0, 0));
if (this.Expand)
{
this.CreateNewCanvas(sourceRectangle, this.processMatrix);
diff --git a/tests/ImageSharp.Tests/Numerics/PointFTests.cs b/tests/ImageSharp.Tests/Numerics/PointFTests.cs
new file mode 100644
index 0000000000..33de60ca7f
--- /dev/null
+++ b/tests/ImageSharp.Tests/Numerics/PointFTests.cs
@@ -0,0 +1,170 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests.Numerics
+{
+ using System;
+ using System.Globalization;
+ using System.Reflection;
+ using Xunit;
+
+ public class PointFTests
+ {
+ [Fact]
+ public void DefaultConstructorTest()
+ {
+ Assert.Equal(PointF.Empty, new PointF());
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MinValue)]
+ [InlineData(float.MaxValue, float.MaxValue)]
+ [InlineData(float.MinValue, float.MaxValue)]
+ [InlineData(0.0, 0.0)]
+ public void NonDefaultConstructorTest(float x, float y)
+ {
+ var p1 = new PointF(x, y);
+
+ Assert.Equal(x, p1.X);
+ Assert.Equal(y, p1.Y);
+ }
+
+ [Fact]
+ public void IsEmptyDefaultsTest()
+ {
+ Assert.True(PointF.Empty.IsEmpty);
+ Assert.True(new PointF().IsEmpty);
+ Assert.True(new PointF(0, 0).IsEmpty);
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MinValue)]
+ [InlineData(float.MaxValue, float.MaxValue)]
+ public void IsEmptyRandomTest(float x, float y)
+ {
+ Assert.False(new PointF(x, y).IsEmpty);
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MinValue)]
+ [InlineData(float.MaxValue, float.MaxValue)]
+ [InlineData(0, 0)]
+ public void CoordinatesTest(float x, float y)
+ {
+ var p = new PointF(x, y);
+ Assert.Equal(x, p.X);
+ Assert.Equal(y, p.Y);
+
+ p.X = 10;
+ Assert.Equal(10, p.X);
+
+ p.Y = -10.123f;
+ Assert.Equal(-10.123, p.Y, 3);
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue, int.MaxValue, int.MinValue)]
+ [InlineData(float.MinValue, float.MaxValue, int.MinValue, int.MaxValue)]
+ [InlineData(0, 0, 0, 0)]
+ public void ArithmeticTestWithSize(float x, float y, int x1, int y1)
+ {
+ var p = new PointF(x, y);
+ var s = new Size(x1, y1);
+
+ var addExpected = new PointF(x + x1, y + y1);
+ var subExpected = new PointF(x - x1, y - y1);
+ Assert.Equal(addExpected, p + s);
+ Assert.Equal(subExpected, p - s);
+ Assert.Equal(addExpected, PointF.Add(p, s));
+ Assert.Equal(subExpected, PointF.Subtract(p, s));
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MaxValue)]
+ [InlineData(0, 0)]
+ public void ArithmeticTestWithSizeF(float x, float y)
+ {
+ var p = new PointF(x, y);
+ var s = new SizeF(y, x);
+
+ var addExpected = new PointF(x + y, y + x);
+ var subExpected = new PointF(x - y, y - x);
+ Assert.Equal(addExpected, p + s);
+ Assert.Equal(subExpected, p - s);
+ Assert.Equal(addExpected, PointF.Add(p, s));
+ Assert.Equal(subExpected, PointF.Subtract(p, s));
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MaxValue)]
+ [InlineData(float.MinValue, float.MinValue)]
+ [InlineData(float.MaxValue, float.MaxValue)]
+ [InlineData(0, 0)]
+ public void EqualityTest(float x, float y)
+ {
+ var pLeft = new PointF(x, y);
+ var pRight = new PointF(y, x);
+
+ if (x == y)
+ {
+ Assert.True(pLeft == pRight);
+ Assert.False(pLeft != pRight);
+ Assert.True(pLeft.Equals(pRight));
+ Assert.True(pLeft.Equals((object)pRight));
+ Assert.Equal(pLeft.GetHashCode(), pRight.GetHashCode());
+ return;
+ }
+
+ Assert.True(pLeft != pRight);
+ Assert.False(pLeft == pRight);
+ Assert.False(pLeft.Equals(pRight));
+ Assert.False(pLeft.Equals((object)pRight));
+ }
+
+ [Fact]
+ public static void EqualityTest_NotPointF()
+ {
+ var point = new PointF(0, 0);
+ Assert.False(point.Equals(null));
+ Assert.False(point.Equals(0));
+
+ // If PointF implements IEquatable (e.g. in .NET Core), then structs that are implicitly
+ // convertible to var can potentially be equal.
+ // See https://github.com/dotnet/corefx/issues/5255.
+ bool expectsImplicitCastToPointF = typeof(IEquatable).IsAssignableFrom(point.GetType());
+ Assert.Equal(expectsImplicitCastToPointF, point.Equals(new Point(0, 0)));
+
+ Assert.False(point.Equals((object)new Point(0, 0))); // No implicit cast
+ }
+
+ [Fact]
+ public static void GetHashCodeTest()
+ {
+ var point = new PointF(10, 10);
+ Assert.Equal(point.GetHashCode(), new PointF(10, 10).GetHashCode());
+ Assert.NotEqual(point.GetHashCode(), new PointF(20, 10).GetHashCode());
+ Assert.NotEqual(point.GetHashCode(), new PointF(10, 20).GetHashCode());
+ }
+
+ [Fact]
+ public void ToStringTest()
+ {
+ var p = new PointF(5.1F, -5.123F);
+ Assert.Equal(string.Format(CultureInfo.CurrentCulture, "PointF [ X={0}, Y={1} ]", p.X, p.Y), p.ToString());
+ }
+
+ [Fact]
+ public void ToStringEmptyTest()
+ {
+ var p = new PointF(0, 0);
+ Assert.Equal("PointF [ Empty ]", p.ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Numerics/PointTests.cs b/tests/ImageSharp.Tests/Numerics/PointTests.cs
index 82b26b946f..8eb36b6e8a 100644
--- a/tests/ImageSharp.Tests/Numerics/PointTests.cs
+++ b/tests/ImageSharp.Tests/Numerics/PointTests.cs
@@ -5,46 +5,225 @@
namespace ImageSharp.Tests
{
+ using System.Globalization;
using Xunit;
- ///
- /// Tests the struct.
- ///
public class PointTests
{
- ///
- /// Tests the equality operators for equality.
- ///
[Fact]
- public void AreEqual()
+ public void DefaultConstructorTest()
{
- Point first = new Point(100, 100);
- Point second = new Point(100, 100);
+ Assert.Equal(Point.Empty, new Point());
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void NonDefaultConstructorTest(int x, int y)
+ {
+ var p1 = new Point(x, y);
+ var p2 = new Point(new Size(x, y));
+
+ Assert.Equal(p1, p2);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue)]
+ [InlineData(int.MinValue)]
+ [InlineData(0)]
+ public void SingleIntConstructorTest(int x)
+ {
+ var p1 = new Point(x);
+ var p2 = new Point(unchecked((short)(x & 0xFFFF)), unchecked((short)((x >> 16) & 0xFFFF)));
+
+ Assert.Equal(p1, p2);
+ }
+
+ [Fact]
+ public void IsEmptyDefaultsTest()
+ {
+ Assert.True(Point.Empty.IsEmpty);
+ Assert.True(new Point().IsEmpty);
+ Assert.True(new Point(0, 0).IsEmpty);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ public void IsEmptyRandomTest(int x, int y)
+ {
+ Assert.False(new Point(x, y).IsEmpty);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void CoordinatesTest(int x, int y)
+ {
+ var p = new Point(x, y);
+ Assert.Equal(x, p.X);
+ Assert.Equal(y, p.Y);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void PointFConversionTest(int x, int y)
+ {
+ PointF p = new Point(x, y);
+ Assert.Equal(new PointF(x, y), p);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void SizeConversionTest(int x, int y)
+ {
+ var sz = (Size)new Point(x, y);
+ Assert.Equal(new Size(x, y), sz);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void ArithmeticTest(int x, int y)
+ {
+ Point addExpected, subExpected, p = new Point(x, y);
+ var s = new Size(y, x);
+
+ unchecked
+ {
+ addExpected = new Point(x + y, y + x);
+ subExpected = new Point(x - y, y - x);
+ }
- Assert.Equal(first, second);
+ Assert.Equal(addExpected, p + s);
+ Assert.Equal(subExpected, p - s);
+ Assert.Equal(addExpected, Point.Add(p, s));
+ Assert.Equal(subExpected, Point.Subtract(p, s));
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MinValue)]
+ [InlineData(float.MaxValue, float.MaxValue)]
+ [InlineData(0, 0)]
+ public void PointFMathematicalTest(float x, float y)
+ {
+ var pf = new PointF(x, y);
+ Point pCeiling, pTruncate, pRound;
+
+ unchecked
+ {
+ pCeiling = new Point((int)MathF.Ceiling(x), (int)MathF.Ceiling(y));
+ pTruncate = new Point((int)x, (int)y);
+ pRound = new Point((int)MathF.Round(x), (int)MathF.Round(y));
+ }
+
+ Assert.Equal(pCeiling, Point.Ceiling(pf));
+ Assert.Equal(pRound, Point.Round(pf));
+ Assert.Equal(pTruncate, (Point)pf);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void OffsetTest(int x, int y)
+ {
+ var p1 = new Point(x, y);
+ var p2 = new Point(y, x);
+
+ p1.Offset(p2);
+
+ Assert.Equal(unchecked(p2.X + p2.Y), p1.X);
+ Assert.Equal(p1.X, p1.Y);
+
+ p2.Offset(x, y);
+ Assert.Equal(p1, p2);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void EqualityTest(int x, int y)
+ {
+ var p1 = new Point(x, y);
+ var p2 = new Point(x / 2 - 1, y / 2 - 1);
+ var p3 = new Point(x, y);
+
+ Assert.True(p1 == p3);
+ Assert.True(p1 != p2);
+ Assert.True(p2 != p3);
+
+ Assert.True(p1.Equals(p3));
+ Assert.False(p1.Equals(p2));
+ Assert.False(p2.Equals(p3));
+
+ Assert.True(p1.Equals((object)p3));
+ Assert.False(p1.Equals((object)p2));
+ Assert.False(p2.Equals((object)p3));
+
+ Assert.Equal(p1.GetHashCode(), p3.GetHashCode());
+ }
+
+ [Fact]
+ public static void EqualityTest_NotPoint()
+ {
+ var point = new Point(0, 0);
+ Assert.False(point.Equals(null));
+ Assert.False(point.Equals(0));
+ Assert.False(point.Equals(new PointF(0, 0)));
}
- ///
- /// Tests the equality operators for inequality.
- ///
[Fact]
- public void AreNotEqual()
+ public static void GetHashCodeTest()
{
- Point first = new Point(0, 100);
- Point second = new Point(100, 100);
+ var point = new Point(10, 10);
+ Assert.Equal(point.GetHashCode(), new Point(10, 10).GetHashCode());
+ Assert.NotEqual(point.GetHashCode(), new Point(20, 10).GetHashCode());
+ Assert.NotEqual(point.GetHashCode(), new Point(10, 20).GetHashCode());
+ }
- Assert.NotEqual(first, second);
+ [Theory]
+ [InlineData(0, 0, 0, 0)]
+ [InlineData(1, -2, 3, -4)]
+ public void ConversionTest(int x, int y, int width, int height)
+ {
+ var rect = new Rectangle(x, y, width, height);
+ RectangleF rectF = rect;
+ Assert.Equal(x, rectF.X);
+ Assert.Equal(y, rectF.Y);
+ Assert.Equal(width, rectF.Width);
+ Assert.Equal(height, rectF.Height);
+ }
+
+ [Fact]
+ public void ToStringTest()
+ {
+ var p = new Point(5, -5);
+ Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Point [ X={0}, Y={1} ]", p.X, p.Y), p.ToString());
}
- ///
- /// Tests whether the point constructor correctly assign properties.
- ///
[Fact]
- public void ConstructorAssignsProperties()
+ public void ToStringEmptyTest()
{
- Point first = new Point(4, 5);
- Assert.Equal(4, first.X);
- Assert.Equal(5, first.Y);
+ var p = new Point(0, 0);
+ Assert.Equal("Point [ Empty ]", p.ToString());
}
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Numerics/SizeFTests.cs b/tests/ImageSharp.Tests/Numerics/SizeFTests.cs
new file mode 100644
index 0000000000..dfeee762f5
--- /dev/null
+++ b/tests/ImageSharp.Tests/Numerics/SizeFTests.cs
@@ -0,0 +1,163 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests
+{
+ using System;
+ using System.Globalization;
+ using System.Reflection;
+ using Xunit;
+
+ public class SizeFTests
+ {
+ [Fact]
+ public void DefaultConstructorTest()
+ {
+ Assert.Equal(SizeF.Empty, new SizeF());
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MinValue)]
+ [InlineData(float.MaxValue, float.MaxValue)]
+ [InlineData(0, 0)]
+ public void NonDefaultConstructorAndDimensionsTest(float width, float height)
+ {
+ var s1 = new SizeF(width, height);
+ var p1 = new PointF(width, height);
+ var s2 = new SizeF(s1);
+
+ Assert.Equal(s1, s2);
+ Assert.Equal(s1, new SizeF(p1));
+ Assert.Equal(s2, new SizeF(p1));
+
+ Assert.Equal(width, s1.Width);
+ Assert.Equal(height, s1.Height);
+
+ s1.Width = 10;
+ Assert.Equal(10, s1.Width);
+
+ s1.Height = -10.123f;
+ Assert.Equal(-10.123, s1.Height, 3);
+ }
+
+ [Fact]
+ public void IsEmptyDefaultsTest()
+ {
+ Assert.True(SizeF.Empty.IsEmpty);
+ Assert.True(new SizeF().IsEmpty);
+ Assert.True(new SizeF(0, 0).IsEmpty);
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MinValue)]
+ [InlineData(float.MaxValue, float.MaxValue)]
+ public void IsEmptyRandomTest(float width, float height)
+ {
+ Assert.False(new SizeF(width, height).IsEmpty);
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MinValue)]
+ [InlineData(float.MaxValue, float.MaxValue)]
+ [InlineData(0, 0)]
+ public void ArithmeticTest(float width, float height)
+ {
+ var s1 = new SizeF(width, height);
+ var s2 = new SizeF(height, width);
+ var addExpected = new SizeF(width + height, width + height);
+ var subExpected = new SizeF(width - height, height - width);
+
+ Assert.Equal(addExpected, s1 + s2);
+ Assert.Equal(addExpected, SizeF.Add(s1, s2));
+
+ Assert.Equal(subExpected, s1 - s2);
+ Assert.Equal(subExpected, SizeF.Subtract(s1, s2));
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MinValue)]
+ [InlineData(float.MaxValue, float.MaxValue)]
+ [InlineData(0, 0)]
+ public void EqualityTest(float width, float height)
+ {
+ var sLeft = new SizeF(width, height);
+ var sRight = new SizeF(height, width);
+
+ if (width == height)
+ {
+ Assert.True(sLeft == sRight);
+ Assert.False(sLeft != sRight);
+ Assert.True(sLeft.Equals(sRight));
+ Assert.True(sLeft.Equals((object)sRight));
+ Assert.Equal(sLeft.GetHashCode(), sRight.GetHashCode());
+ return;
+ }
+
+ Assert.True(sLeft != sRight);
+ Assert.False(sLeft == sRight);
+ Assert.False(sLeft.Equals(sRight));
+ Assert.False(sLeft.Equals((object)sRight));
+ }
+
+ [Fact]
+ public static void EqualityTest_NotSizeF()
+ {
+ var size = new SizeF(0, 0);
+ Assert.False(size.Equals(null));
+ Assert.False(size.Equals(0));
+
+ // If SizeF implements IEquatable (e.g in .NET Core), then classes that are implicitly
+ // convertible to SizeF can potentially be equal.
+ // See https://github.com/dotnet/corefx/issues/5255.
+ bool expectsImplicitCastToSizeF = typeof(IEquatable).IsAssignableFrom(size.GetType());
+ Assert.Equal(expectsImplicitCastToSizeF, size.Equals(new Size(0, 0)));
+
+ Assert.False(size.Equals((object)new Size(0, 0))); // No implicit cast
+ }
+
+ [Fact]
+ public static void GetHashCodeTest()
+ {
+ var size = new SizeF(10, 10);
+ Assert.Equal(size.GetHashCode(), new SizeF(10, 10).GetHashCode());
+ Assert.NotEqual(size.GetHashCode(), new SizeF(20, 10).GetHashCode());
+ Assert.NotEqual(size.GetHashCode(), new SizeF(10, 20).GetHashCode());
+ }
+
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MinValue)]
+ [InlineData(float.MaxValue, float.MaxValue)]
+ [InlineData(0, 0)]
+ public void ConversionTest(float width, float height)
+ {
+ var s1 = new SizeF(width, height);
+ var p1 = (PointF)s1;
+ var s2 = new Size(unchecked((int)width), unchecked((int)height));
+
+ Assert.Equal(new PointF(width, height), p1);
+ Assert.Equal(p1, (PointF)s1);
+ Assert.Equal(s2, (Size)s1);
+ }
+
+ [Fact]
+ public void ToStringTest()
+ {
+ var sz = new SizeF(10, 5);
+ Assert.Equal(string.Format(CultureInfo.CurrentCulture, "SizeF [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString());
+ }
+
+ [Fact]
+ public void ToStringTestEmpty()
+ {
+ var sz = new SizeF(0, 0);
+ Assert.Equal("SizeF [ Empty ]", sz.ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Numerics/SizeTests.cs b/tests/ImageSharp.Tests/Numerics/SizeTests.cs
index 29eb768d93..010cdad3c4 100644
--- a/tests/ImageSharp.Tests/Numerics/SizeTests.cs
+++ b/tests/ImageSharp.Tests/Numerics/SizeTests.cs
@@ -5,6 +5,7 @@
namespace ImageSharp.Tests
{
+ using System.Globalization;
using Xunit;
///
@@ -12,39 +13,183 @@ namespace ImageSharp.Tests
///
public class SizeTests
{
- ///
- /// Tests the equality operators for equality.
- ///
[Fact]
- public void AreEqual()
+ public void DefaultConstructorTest()
{
- Size first = new Size(100, 100);
- Size second = new Size(100, 100);
+ Assert.Equal(Size.Empty, new Size());
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void NonDefaultConstructorTest(int width, int height)
+ {
+ var s1 = new Size(width, height);
+ var s2 = new Size(new Point(width, height));
- Assert.Equal(first, second);
+ Assert.Equal(s1, s2);
+
+ s1.Width = 10;
+ Assert.Equal(10, s1.Width);
+
+ s1.Height = -10;
+ Assert.Equal(-10, s1.Height);
}
- ///
- /// Tests the equality operators for inequality.
- ///
[Fact]
- public void AreNotEqual()
+ public void IsEmptyDefaultsTest()
+ {
+ Assert.True(Size.Empty.IsEmpty);
+ Assert.True(new Size().IsEmpty);
+ Assert.True(new Size(0, 0).IsEmpty);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ public void IsEmptyRandomTest(int width, int height)
+ {
+ Assert.False(new Size(width, height).IsEmpty);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void DimensionsTest(int width, int height)
{
- Size first = new Size(0, 100);
- Size second = new Size(100, 100);
+ var p = new Size(width, height);
+ Assert.Equal(width, p.Width);
+ Assert.Equal(height, p.Height);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void PointFConversionTest(int width, int height)
+ {
+ SizeF sz = new Size(width, height);
+ Assert.Equal(new SizeF(width, height), sz);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void SizeConversionTest(int width, int height)
+ {
+ var sz = (Point)new Size(width, height);
+ Assert.Equal(new Point(width, height), sz);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void ArithmeticTest(int width, int height)
+ {
+ var sz1 = new Size(width, height);
+ var sz2 = new Size(height, width);
+ Size addExpected, subExpected;
+
+ unchecked
+ {
+ addExpected = new Size(width + height, height + width);
+ subExpected = new Size(width - height, height - width);
+ }
+
+ Assert.Equal(addExpected, sz1 + sz2);
+ Assert.Equal(subExpected, sz1 - sz2);
+ Assert.Equal(addExpected, Size.Add(sz1, sz2));
+ Assert.Equal(subExpected, Size.Subtract(sz1, sz2));
+ }
- Assert.NotEqual(first, second);
+ [Theory]
+ [InlineData(float.MaxValue, float.MinValue)]
+ [InlineData(float.MinValue, float.MinValue)]
+ [InlineData(float.MaxValue, float.MaxValue)]
+ [InlineData(0, 0)]
+ public void PointFMathematicalTest(float width, float height)
+ {
+ var szF = new SizeF(width, height);
+ Size pCeiling, pTruncate, pRound;
+
+ unchecked
+ {
+ pCeiling = new Size((int)MathF.Ceiling(width), (int)MathF.Ceiling(height));
+ pTruncate = new Size((int)width, (int)height);
+ pRound = new Size((int)MathF.Round(width), (int)MathF.Round(height));
+ }
+
+ Assert.Equal(pCeiling, Size.Ceiling(szF));
+ Assert.Equal(pRound, Size.Round(szF));
+ Assert.Equal(pTruncate, (Size)szF);
+ }
+
+ [Theory]
+ [InlineData(int.MaxValue, int.MinValue)]
+ [InlineData(int.MinValue, int.MinValue)]
+ [InlineData(int.MaxValue, int.MaxValue)]
+ [InlineData(0, 0)]
+ public void EqualityTest(int width, int height)
+ {
+ var p1 = new Size(width, height);
+ var p2 = new Size(unchecked(width - 1), unchecked(height - 1));
+ var p3 = new Size(width, height);
+
+ Assert.True(p1 == p3);
+ Assert.True(p1 != p2);
+ Assert.True(p2 != p3);
+
+ Assert.True(p1.Equals(p3));
+ Assert.False(p1.Equals(p2));
+ Assert.False(p2.Equals(p3));
+
+ Assert.True(p1.Equals((object)p3));
+ Assert.False(p1.Equals((object)p2));
+ Assert.False(p2.Equals((object)p3));
+
+ Assert.Equal(p1.GetHashCode(), p3.GetHashCode());
+ }
+
+ [Fact]
+ public static void EqualityTest_NotSize()
+ {
+ var size = new Size(0, 0);
+ Assert.False(size.Equals(null));
+ Assert.False(size.Equals(0));
+ Assert.False(size.Equals(new SizeF(0, 0)));
+ }
+
+ [Fact]
+ public static void GetHashCodeTest()
+ {
+ var size = new Size(10, 10);
+ Assert.Equal(size.GetHashCode(), new Size(10, 10).GetHashCode());
+ Assert.NotEqual(size.GetHashCode(), new Size(20, 10).GetHashCode());
+ Assert.NotEqual(size.GetHashCode(), new Size(10, 20).GetHashCode());
+ }
+
+ [Fact]
+ public void ToStringTest()
+ {
+ var sz = new Size(10, 5);
+ Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Size [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString());
}
- ///
- /// Tests whether the size constructor correctly assign properties.
- ///
[Fact]
- public void ConstructorAssignsProperties()
+ public void ToStringTestEmpty()
{
- Size first = new Size(4, 5);
- Assert.Equal(4, first.Width);
- Assert.Equal(5, first.Height);
+ var sz = new Size(0, 0);
+ Assert.Equal("Size [ Empty ]", sz.ToString());
}
}
}
\ No newline at end of file