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