Browse Source

Merge pull request #230 from JimBobSquarePants/chore-primitives

Expand and test primitives
pull/234/head
James Jackson-South 9 years ago
committed by GitHub
parent
commit
d14905dcd7
  1. 4
      src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs
  2. 3
      src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs
  3. 72
      src/ImageSharp/Numerics/Matrix3x2Extensions.cs
  4. 259
      src/ImageSharp/Numerics/Point.cs
  5. 233
      src/ImageSharp/Numerics/PointF.cs
  6. 440
      src/ImageSharp/Numerics/Rectangle.cs
  7. 414
      src/ImageSharp/Numerics/RectangleF.cs
  8. 137
      src/ImageSharp/Numerics/Size.cs
  9. 179
      src/ImageSharp/Numerics/SizeF.cs
  10. 2
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  11. 2
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  12. 2
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  13. 2
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  14. 2
      tests/ImageSharp.Benchmarks/Samplers/Glow.cs
  15. 192
      tests/ImageSharp.Tests/Numerics/PointFTests.cs
  16. 250
      tests/ImageSharp.Tests/Numerics/PointTests.cs
  17. 267
      tests/ImageSharp.Tests/Numerics/RectangleFTests.cs
  18. 318
      tests/ImageSharp.Tests/Numerics/RectangleTests.cs
  19. 163
      tests/ImageSharp.Tests/Numerics/SizeFTests.cs
  20. 187
      tests/ImageSharp.Tests/Numerics/SizeTests.cs

4
src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs

@ -132,7 +132,7 @@ namespace ImageSharp.Drawing.Pens
{
this.brush = brush.CreateApplicator(sourcePixels, region, options);
this.halfWidth = width / 2;
this.RequiredRegion = RectangleF.Outset(region, width);
this.RequiredRegion = RectangleF.Inflate(region, width, width);
}
public override RectangleF RequiredRegion
@ -185,7 +185,7 @@ namespace ImageSharp.Drawing.Pens
this.pattern[i + 1] = this.totalLength;
}
this.RequiredRegion = RectangleF.Outset(region, width);
this.RequiredRegion = RectangleF.Inflate(region, width, width);
}
public override RectangleF RequiredRegion

3
src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs

@ -6,7 +6,6 @@
namespace ImageSharp.Drawing.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
using ImageSharp.Memory;
@ -58,7 +57,7 @@ namespace ImageSharp.Drawing.Processors
{
using (PenApplicator<TPixel> applicator = this.Pen.CreateApplicator(source, this.Path.Bounds, this.Options))
{
Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion);
var rect = Rectangle.Ceiling(applicator.RequiredRegion);
int polyStartY = rect.Y - PaddingFactor;
int polyEndY = rect.Bottom + PaddingFactor;

72
src/ImageSharp/Numerics/Matrix3x2Extensions.cs

@ -0,0 +1,72 @@
// <copyright file="Matrix3x2Extensions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Extension methods for the <see cref="Matrix3x2"/> struct
/// </summary>
public static class Matrix3x2Extensions
{
/// <summary>
/// Creates a rotation matrix for the given rotation in degrees and a center point.
/// </summary>
/// <param name="degree">The angle in degrees</param>
/// <param name="centerPoint">The center point</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
[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));
}
/// <summary>
/// Creates a rotation matrix for the given rotation in degrees and a center point.
/// </summary>
/// <param name="degree">The angle in degrees</param>
/// <param name="centerPoint">The center point</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotation(float degree, PointF centerPoint)
{
float radian = MathF.DegreeToRadian(degree);
return Matrix3x2.CreateRotation(radian, centerPoint);
}
/// <summary>
/// Creates a skew matrix for the given angle in degrees and a center point.
/// </summary>
/// <param name="degreesX">The x-angle in degrees</param>
/// <param name="degreesY">The y-angle in degrees</param>
/// <param name="centerPoint">The center point</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
[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));
}
/// <summary>
/// Creates a skew matrix for the given angle in degrees and a center point.
/// </summary>
/// <param name="degreesX">The x-angle in degrees</param>
/// <param name="degreesY">The y-angle in degrees</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
/// <param name="centerPoint">The center point</param>
[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));
}
}
}

259
src/ImageSharp/Numerics/Point.cs

@ -25,6 +25,17 @@ namespace ImageSharp
/// </summary>
public static readonly Point Empty = default(Point);
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> struct.
/// </summary>
/// <param name="value">The horizontal and vertical position of the point.</param>
public Point(int value)
: this()
{
this.X = LowInt16(value);
this.Y = HighInt16(value);
}
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> struct.
/// </summary>
@ -38,15 +49,13 @@ namespace ImageSharp
}
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> struct.
/// Initializes a new instance of the <see cref="Point"/> struct from the given <see cref="Size"/>.
/// </summary>
/// <param name="vector">
/// The vector representing the width and height.
/// </param>
public Point(Vector2 vector)
/// <param name="size">The size</param>
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;
}
/// <summary>
@ -66,176 +75,160 @@ namespace ImageSharp
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Computes the sum of adding two points.
/// Creates a <see cref="PointF"/> with the coordinates of the specified <see cref="Point"/>.
/// </summary>
/// <param name="left">The point on the left hand of the operand.</param>
/// <param name="right">The point on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Point"/>
/// </returns>
/// <param name="point">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point operator +(Point left, Point right)
{
return new Point(left.X + right.X, left.Y + right.Y);
}
public static implicit operator PointF(Point point) => new PointF(point.X, point.Y);
/// <summary>
/// Creates a <see cref="Vector2"/> with the coordinates of the specified <see cref="Point"/>.
/// </summary>
/// <param name="point">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Vector2(Point point) => new Vector2(point.X, point.Y);
/// <summary>
/// Creates a <see cref="Size"/> with the coordinates of the specified <see cref="Point"/>.
/// </summary>
/// <param name="point">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Size(Point point) => new Size(point.X, point.Y);
/// <summary>
/// Computes the difference left by subtracting one point from another.
/// Translates a <see cref="Point"/> by a given <see cref="Size"/>.
/// </summary>
/// <param name="left">The point on the left hand of the operand.</param>
/// <param name="right">The point on the right hand of the operand.</param>
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Point"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point operator -(Point left, Point right)
{
return new Point(left.X - right.X, left.Y - right.Y);
}
public static Point operator +(Point point, Size size) => Add(point, size);
/// <summary>
/// Translates a <see cref="Point"/> by the negative of a given <see cref="Size"/>.
/// </summary>
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point operator -(Point point, Size size) => Subtract(point, size);
/// <summary>
/// Compares two <see cref="Point"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Point"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Point"/> on the right side of the operand.
/// </param>
/// <param name="left">The <see cref="Point"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Point"/> on the right side of the operand.</param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Point left, Point right)
{
return left.Equals(right);
}
public static bool operator ==(Point left, Point right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Point"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Point"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Point"/> on the right side of the operand.
/// </param>
/// <param name="left">The <see cref="Point"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Point"/> on the right side of the operand.</param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Point left, Point right)
{
return !left.Equals(right);
}
public static bool operator !=(Point left, Point right) => !left.Equals(right);
/// <summary>
/// Creates a rotation matrix for the given point and angle.
/// Translates a <see cref="Point"/> by the negative of a given <see cref="Size"/>.
/// </summary>
/// <param name="origin">The origin point to rotate around</param>
/// <param name="degrees">Rotation in degrees</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
public static Matrix3x2 CreateRotation(Point origin, float degrees)
{
float radians = MathF.DegreeToRadian(degrees);
return Matrix3x2.CreateRotation(radians, new Vector2(origin.X, origin.Y));
}
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Add(Point point, Size size) => new Point(unchecked(point.X + size.Width), unchecked(point.Y + size.Height));
/// <summary>
/// Rotates a point around a given a rotation matrix.
/// Translates a <see cref="Point"/> by the negative of a given <see cref="Size"/>.
/// </summary>
/// <param name="point">The point to rotate</param>
/// <param name="rotation">Rotation matrix used</param>
/// <returns>The rotated <see cref="Point"/></returns>
public static Point Rotate(Point point, Matrix3x2 rotation)
{
return new Point(Vector2.Transform(new Vector2(point.X, point.Y), rotation));
}
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Subtract(Point point, Size size) => new Point(unchecked(point.X - size.Width), unchecked(point.Y - size.Height));
/// <summary>
/// Rotates a point around a given origin by the specified angle in degrees.
/// Converts a <see cref="PointF"/> to a <see cref="Point"/> by performing a ceiling operation on all the coordinates.
/// </summary>
/// <param name="point">The point to rotate</param>
/// <param name="origin">The center point to rotate around.</param>
/// <param name="degrees">The angle in degrees.</param>
/// <returns>The rotated <see cref="Point"/></returns>
public static Point Rotate(Point point, Point origin, float degrees)
{
return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateRotation(origin, degrees)));
}
/// <param name="point">The point</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Ceiling(PointF point) => new Point(unchecked((int)MathF.Ceiling(point.X)), unchecked((int)MathF.Ceiling(point.Y)));
/// <summary>
/// Creates a skew matrix for the given point and angle.
/// Converts a <see cref="PointF"/> to a <see cref="Point"/> by performing a round operation on all the coordinates.
/// </summary>
/// <param name="origin">The origin point to rotate around</param>
/// <param name="degreesX">The x-angle in degrees.</param>
/// <param name="degreesY">The y-angle in degrees.</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY)
{
float radiansX = MathF.DegreeToRadian(degreesX);
float radiansY = MathF.DegreeToRadian(degreesY);
return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(origin.X, origin.Y));
}
/// <param name="point">The point</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Round(PointF point) => new Point(unchecked((int)MathF.Round(point.X)), unchecked((int)MathF.Round(point.Y)));
/// <summary>
/// Skews a point using a given a skew matrix.
/// Converts a <see cref="PointF"/> to a <see cref="Point"/> by performing a truncate operation on all the coordinates.
/// </summary>
/// <param name="point">The point to rotate</param>
/// <param name="skew">Rotation matrix used</param>
/// <returns>The rotated <see cref="Point"/></returns>
public static Point Skew(Point point, Matrix3x2 skew)
{
return new Point(Vector2.Transform(new Vector2(point.X, point.Y), skew));
}
/// <param name="point">The point</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Truncate(PointF point) => new Point(unchecked((int)point.X), unchecked((int)point.Y));
/// <summary>
/// Skews a point around a given origin by the specified angles in degrees.
/// Converts a <see cref="Vector2"/> to a <see cref="Point"/> by performing a round operation on all the coordinates.
/// </summary>
/// <param name="point">The point to skew.</param>
/// <param name="origin">The center point to rotate around.</param>
/// <param name="degreesX">The x-angle in degrees.</param>
/// <param name="degreesY">The y-angle in degrees.</param>
/// <returns>The skewed <see cref="Point"/></returns>
public static Point Skew(Point point, Point origin, float degreesX, float degreesY)
{
return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateSkew(origin, degreesX, degreesY)));
}
/// <param name="vector">The vector</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Round(Vector2 vector) => new Point(unchecked((int)MathF.Round(vector.X)), unchecked((int)MathF.Round(vector.Y)));
/// <summary>
/// Gets a <see cref="Vector2"/> representation for this <see cref="Point"/>.
/// Rotates a point around the given rotation matrix.
/// </summary>
/// <returns>A <see cref="Vector2"/> representation for this object.</returns>
public Vector2 ToVector2()
{
return new Vector2(this.X, this.Y);
}
/// <param name="point">The point to rotate</param>
/// <param name="rotation">Rotation matrix used</param>
/// <returns>The rotated <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Rotate(Point point, Matrix3x2 rotation) => Round(Vector2.Transform(new Vector2(point.X, point.Y), rotation));
/// <summary>
/// Skews a point using the given skew matrix.
/// </summary>
/// <param name="point">The point to rotate</param>
/// <param name="skew">Rotation matrix used</param>
/// <returns>The rotated <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Skew(Point point, Matrix3x2 skew) => Round(Vector2.Transform(new Vector2(point.X, point.Y), skew));
/// <summary>
/// Translates this <see cref="Point"/> by the specified amount.
/// </summary>
/// <param name="dx">The amount to offset the x-coordinate.</param>
/// <param name="dy">The amount to offset the y-coordinate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(int dx, int dy)
{
this.X += dx;
this.Y += dy;
unchecked
{
this.X += dx;
this.Y += dy;
}
}
/// <summary>
/// Translates this <see cref="Point"/> by the specified amount.
/// </summary>
/// <param name="p">The <see cref="Point"/> used offset this <see cref="Point"/>.</param>
public void Offset(Point p)
{
this.Offset(p.X, p.Y);
}
/// <param name="point">The <see cref="Point"/> used offset this <see cref="Point"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(Point point) => this.Offset(point.X, point.Y);
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
@ -249,34 +242,16 @@ namespace ImageSharp
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Point)
{
return this.Equals((Point)obj);
}
return false;
}
public override bool Equals(object obj) => obj is Point && this.Equals((Point)obj);
/// <inheritdoc/>
public bool Equals(Point other)
{
return this.X == other.X && this.Y == other.Y;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Point other) => this.X == other.X && this.Y == other.Y;
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="point">
/// The instance of <see cref="Point"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(Point point)
{
return point.X ^ point.Y;
}
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) => point.X ^ point.Y;
}
}

233
src/ImageSharp/Numerics/PointF.cs

@ -0,0 +1,233 @@
// <copyright file="PointF.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an ordered pair of single precision floating point x- and y-coordinates that defines a point in
/// a two-dimensional plane.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public struct PointF : IEquatable<PointF>
{
/// <summary>
/// Represents a <see cref="PointF"/> that has X and Y values set to zero.
/// </summary>
public static readonly PointF Empty = default(PointF);
/// <summary>
/// Initializes a new instance of the <see cref="PointF"/> struct.
/// </summary>
/// <param name="x">The horizontal position of the point.</param>
/// <param name="y">The vertical position of the point.</param>
public PointF(float x, float y)
: this()
{
this.X = x;
this.Y = y;
}
/// <summary>
/// Initializes a new instance of the <see cref="PointF"/> struct from the given <see cref="SizeF"/>.
/// </summary>
/// <param name="size">The size</param>
public PointF(SizeF size)
{
this.X = size.Width;
this.Y = size.Height;
}
/// <summary>
/// Gets or sets the x-coordinate of this <see cref="PointF"/>.
/// </summary>
public float X { get; set; }
/// <summary>
/// Gets or sets the y-coordinate of this <see cref="PointF"/>.
/// </summary>
public float Y { get; set; }
/// <summary>
/// Gets a value indicating whether this <see cref="PointF"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Creates a <see cref="Vector2"/> with the coordinates of the specified <see cref="PointF"/>.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>
/// The <see cref="Vector2"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator PointF(Vector2 vector) => new PointF(vector.X, vector.Y);
/// <summary>
/// Creates a <see cref="Vector2"/> with the coordinates of the specified <see cref="PointF"/>.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The <see cref="Vector2"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Vector2(PointF point) => new Vector2(point.X, point.Y);
/// <summary>
/// Creates a <see cref="Point"/> with the coordinates of the specified <see cref="PointF"/> by truncating each of the coordinates.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The <see cref="Point"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Point(PointF point) => Point.Truncate(point);
/// <summary>
/// Translates a <see cref="PointF"/> by a given <see cref="SizeF"/>.
/// </summary>
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>
/// The <see cref="PointF"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF operator +(PointF point, SizeF size) => Add(point, size);
/// <summary>
/// Translates a <see cref="PointF"/> by the negative of a given <see cref="SizeF"/>.
/// </summary>
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The <see cref="PointF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF operator -(PointF point, SizeF size) => Subtract(point, size);
/// <summary>
/// Compares two <see cref="PointF"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="PointF"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="PointF"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(PointF left, PointF right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="PointF"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="PointF"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="PointF"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(PointF left, PointF right) => !left.Equals(right);
/// <summary>
/// Translates a <see cref="PointF"/> by the negative of a given <see cref="SizeF"/>.
/// </summary>
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The <see cref="PointF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Add(PointF point, SizeF size) => new PointF(point.X + size.Width, point.Y + size.Height);
/// <summary>
/// Translates a <see cref="PointF"/> by the negative of a given <see cref="SizeF"/>.
/// </summary>
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The <see cref="PointF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Subtract(PointF point, SizeF size) => new PointF(point.X - size.Width, point.Y - size.Height);
/// <summary>
/// Rotates a point around the given rotation matrix.
/// </summary>
/// <param name="point">The point to rotate</param>
/// <param name="rotation">Rotation matrix used</param>
/// <returns>The rotated <see cref="PointF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Rotate(PointF point, Matrix3x2 rotation) => Vector2.Transform(new Vector2(point.X, point.Y), rotation);
/// <summary>
/// Skews a point using the given skew matrix.
/// </summary>
/// <param name="point">The point to rotate</param>
/// <param name="skew">Rotation matrix used</param>
/// <returns>The rotated <see cref="PointF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Skew(PointF point, Matrix3x2 skew) => Vector2.Transform(new Vector2(point.X, point.Y), skew);
/// <summary>
/// Translates this <see cref="PointF"/> by the specified amount.
/// </summary>
/// <param name="dx">The amount to offset the x-coordinate.</param>
/// <param name="dy">The amount to offset the y-coordinate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(float dx, float dy)
{
this.X += dx;
this.Y += dy;
}
/// <summary>
/// Translates this <see cref="PointF"/> by the specified amount.
/// </summary>
/// <param name="point">The <see cref="PointF"/> used offset this <see cref="PointF"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(PointF point) => this.Offset(point.X, point.Y);
/// <inheritdoc/>
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "PointF [ Empty ]";
}
return $"PointF [ X={this.X}, Y={this.Y} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj) => obj is PointF && this.Equals((PointF)obj);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(PointF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y);
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="point">
/// The instance of <see cref="PointF"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(PointF point) => point.X.GetHashCode() ^ point.Y.GetHashCode();
}
}

440
src/ImageSharp/Numerics/Rectangle.cs

@ -8,6 +8,7 @@ namespace ImageSharp
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Stores a set of four integers that represent the location and size of a rectangle.
@ -23,11 +24,6 @@ namespace ImageSharp
/// </summary>
public static readonly Rectangle Empty = default(Rectangle);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
/// </summary>
@ -37,7 +33,10 @@ namespace ImageSharp
/// <param name="height">The height of the rectangle.</param>
public Rectangle(int x, int y, int width, int height)
{
this.backingVector = new Vector4(x, y, width, height);
this.X = x;
this.Y = y;
this.Width = width;
this.Height = height;
}
/// <summary>
@ -51,197 +50,325 @@ namespace ImageSharp
/// </param>
public Rectangle(Point point, Size size)
{
this.backingVector = new Vector4(point.X, point.Y, size.Width, size.Height);
this.X = point.X;
this.Y = point.Y;
this.Width = size.Width;
this.Height = size.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
/// Gets or sets the x-coordinate of this <see cref="Rectangle"/>.
/// </summary>
/// <param name="topLeft">
/// The <see cref="Point"/> which specifies the rectangles top left point in a two-dimensional plane.
/// </param>
/// <param name="bottomRight">
/// The <see cref="Point"/>which specifies the rectangles bottom right point in a two-dimensional plane.
/// </param>
public Rectangle(Point topLeft, Point bottomRight)
{
this.backingVector = new Vector4(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
}
public int X { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
/// Gets or sets the y-coordinate of this <see cref="Rectangle"/>.
/// </summary>
/// <param name="vector">The vector.</param>
public Rectangle(Vector4 vector)
{
this.backingVector = vector;
}
public int Y { get; set; }
/// <summary>
/// Gets or sets the x-coordinate of this <see cref="Rectangle"/>.
/// Gets or sets the width of this <see cref="Rectangle"/>.
/// </summary>
public int Width { get; set; }
/// <summary>
/// Gets or sets the height of this <see cref="Rectangle"/>.
/// </summary>
public int X
public int Height { get; set; }
/// <summary>
/// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this <see cref="Rectangle"/>.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Point Location
{
get
{
return (int)this.backingVector.X;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Point(this.X, this.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.backingVector.X = value;
this.X = value.X;
this.Y = value.Y;
}
}
/// <summary>
/// Gets or sets the y-coordinate of this <see cref="Rectangle"/>.
/// Gets or sets the size of this <see cref="Rectangle"/>.
/// </summary>
public int Y
[EditorBrowsable(EditorBrowsableState.Never)]
public Size Size
{
get
{
return (int)this.backingVector.Y;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Size(this.Width, this.Height);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.backingVector.Y = value;
this.Width = value.Width;
this.Height = value.Height;
}
}
/// <summary>
/// Gets or sets the width of this <see cref="Rectangle"/>.
/// Gets a value indicating whether this <see cref="Rectangle"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Gets the y-coordinate of the top edge of this <see cref="Rectangle"/>.
/// </summary>
public int Width
public int Top
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return (int)this.backingVector.Z;
return this.Y;
}
}
set
/// <summary>
/// Gets the x-coordinate of the right edge of this <see cref="Rectangle"/>.
/// </summary>
public int Right
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
this.backingVector.Z = value;
return unchecked(this.X + this.Width);
}
}
/// <summary>
/// Gets or sets the height of this <see cref="Rectangle"/>.
/// Gets the y-coordinate of the bottom edge of this <see cref="Rectangle"/>.
/// </summary>
public int Height
public int Bottom
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return (int)this.backingVector.W;
return unchecked(this.Y + this.Height);
}
}
set
/// <summary>
/// Gets the x-coordinate of the left edge of this <see cref="Rectangle"/>.
/// </summary>
public int Left
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
this.backingVector.W = value;
return this.X;
}
}
/// <summary>
/// Gets the size of this <see cref="Rectangle"/>.
/// Creates a <see cref="RectangleF"/> with the coordinates of the specified <see cref="Rectangle"/>.
/// </summary>
public Size Size => new Size(this.Width, this.Height);
/// <param name="rectangle">The rectangle</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator RectangleF(Rectangle rectangle) => new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
/// <summary>
/// Gets a value indicating whether this <see cref="Rectangle"/> is empty.
/// Creates a <see cref="Vector4"/> with the coordinates of the specified <see cref="Rectangle"/>.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <param name="rectangle">The rectangle</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Vector4(Rectangle rectangle) => new Vector4(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
/// <summary>
/// Gets the y-coordinate of the top edge of this <see cref="Rectangle"/>.
/// Compares two <see cref="Rectangle"/> objects for equality.
/// </summary>
public int Top => this.Y;
/// <param name="left">The <see cref="Rectangle"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Rectangle"/> on the right side of the operand.</param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Rectangle left, Rectangle right) => left.Equals(right);
/// <summary>
/// Gets the x-coordinate of the right edge of this <see cref="Rectangle"/>.
/// Compares two <see cref="Rectangle"/> objects for inequality.
/// </summary>
public int Right => this.X + this.Width;
/// <param name="left">The <see cref="Rectangle"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Rectangle"/> on the right side of the operand.</param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Rectangle left, Rectangle right) => !left.Equals(right);
/// <summary>
/// Gets the y-coordinate of the bottom edge of this <see cref="Rectangle"/>.
/// Creates a new <see cref="Rectangle"/> with the specified location and size. </summary>
/// <param name="left">The left coordinate of the rectangle</param>
/// <param name="top">The top coordinate of the rectangle</param>
/// <param name="right">The right coordinate of the rectangle</param>
/// <param name="bottom">The bottom coordinate of the rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
// ReSharper disable once InconsistentNaming
public static Rectangle FromLTRB(int left, int top, int right, int bottom) => new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top));
/// <summary>
/// Returns the center point of the given <see cref="Rectangle"/>
/// </summary>
public int Bottom => this.Y + this.Height;
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Center(Rectangle rectangle) => new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
/// <summary>
/// Gets the x-coordinate of the left edge of this <see cref="Rectangle"/>.
/// Creates a rectangle that represents the intersection between <paramref name="a"/> and
/// <paramref name="b"/>. If there is no intersection, an empty rectangle is returned.
/// </summary>
public int Left => this.X;
/// <param name="a">The first rectangle</param>
/// <param name="b">The second rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Intersect(Rectangle a, Rectangle b)
{
int x1 = Math.Max(a.X, b.X);
int x2 = Math.Min(a.Right, b.Right);
int y1 = Math.Max(a.Y, b.Y);
int y2 = Math.Min(a.Bottom, b.Bottom);
if (x2 >= x1 && y2 >= y1)
{
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
return Empty;
}
/// <summary>
/// Computes the sum of adding two rectangles.
/// Creates a <see cref="Rectangle"/> that is inflated by the specified amount.
/// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Rectangle"/>
/// </returns>
public static Rectangle operator +(Rectangle left, Rectangle right)
/// <param name="rectangle">The rectangle</param>
/// <param name="x">The amount to inflate the width by</param>
/// <param name="y">The amount to inflate the height by</param>
/// <returns>A new <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Inflate(Rectangle rectangle, int x, int y)
{
return new Rectangle(left.backingVector + right.backingVector);
Rectangle r = rectangle;
r.Inflate(x, y);
return r;
}
/// <summary>
/// Computes the difference left by subtracting one rectangle from another.
/// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a ceiling operation on all the coordinates.
/// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Rectangle"/>
/// </returns>
public static Rectangle operator -(Rectangle left, Rectangle right)
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Ceiling(RectangleF rectangle)
{
return new Rectangle(left.backingVector - right.backingVector);
unchecked
{
return new Rectangle(
(int)MathF.Ceiling(rectangle.X),
(int)MathF.Ceiling(rectangle.Y),
(int)MathF.Ceiling(rectangle.Width),
(int)MathF.Ceiling(rectangle.Height));
}
}
/// <summary>
/// Compares two <see cref="Rectangle"/> objects for equality.
/// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a truncate operation on all the coordinates.
/// </summary>
/// <param name="left">
/// The <see cref="Rectangle"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Rectangle"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Rectangle left, Rectangle right)
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Truncate(RectangleF rectangle)
{
return left.Equals(right);
unchecked
{
return new Rectangle(
(int)rectangle.X,
(int)rectangle.Y,
(int)rectangle.Width,
(int)rectangle.Height);
}
}
/// <summary>
/// Compares two <see cref="Rectangle"/> objects for inequality.
/// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a round operation on all the coordinates.
/// </summary>
/// <param name="left">
/// The <see cref="Rectangle"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Rectangle"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Rectangle left, Rectangle right)
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Round(RectangleF rectangle)
{
return !left.Equals(right);
unchecked
{
return new Rectangle(
(int)MathF.Round(rectangle.X),
(int)MathF.Round(rectangle.Y),
(int)MathF.Round(rectangle.Width),
(int)MathF.Round(rectangle.Height));
}
}
/// <summary>
/// Returns the center point of the given <see cref="Rectangle"/>
/// Creates a rectangle that represents the union between <paramref name="a"/> and <paramref name="b"/>.
/// </summary>
/// <param name="a">The first rectangle</param>
/// <param name="b">The second rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Union(Rectangle a, Rectangle b)
{
int x1 = Math.Min(a.X, b.X);
int x2 = Math.Max(a.Right, b.Right);
int y1 = Math.Min(a.Y, b.Y);
int y2 = Math.Max(a.Bottom, b.Bottom);
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
/// <summary>
/// Creates a Rectangle that represents the intersection between this Rectangle and the <paramref name="rectangle"/>.
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns><see cref="Point"/></returns>
public static Point Center(Rectangle rectangle)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Intersect(Rectangle rectangle)
{
return new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
Rectangle result = Intersect(rectangle, this);
this.X = result.X;
this.Y = result.Y;
this.Width = result.Width;
this.Height = result.Height;
}
/// <summary>
/// Inflates this <see cref="Rectangle"/> by the specified amount.
/// </summary>
/// <param name="width">The width</param>
/// <param name="height">The height</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Inflate(int width, int height)
{
unchecked
{
this.X -= width;
this.Y -= height;
this.Width += 2 * width;
this.Height += 2 * height;
}
}
/// <summary>
/// Inflates this <see cref="Rectangle"/> by the specified amount.
/// </summary>
/// <param name="size">The size</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Inflate(Size size) => this.Inflate(size.Width, size.Height);
/// <summary>
/// Determines if the specfied point is contained within the rectangular region defined by
/// this <see cref="Rectangle"/>.
@ -249,33 +376,63 @@ namespace ImageSharp
/// <param name="x">The x-coordinate of the given point.</param>
/// <param name="y">The y-coordinate of the given point.</param>
/// <returns>The <see cref="bool"/></returns>
public bool Contains(int x, int y)
{
// TODO: SIMD?
return this.X <= x
&& x < this.Right
&& this.Y <= y
&& y < this.Bottom;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom;
/// <summary>
/// Determines if the specified point is contained within the rectangular region defined by this <see cref="Rectangle"/> .
/// </summary>
/// <param name="point">The point</param>
/// <returns>The <see cref="bool"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(Point point) => this.Contains(point.X, point.Y);
/// <summary>
/// Determines if the rectangular region represented by <paramref name="rectangle"/> is entirely contained
/// within the rectangular region represented by this <see cref="Rectangle"/> .
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="bool"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(Rectangle rectangle) =>
(this.X <= rectangle.X) && (rectangle.Right <= this.Right) &&
(this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom);
/// <summary>
/// Determines if the specfied <see cref="Rectangle"/> intersects the rectangular region defined by
/// this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The other Rectange </param>
/// <param name="rectangle">The other Rectange </param>
/// <returns>The <see cref="bool"/></returns>
public bool Intersects(Rectangle rect)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IntersectsWith(Rectangle rectangle) =>
(rectangle.X < this.Right) && (this.X < rectangle.Right) &&
(rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom);
/// <summary>
/// Adjusts the location of this rectangle by the specified amount.
/// </summary>
/// <param name="point">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(Point point) => this.Offset(point.X, point.Y);
/// <summary>
/// Adjusts the location of this rectangle by the specified amount.
/// </summary>
/// <param name="dx">The amount to offset the x-coordinate.</param>
/// <param name="dy">The amount to offset the y-coordinate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(int dx, int dy)
{
return rect.Left <= this.Right && rect.Right >= this.Left
&&
rect.Top <= this.Bottom && rect.Bottom >= this.Top;
unchecked
{
this.X += dx;
this.Y += dy;
}
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
@ -285,39 +442,26 @@ namespace ImageSharp
return "Rectangle [ Empty ]";
}
return
$"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
return $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Rectangle)
{
return this.Equals((Rectangle)obj);
}
return false;
}
public override bool Equals(object obj) => obj is Rectangle && this.Equals((Rectangle)obj);
/// <inheritdoc/>
public bool Equals(Rectangle other)
{
return this.backingVector.Equals(other.backingVector);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Rectangle other) => this.X == other.X && this.Y == other.Y && this.Width == other.Width && this.Height == other.Height;
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="rectangle">
/// The instance of <see cref="Rectangle"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(Rectangle rectangle)
{
return rectangle.backingVector.GetHashCode();
unchecked
{
int hashCode = rectangle.X;
hashCode = (hashCode * 397) ^ rectangle.Y;
hashCode = (hashCode * 397) ^ rectangle.Width;
hashCode = (hashCode * 397) ^ rectangle.Height;
return hashCode;
}
}
}
}
}

414
src/ImageSharp/Numerics/RectangleF.cs

@ -8,9 +8,10 @@ namespace ImageSharp
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Stores a set of four integers that represent the location and size of a rectangle.
/// Stores a set of four single precision floating points that represent the location and size of a rectangle.
/// </summary>
/// <remarks>
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
@ -19,15 +20,10 @@ namespace ImageSharp
public struct RectangleF : IEquatable<RectangleF>
{
/// <summary>
/// Represents a <see cref="Rectangle"/> that has X, Y, Width, and Height values set to zero.
/// Represents a <see cref="RectangleF"/> that has X, Y, Width, and Height values set to zero.
/// </summary>
public static readonly RectangleF Empty = default(RectangleF);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="RectangleF"/> struct.
/// </summary>
@ -37,79 +33,80 @@ namespace ImageSharp
/// <param name="height">The height of the rectangle.</param>
public RectangleF(float x, float y, float width, float height)
{
this.backingVector = new Vector4(x, y, width, height);
this.X = x;
this.Y = y;
this.Width = width;
this.Height = height;
}
/// <summary>
/// Initializes a new instance of the <see cref="RectangleF"/> struct.
/// </summary>
/// <param name="vector">The vector.</param>
public RectangleF(Vector4 vector)
/// <param name="point">
/// The <see cref="Point"/> which specifies the rectangles point in a two-dimensional plane.
/// </param>
/// <param name="size">
/// The <see cref="Size"/> which specifies the rectangles height and width.
/// </param>
public RectangleF(PointF point, SizeF size)
{
this.backingVector = vector;
this.X = point.X;
this.Y = point.Y;
this.Width = size.Width;
this.Height = size.Height;
}
/// <summary>
/// Gets or sets the x-coordinate of this <see cref="RectangleF"/>.
/// </summary>
public float X
{
get
{
return this.backingVector.X;
}
set
{
this.backingVector.X = value;
}
}
public float X { get; set; }
/// <summary>
/// Gets or sets the y-coordinate of this <see cref="RectangleF"/>.
/// </summary>
public float Y
{
get
{
return this.backingVector.Y;
}
set
{
this.backingVector.Y = value;
}
}
public float Y { get; set; }
/// <summary>
/// Gets or sets the width of this <see cref="RectangleF"/>.
/// </summary>
public float Width
public float Width { get; set; }
/// <summary>
/// Gets or sets the height of this <see cref="RectangleF"/>.
/// </summary>
public float Height { get; set; }
/// <summary>
/// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this <see cref="RectangleF"/>.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public PointF Location
{
get
{
return this.backingVector.Z;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new PointF(this.X, this.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.backingVector.Z = value;
this.X = value.X;
this.Y = value.Y;
}
}
/// <summary>
/// Gets or sets the height of this <see cref="RectangleF"/>.
/// Gets or sets the size of this <see cref="RectangleF"/>.
/// </summary>
public float Height
[EditorBrowsable(EditorBrowsableState.Never)]
public SizeF Size
{
get
{
return this.backingVector.W;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new SizeF(this.Width, this.Height);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.backingVector.W = value;
this.Width = value.Width;
this.Height = value.Height;
}
}
@ -117,141 +114,196 @@ namespace ImageSharp
/// Gets a value indicating whether this <see cref="RectangleF"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
public bool IsEmpty => (this.Width <= 0) || (this.Height <= 0);
/// <summary>
/// Gets the y-coordinate of the top edge of this <see cref="RectangleF"/>.
/// </summary>
public float Top => this.Y;
public float Top
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Y;
}
}
/// <summary>
/// Gets the x-coordinate of the right edge of this <see cref="RectangleF"/>.
/// </summary>
public float Right => this.X + this.Width;
public float Right
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.X + this.Width;
}
}
/// <summary>
/// Gets the y-coordinate of the bottom edge of this <see cref="RectangleF"/>.
/// </summary>
public float Bottom => this.Y + this.Height;
public float Bottom
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Y + this.Height;
}
}
/// <summary>
/// Gets the x-coordinate of the left edge of this <see cref="RectangleF"/>.
/// </summary>
public float Left => this.X;
public float Left
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.X;
}
}
/// <summary>
/// Performs an implicit conversion from <see cref="Rectangle"/> to <see cref="RectangleF"/>.
/// Creates a <see cref="Rectangle"/> with the coordinates of the specified <see cref="RectangleF"/> by truncating each coordinate.
/// </summary>
/// <param name="d">The d.</param>
/// <returns>
/// The result of the conversion.
/// </returns>
public static implicit operator RectangleF(Rectangle d)
{
return new RectangleF(d.Left, d.Top, d.Width, d.Height);
}
/// <param name="rectangle">The rectangle</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Rectangle(RectangleF rectangle) => Rectangle.Truncate(rectangle);
/// <summary>
/// Computes the sum of adding two rectangles.
/// Compares two <see cref="RectangleF"/> objects for equality.
/// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <param name="left">The <see cref="RectangleF"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="RectangleF"/> on the right side of the operand.</param>
/// <returns>
/// The <see cref="RectangleF"/>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static RectangleF operator +(RectangleF left, RectangleF right)
{
return new RectangleF(left.backingVector + right.backingVector);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(RectangleF left, RectangleF right) => left.Equals(right);
/// <summary>
/// Computes the difference left by subtracting one rectangle from another.
/// Compares two <see cref="RectangleF"/> objects for inequality.
/// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <param name="left">The <see cref="RectangleF"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="RectangleF"/> on the right side of the operand.</param>
/// <returns>
/// The <see cref="RectangleF"/>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static RectangleF operator -(RectangleF left, RectangleF right)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(RectangleF left, RectangleF right) => !left.Equals(right);
/// <summary>
/// Creates a new <see cref="RectangleF"/> with the specified location and size. </summary>
/// <param name="left">The left coordinate of the rectangle</param>
/// <param name="top">The top coordinate of the rectangle</param>
/// <param name="right">The right coordinate of the rectangle</param>
/// <param name="bottom">The bottom coordinate of the rectangle</param>
/// <returns>The <see cref="RectangleF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
// ReSharper disable once InconsistentNaming
public static RectangleF FromLTRB(float left, float top, float right, float bottom) => new RectangleF(left, top, right - left, bottom - top);
/// <summary>
/// Returns the center point of the given <see cref="RectangleF"/>
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Center(RectangleF rectangle) => new PointF(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
/// <summary>
/// Creates a rectangle that represents the intersection between <paramref name="a"/> and
/// <paramref name="b"/>. If there is no intersection, an empty rectangle is returned.
/// </summary>
/// <param name="a">The first rectangle</param>
/// <param name="b">The second rectangle</param>
/// <returns>The <see cref="RectangleF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RectangleF Intersect(RectangleF a, RectangleF b)
{
return new RectangleF(left.backingVector - right.backingVector);
float x1 = MathF.Max(a.X, b.X);
float x2 = MathF.Min(a.Right, b.Right);
float y1 = MathF.Max(a.Y, b.Y);
float y2 = MathF.Min(a.Bottom, b.Bottom);
if (x2 >= x1 && y2 >= y1)
{
return new RectangleF(x1, y1, x2 - x1, y2 - y1);
}
return Empty;
}
/// <summary>
/// Compares two <see cref="RectangleF"/> objects for equality.
/// Creates a <see cref="RectangleF"/> that is inflated by the specified amount.
/// </summary>
/// <param name="left">
/// The <see cref="RectangleF"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="RectangleF"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(RectangleF left, RectangleF right)
/// <param name="rectangle">The rectangle</param>
/// <param name="x">The amount to inflate the width by</param>
/// <param name="y">The amount to inflate the height by</param>
/// <returns>A new <see cref="RectangleF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RectangleF Inflate(RectangleF rectangle, float x, float y)
{
return left.Equals(right);
RectangleF r = rectangle;
r.Inflate(x, y);
return r;
}
/// <summary>
/// Compares two <see cref="RectangleF"/> objects for inequality.
/// Creates a rectangle that represents the union between <paramref name="a"/> and <paramref name="b"/>.
/// </summary>
/// <param name="left">
/// The <see cref="RectangleF"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="RectangleF"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(RectangleF left, RectangleF right)
/// <param name="a">The first rectangle</param>
/// <param name="b">The second rectangle</param>
/// <returns>The <see cref="RectangleF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RectangleF Union(RectangleF a, RectangleF b)
{
return !left.Equals(right);
float x1 = MathF.Min(a.X, b.X);
float x2 = MathF.Max(a.Right, b.Right);
float y1 = MathF.Min(a.Y, b.Y);
float y2 = MathF.Max(a.Bottom, b.Bottom);
return new RectangleF(x1, y1, x2 - x1, y2 - y1);
}
/// <summary>
/// Returns the center point of the given <see cref="RectangleF"/>
/// Creates a RectangleF that represents the intersection between this RectangleF and the <paramref name="rectangle"/>.
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns><see cref="Point"/></returns>
public static Vector2 Center(RectangleF rectangle)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Intersect(RectangleF rectangle)
{
return new Vector2(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
RectangleF result = Intersect(rectangle, this);
this.X = result.X;
this.Y = result.Y;
this.Width = result.Width;
this.Height = result.Height;
}
/// <summary>
/// Rounds the points away from the center this into a <see cref="Rectangle"/>
/// by rounding the dimensions to the nerent integer ensuring that the new rectangle is
/// never smaller than the source <see cref="RectangleF"/>
/// Inflates this <see cref="RectangleF"/> by the specified amount.
/// </summary>
/// <param name="source">The source area to round out</param>
/// <returns>
/// The smallest <see cref="Rectangle"/> that the <see cref="RectangleF"/> will fit inside.
/// </returns>
public static Rectangle Ceiling(RectangleF source)
/// <param name="width">The width</param>
/// <param name="height">The height</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Inflate(float width, float height)
{
int y = (int)Math.Floor(source.Y);
int width = (int)Math.Ceiling(source.Width);
int x = (int)Math.Floor(source.X);
int height = (int)Math.Ceiling(source.Height);
return new Rectangle(x, y, width, height);
this.X -= width;
this.Y -= height;
this.Width += 2 * width;
this.Height += 2 * height;
}
/// <summary>
/// Outsets the specified region.
/// Inflates this <see cref="RectangleF"/> by the specified amount.
/// </summary>
/// <param name="region">The region.</param>
/// <param name="width">The width.</param>
/// <returns>
/// The <see cref="RectangleF"/> with all dimensions move away from the center by the offset.
/// </returns>
public static RectangleF Outset(RectangleF region, float width)
{
float dblWidth = width * 2;
return new RectangleF(region.X - width, region.Y - width, region.Width + dblWidth, region.Height + dblWidth);
}
/// <param name="size">The size</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Inflate(SizeF size) => this.Inflate(size.Width, size.Height);
/// <summary>
/// Determines if the specfied point is contained within the rectangular region defined by
@ -260,75 +312,89 @@ namespace ImageSharp
/// <param name="x">The x-coordinate of the given point.</param>
/// <param name="y">The y-coordinate of the given point.</param>
/// <returns>The <see cref="bool"/></returns>
public bool Contains(float x, float y)
{
// TODO: SIMD?
return this.X <= x
&& x < this.Right
&& this.Y <= y
&& y < this.Bottom;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(float x, float y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom;
/// <summary>
/// Determines if the specified point is contained within the rectangular region defined by this <see cref="RectangleF"/> .
/// </summary>
/// <param name="point">The point</param>
/// <returns>The <see cref="bool"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(PointF point) => this.Contains(point.X, point.Y);
/// <summary>
/// Determines if the rectangular region represented by <paramref name="rectangle"/> is entirely contained
/// within the rectangular region represented by this <see cref="RectangleF"/> .
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="bool"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(RectangleF rectangle) =>
(this.X <= rectangle.X) && (rectangle.Right <= this.Right) &&
(this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom);
/// <summary>
/// Determines if the specfied <see cref="Rectangle"/> intersects the rectangular region defined by
/// this <see cref="Rectangle"/>.
/// Determines if the specfied <see cref="RectangleF"/> intersects the rectangular region defined by
/// this <see cref="RectangleF"/>.
/// </summary>
/// <param name="rect">The other Rectange </param>
/// <param name="rectangle">The other Rectange </param>
/// <returns>The <see cref="bool"/></returns>
public bool Intersects(RectangleF rect)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IntersectsWith(RectangleF rectangle) =>
(rectangle.X < this.Right) && (this.X < rectangle.Right) &&
(rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom);
/// <summary>
/// Adjusts the location of this rectangle by the specified amount.
/// </summary>
/// <param name="point">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(PointF point) => this.Offset(point.X, point.Y);
/// <summary>
/// Adjusts the location of this rectangle by the specified amount.
/// </summary>
/// <param name="dx">The amount to offset the x-coordinate.</param>
/// <param name="dy">The amount to offset the y-coordinate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(float dx, float dy)
{
return rect.Left <= this.Right && rect.Right >= this.Left
&&
rect.Top <= this.Bottom && rect.Bottom >= this.Top;
this.X += dx;
this.Y += dy;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Rectangle [ Empty ]";
return "RectangleF [ Empty ]";
}
return
$"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
return $"RectangleF [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is RectangleF)
{
return this.Equals((RectangleF)obj);
}
return false;
}
public override bool Equals(object obj) => obj is RectangleF && this.Equals((RectangleF)obj);
/// <inheritdoc/>
public bool Equals(RectangleF other)
{
return this.backingVector.Equals(other.backingVector);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(RectangleF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y) && this.Width.Equals(other.Width) && this.Height.Equals(other.Height);
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="rectangle">
/// The instance of <see cref="RectangleF"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(RectangleF rectangle)
{
return rectangle.backingVector.GetHashCode();
unchecked
{
int hashCode = rectangle.X.GetHashCode();
hashCode = (hashCode * 397) ^ rectangle.Y.GetHashCode();
hashCode = (hashCode * 397) ^ rectangle.Width.GetHashCode();
hashCode = (hashCode * 397) ^ rectangle.Height.GetHashCode();
return hashCode;
}
}
}
}
}

137
src/ImageSharp/Numerics/Size.cs

@ -23,6 +23,17 @@ namespace ImageSharp
/// </summary>
public static readonly Size Empty = default(Size);
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary>
/// <param name="value">The width and height of the size</param>
public Size(int value)
: this()
{
this.Width = value;
this.Height = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary>
@ -34,6 +45,27 @@ namespace ImageSharp
this.Height = height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary>
/// <param name="size">The size</param>
public Size(Size size)
: this()
{
this.Width = size.Width;
this.Height = size.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct from the given <see cref="Point"/>.
/// </summary>
/// <param name="point">The point</param>
public Size(Point point)
{
this.Width = point.X;
this.Height = point.Y;
}
/// <summary>
/// Gets or sets the width of this <see cref="Size"/>.
/// </summary>
@ -50,6 +82,20 @@ namespace ImageSharp
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Creates a <see cref="SizeF"/> with the dimensions of the specified <see cref="Size"/>.
/// </summary>
/// <param name="size">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator SizeF(Size size) => new SizeF(size.Width, size.Height);
/// <summary>
/// Converts the given <see cref="Size"/> into a <see cref="Point"/>.
/// </summary>
/// <param name="size">The size</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Point(Size size) => new Point(size.Width, size.Height);
/// <summary>
/// Computes the sum of adding two sizes.
/// </summary>
@ -59,10 +105,7 @@ namespace ImageSharp
/// The <see cref="Size"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size operator +(Size left, Size right)
{
return new Size(left.Width + right.Width, left.Height + right.Height);
}
public static Size operator +(Size left, Size right) => Add(left, right);
/// <summary>
/// Computes the difference left by subtracting one size from another.
@ -73,10 +116,7 @@ namespace ImageSharp
/// The <see cref="Size"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size operator -(Size left, Size right)
{
return new Size(left.Width - right.Width, left.Height - right.Height);
}
public static Size operator -(Size left, Size right) => Subtract(left, right);
/// <summary>
/// Compares two <see cref="Size"/> objects for equality.
@ -91,10 +131,7 @@ namespace ImageSharp
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Size left, Size right)
{
return left.Equals(right);
}
public static bool operator ==(Size left, Size right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Size"/> objects for inequality.
@ -109,16 +146,52 @@ namespace ImageSharp
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Size left, Size right)
{
return !left.Equals(right);
}
public static bool operator !=(Size left, Size right) => !left.Equals(right);
/// <summary>
/// Performs vector addition of two <see cref="Size"/> objects.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>The <see cref="Size"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size Add(Size left, Size right) => new Size(unchecked(left.Width + right.Width), unchecked(left.Height + right.Height));
/// <summary>
/// Contracts a <see cref="Size"/> by another <see cref="Size"/>
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>The <see cref="Size"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size Subtract(Size left, Size right) => new Size(unchecked(left.Width - right.Width), unchecked(left.Height - right.Height));
/// <summary>
/// Converts a <see cref="SizeF"/> to a <see cref="Size"/> by performing a ceiling operation on all the dimensions.
/// </summary>
/// <param name="size">The size</param>
/// <returns>The <see cref="Size"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size Ceiling(SizeF size) => new Size(unchecked((int)MathF.Ceiling(size.Width)), unchecked((int)MathF.Ceiling(size.Height)));
/// <summary>
/// Converts a <see cref="SizeF"/> to a <see cref="Size"/> by performing a round operation on all the dimensions.
/// </summary>
/// <param name="size">The size</param>
/// <returns>The <see cref="Size"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size Round(SizeF size) => new Size(unchecked((int)MathF.Round(size.Width)), unchecked((int)MathF.Round(size.Height)));
/// <summary>
/// Converts a <see cref="SizeF"/> to a <see cref="Size"/> by performing a round operation on all the dimensions.
/// </summary>
/// <param name="size">The size</param>
/// <returns>The <see cref="Size"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size Truncate(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height));
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
@ -132,22 +205,11 @@ namespace ImageSharp
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Size)
{
return this.Equals((Size)obj);
}
return false;
}
public override bool Equals(object obj) => obj is Size && this.Equals((Size)obj);
/// <inheritdoc/>
public bool Equals(Size other)
{
return this.Width == other.Width && this.Height == other.Height;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Size other) => this.Width == other.Width && this.Height == other.Height;
/// <summary>
/// Returns the hash code for this instance.
@ -158,9 +220,6 @@ namespace ImageSharp
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(Size size)
{
return size.Width ^ size.Height;
}
private int GetHashCode(Size size) => size.Width ^ size.Height;
}
}
}

179
src/ImageSharp/Numerics/SizeF.cs

@ -0,0 +1,179 @@
// <copyright file="SizeF.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
/// <summary>
/// Stores an ordered pair of single precision floating points, which specify a height and width.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public struct SizeF : IEquatable<SizeF>
{
/// <summary>
/// Represents a <see cref="SizeF"/> that has Width and Height values set to zero.
/// </summary>
public static readonly SizeF Empty = default(SizeF);
/// <summary>
/// Initializes a new instance of the <see cref="SizeF"/> struct.
/// </summary>
/// <param name="width">The width of the size.</param>
/// <param name="height">The height of the size.</param>
public SizeF(float width, float height)
{
this.Width = width;
this.Height = height;
}
/// <summary>
/// Initializes a new instance of the <see cref="SizeF"/> struct.
/// </summary>
/// <param name="size">The size</param>
public SizeF(SizeF size)
: this()
{
this.Width = size.Width;
this.Height = size.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="SizeF"/> struct from the given <see cref="PointF"/>.
/// </summary>
/// <param name="point">The point</param>
public SizeF(PointF point)
{
this.Width = point.X;
this.Height = point.Y;
}
/// <summary>
/// Gets or sets the width of this <see cref="SizeF"/>.
/// </summary>
public float Width { get; set; }
/// <summary>
/// Gets or sets the height of this <see cref="SizeF"/>.
/// </summary>
public float Height { get; set; }
/// <summary>
/// Gets a value indicating whether this <see cref="SizeF"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Creates a <see cref="Size"/> with the dimensions of the specified <see cref="SizeF"/> by truncating each of the dimensions.
/// </summary>
/// <param name="size">The size.</param>
/// <returns>
/// The <see cref="Size"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Size(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height));
/// <summary>
/// Converts the given <see cref="SizeF"/> into a <see cref="PointF"/>.
/// </summary>
/// <param name="size">The size</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator PointF(SizeF size) => new PointF(size.Width, size.Height);
/// <summary>
/// Computes the sum of adding two sizes.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// The <see cref="SizeF"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SizeF operator +(SizeF left, SizeF right) => Add(left, right);
/// <summary>
/// Computes the difference left by subtracting one size from another.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// The <see cref="SizeF"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SizeF operator -(SizeF left, SizeF right) => Subtract(left, right);
/// <summary>
/// Compares two <see cref="SizeF"/> objects for equality.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(SizeF left, SizeF right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="SizeF"/> objects for inequality.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(SizeF left, SizeF right) => !left.Equals(right);
/// <summary>
/// Performs vector addition of two <see cref="SizeF"/> objects.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>The <see cref="SizeF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SizeF Add(SizeF left, SizeF right) => new SizeF(left.Width + right.Width, left.Height + right.Height);
/// <summary>
/// Contracts a <see cref="SizeF"/> by another <see cref="SizeF"/>
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>The <see cref="SizeF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SizeF Subtract(SizeF left, SizeF right) => new SizeF(left.Width - right.Width, left.Height - right.Height);
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "SizeF [ Empty ]";
}
return $"SizeF [ Width={this.Width}, Height={this.Height} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj) => obj is SizeF && this.Equals((SizeF)obj);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(SizeF other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height);
private int GetHashCode(SizeF size) => size.Width.GetHashCode() ^ size.Height.GetHashCode();
}
}

2
src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs

@ -52,7 +52,7 @@ namespace ImageSharp.Processing.Processors
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
TPixel glowColor = this.GlowColor;
var centre = Rectangle.Center(sourceRectangle).ToVector2();
Vector2 centre = Rectangle.Center(sourceRectangle);
float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F;
// Align start/end positions.

2
src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs

@ -58,7 +58,7 @@ namespace ImageSharp.Processing.Processors
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
TPixel vignetteColor = this.VignetteColor;
var centre = Rectangle.Center(sourceRectangle).ToVector2();
Vector2 centre = Rectangle.Center(sourceRectangle);
float rX = this.RadiusX > 0 ? MathF.Min(this.RadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F;
float rY = this.RadiusY > 0 ? MathF.Min(this.RadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F;
float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY));

2
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);

2
src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs

@ -73,7 +73,7 @@ namespace ImageSharp.Processing.Processors
/// <inheritdoc/>
protected override void BeforeApply(ImageBase<TPixel> 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);

2
tests/ImageSharp.Benchmarks/Samplers/Glow.cs

@ -79,7 +79,7 @@ namespace ImageSharp.Benchmarks
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
TPixel glowColor = this.GlowColor;
Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2();
Vector2 centre = Rectangle.Center(sourceRectangle);
float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F;
// Align start/end positions.

192
tests/ImageSharp.Tests/Numerics/PointFTests.cs

@ -0,0 +1,192 @@
// <copyright file="PointFTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Numerics
{
using System;
using System.Globalization;
using System.Numerics;
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));
}
[Fact]
public void RotateTest()
{
var p = new PointF(13, 17);
Matrix3x2 matrix = Matrix3x2Extensions.CreateRotation(45, PointF.Empty);
var pout = PointF.Rotate(p, matrix);
Assert.Equal(new PointF(-2.82842732F, 21.2132034F), pout);
}
[Fact]
public void SkewTest()
{
var p = new PointF(13, 17);
Matrix3x2 matrix = Matrix3x2Extensions.CreateSkew(45, 45, PointF.Empty);
var pout = PointF.Skew(p, matrix);
Assert.Equal(new PointF(30, 30), pout);
}
[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<PointF> (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<PointF>).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());
}
}
}

250
tests/ImageSharp.Tests/Numerics/PointTests.cs

@ -5,46 +5,248 @@
namespace ImageSharp.Tests
{
using System.Globalization;
using System.Numerics;
using Xunit;
/// <summary>
/// Tests the <see cref="Point"/> struct.
/// </summary>
public class PointTests
{
/// <summary>
/// Tests the equality operators for equality.
/// </summary>
[Fact]
public void AreEqual()
public void DefaultConstructorTest()
{
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(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);
}
[Fact]
public void RotateTest()
{
var p = new Point(13, 17);
Matrix3x2 matrix = Matrix3x2Extensions.CreateRotation(45, Point.Empty);
var pout = Point.Rotate(p, matrix);
Assert.Equal(new Point(-3, 21), pout);
}
[Fact]
public void SkewTest()
{
Point first = new Point(100, 100);
Point second = new Point(100, 100);
var p = new Point(13, 17);
Matrix3x2 matrix = Matrix3x2Extensions.CreateSkew(45, 45, Point.Empty);
Assert.Equal(first, second);
var pout = Point.Skew(p, matrix);
Assert.Equal(new Point(30, 30), pout);
}
[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());
}
/// <summary>
/// Tests the equality operators for inequality.
/// </summary>
[Fact]
public void AreNotEqual()
public static void EqualityTest_NotPoint()
{
Point first = new Point(0, 100);
Point second = new Point(100, 100);
var point = new Point(0, 0);
Assert.False(point.Equals(null));
Assert.False(point.Equals(0));
Assert.False(point.Equals(new PointF(0, 0)));
}
Assert.NotEqual(first, second);
[Fact]
public static void GetHashCodeTest()
{
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());
}
[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());
}
/// <summary>
/// Tests whether the point constructor correctly assign properties.
/// </summary>
[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());
}
}
}

267
tests/ImageSharp.Tests/Numerics/RectangleFTests.cs

@ -0,0 +1,267 @@
// <copyright file="RectangleFTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests
{
using System;
using System.Globalization;
using System.Reflection;
using Xunit;
/// <summary>
/// Tests the <see cref="RectangleF"/> struct.
/// </summary>
public class RectangleFTests
{
[Fact]
public void DefaultConstructorTest()
{
Assert.Equal(RectangleF.Empty, new RectangleF());
}
[Theory]
[InlineData(0, 0, 0, 0)]
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)]
[InlineData(float.MaxValue, 0, 0, float.MaxValue)]
[InlineData(0, float.MinValue, float.MaxValue, 0)]
public void NonDefaultConstructorTest(float x, float y, float width, float height)
{
var rect1 = new RectangleF(x, y, width, height);
var p = new PointF(x, y);
var s = new SizeF(width, height);
var rect2 = new RectangleF(p, s);
Assert.Equal(rect1, rect2);
}
[Theory]
[InlineData(0, 0, 0, 0)]
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)]
[InlineData(float.MaxValue, 0, 0, float.MaxValue)]
[InlineData(0, float.MinValue, float.MaxValue, 0)]
public void FromLTRBTest(float left, float top, float right, float bottom)
{
var expected = new RectangleF(left, top, right - left, bottom - top);
var actual = RectangleF.FromLTRB(left, top, right, bottom);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(0, 0, 0, 0)]
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)]
[InlineData(float.MaxValue, 0, 0, float.MaxValue)]
[InlineData(0, float.MinValue, float.MaxValue, 0)]
public void DimensionsTest(float x, float y, float width, float height)
{
var rect = new RectangleF(x, y, width, height);
var p = new PointF(x, y);
var s = new SizeF(width, height);
Assert.Equal(p, rect.Location);
Assert.Equal(s, rect.Size);
Assert.Equal(x, rect.X);
Assert.Equal(y, rect.Y);
Assert.Equal(width, rect.Width);
Assert.Equal(height, rect.Height);
Assert.Equal(x, rect.Left);
Assert.Equal(y, rect.Top);
Assert.Equal(x + width, rect.Right);
Assert.Equal(y + height, rect.Bottom);
}
[Fact]
public void IsEmptyTest()
{
Assert.True(RectangleF.Empty.IsEmpty);
Assert.True(new RectangleF().IsEmpty);
Assert.True(new RectangleF(1, -2, -10, 10).IsEmpty);
Assert.True(new RectangleF(1, -2, 10, -10).IsEmpty);
Assert.True(new RectangleF(1, -2, 0, 0).IsEmpty);
Assert.False(new RectangleF(0, 0, 10, 10).IsEmpty);
}
[Theory]
[InlineData(0, 0)]
[InlineData(float.MaxValue, float.MinValue)]
public static void LocationSetTest(float x, float y)
{
var point = new PointF(x, y);
var rect = new RectangleF(10, 10, 10, 10) { Location = point };
Assert.Equal(point, rect.Location);
Assert.Equal(point.X, rect.X);
Assert.Equal(point.Y, rect.Y);
}
[Theory]
[InlineData(0, 0)]
[InlineData(float.MaxValue, float.MinValue)]
public static void SizeSetTest(float x, float y)
{
var size = new SizeF(x, y);
var rect = new RectangleF(10, 10, 10, 10) { Size = size };
Assert.Equal(size, rect.Size);
Assert.Equal(size.Width, rect.Width);
Assert.Equal(size.Height, rect.Height);
}
[Theory]
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)]
[InlineData(float.MaxValue, 0, 0, float.MaxValue)]
[InlineData(0, float.MinValue, float.MaxValue, 0)]
public void EqualityTest(float x, float y, float width, float height)
{
var rect1 = new RectangleF(x, y, width, height);
var rect2 = new RectangleF(width, height, x, y);
Assert.True(rect1 != rect2);
Assert.False(rect1 == rect2);
Assert.False(rect1.Equals(rect2));
Assert.False(rect1.Equals((object)rect2));
}
[Fact]
public static void EqualityTestNotRectangleF()
{
var rectangle = new RectangleF(0, 0, 0, 0);
Assert.False(rectangle.Equals(null));
Assert.False(rectangle.Equals(0));
// If RectangleF implements IEquatable<RectangleF> (e.g. in .NET Core), then classes that are implicitly
// convertible to RectangleF can potentially be equal.
// See https://github.com/dotnet/corefx/issues/5255.
bool expectsImplicitCastToRectangleF = typeof(IEquatable<RectangleF>).IsAssignableFrom(rectangle.GetType());
Assert.Equal(expectsImplicitCastToRectangleF, rectangle.Equals(new Rectangle(0, 0, 0, 0)));
Assert.False(rectangle.Equals((object)new Rectangle(0, 0, 0, 0))); // No implicit cast
}
[Fact]
public static void GetHashCodeTest()
{
var rect1 = new RectangleF(10, 10, 10, 10);
var rect2 = new RectangleF(10, 10, 10, 10);
Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode());
Assert.NotEqual(rect1.GetHashCode(), new RectangleF(20, 10, 10, 10).GetHashCode());
Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 20, 10, 10).GetHashCode());
Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 20, 10).GetHashCode());
Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 10, 20).GetHashCode());
}
[Theory]
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)]
[InlineData(0, float.MinValue, float.MaxValue, 0)]
public void ContainsTest(float x, float y, float width, float height)
{
var rect = new RectangleF(x, y, width, height);
float X = (x + width) / 2;
float Y = (y + height) / 2;
var p = new PointF(X, Y);
var r = new RectangleF(X, Y, width / 2, height / 2);
Assert.False(rect.Contains(X, Y));
Assert.False(rect.Contains(p));
Assert.False(rect.Contains(r));
}
[Theory]
[InlineData(0, 0, 0, 0)]
[InlineData(float.MaxValue / 2, float.MinValue / 2, float.MinValue / 2, float.MaxValue / 2)]
[InlineData(0, float.MinValue, float.MaxValue, 0)]
public void InflateTest(float x, float y, float width, float height)
{
var rect = new RectangleF(x, y, width, height);
var inflatedRect = new RectangleF(x - width, y - height, width + 2 * width, height + 2 * height);
rect.Inflate(width, height);
Assert.Equal(inflatedRect, rect);
var s = new SizeF(x, y);
inflatedRect = RectangleF.Inflate(rect, x, y);
rect.Inflate(s);
Assert.Equal(inflatedRect, rect);
}
[Theory]
[InlineData(float.MaxValue, float.MinValue, float.MaxValue / 2, float.MinValue / 2)]
[InlineData(0, float.MinValue, float.MaxValue, 0)]
public void IntersectTest(float x, float y, float width, float height)
{
var rect1 = new RectangleF(x, y, width, height);
var rect2 = new RectangleF(y, x, width, height);
var expectedRect = RectangleF.Intersect(rect1, rect2);
rect1.Intersect(rect2);
Assert.Equal(expectedRect, rect1);
Assert.False(rect1.IntersectsWith(expectedRect));
}
[Fact]
public static void IntersectIntersectingRectsTest()
{
var rect1 = new RectangleF(0, 0, 5, 5);
var rect2 = new RectangleF(1, 1, 3, 3);
var expected = new RectangleF(1, 1, 3, 3);
Assert.Equal(expected, RectangleF.Intersect(rect1, rect2));
}
[Theory]
[InlineData(0, 0, 0, 0)]
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)]
[InlineData(float.MaxValue, 0, 0, float.MaxValue)]
[InlineData(0, float.MinValue, float.MaxValue, 0)]
public void UnionTest(float x, float y, float width, float height)
{
var a = new RectangleF(x, y, width, height);
var b = new RectangleF(width, height, x, y);
float x1 = Math.Min(a.X, b.X);
float x2 = Math.Max(a.X + a.Width, b.X + b.Width);
float y1 = Math.Min(a.Y, b.Y);
float y2 = Math.Max(a.Y + a.Height, b.Y + b.Height);
var expectedRectangle = new RectangleF(x1, y1, x2 - x1, y2 - y1);
Assert.Equal(expectedRectangle, RectangleF.Union(a, b));
}
[Theory]
[InlineData(0, 0, 0, 0)]
[InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)]
[InlineData(float.MaxValue, 0, 0, float.MaxValue)]
[InlineData(0, float.MinValue, float.MaxValue, 0)]
public void OffsetTest(float x, float y, float width, float height)
{
var r1 = new RectangleF(x, y, width, height);
var expectedRect = new RectangleF(x + width, y + height, width, height);
var p = new PointF(width, height);
r1.Offset(p);
Assert.Equal(expectedRect, r1);
expectedRect.Offset(p);
r1.Offset(width, height);
Assert.Equal(expectedRect, r1);
}
[Fact]
public void ToStringTest()
{
var r = new RectangleF(5, 5.1F, 1.3F, 1);
Assert.Equal(string.Format(CultureInfo.CurrentCulture, "RectangleF [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString());
}
[InlineData(0, 0, 0, 0)]
[InlineData(5, -5, 0.2, -1.3)]
public void ToStringTestEmpty(float x, float y, float width, float height)
{
var r = new RectangleF(x, y, width, height);
Assert.Equal("RectangleF [ Empty ]", r.ToString());
}
}
}

318
tests/ImageSharp.Tests/Numerics/RectangleTests.cs

@ -5,6 +5,9 @@
namespace ImageSharp.Tests
{
using System;
using System.Globalization;
using Xunit;
/// <summary>
@ -12,55 +15,294 @@ namespace ImageSharp.Tests
/// </summary>
public class RectangleTests
{
/// <summary>
/// Tests the equality operators for equality.
/// </summary>
[Fact]
public void AreEqual()
public void DefaultConstructorTest()
{
Assert.Equal(Rectangle.Empty, new Rectangle());
}
[Theory]
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)]
[InlineData(int.MaxValue, 0, int.MinValue, 0)]
[InlineData(0, 0, 0, 0)]
[InlineData(0, int.MinValue, 0, int.MaxValue)]
public void NonDefaultConstructorTest(int x, int y, int width, int height)
{
var rect1 = new Rectangle(x, y, width, height);
var rect2 = new Rectangle(new Point(x, y), new Size(width, height));
Assert.Equal(rect1, rect2);
}
[Theory]
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)]
[InlineData(int.MaxValue, 0, int.MinValue, 0)]
[InlineData(0, 0, 0, 0)]
[InlineData(0, int.MinValue, 0, int.MaxValue)]
public void FromLTRBTest(int left, int top, int right, int bottom)
{
Rectangle first = new Rectangle(1, 1, 100, 100);
Rectangle second = new Rectangle(1, 1, 100, 100);
var rect1 = new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top));
var rect2 = Rectangle.FromLTRB(left, top, right, bottom);
Assert.Equal(first, second);
Assert.Equal(rect1, rect2);
}
/// <summary>
/// Tests the equality operators for inequality.
/// </summary>
[Fact]
public void AreNotEqual()
public void EmptyTest()
{
Assert.True(Rectangle.Empty.IsEmpty);
Assert.True(new Rectangle(0, 0, 0, 0).IsEmpty);
Assert.True(new Rectangle().IsEmpty);
}
[Theory]
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)]
[InlineData(int.MaxValue, 0, int.MinValue, 0)]
[InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)]
[InlineData(0, int.MinValue, 0, int.MaxValue)]
public void NonEmptyTest(int x, int y, int width, int height)
{
Assert.False(new Rectangle(x, y, width, height).IsEmpty);
}
[Theory]
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)]
[InlineData(int.MaxValue, 0, int.MinValue, 0)]
[InlineData(0, 0, 0, 0)]
[InlineData(0, int.MinValue, 0, int.MaxValue)]
[InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)]
public void DimensionsTest(int x, int y, int width, int height)
{
var rect = new Rectangle(x, y, width, height);
Assert.Equal(new Point(x, y), rect.Location);
Assert.Equal(new Size(width, height), rect.Size);
Assert.Equal(x, rect.X);
Assert.Equal(y, rect.Y);
Assert.Equal(width, rect.Width);
Assert.Equal(height, rect.Height);
Assert.Equal(x, rect.Left);
Assert.Equal(y, rect.Top);
Assert.Equal(unchecked(x + width), rect.Right);
Assert.Equal(unchecked(y + height), rect.Bottom);
var p = new Point(width, height);
var s = new Size(x, y);
rect.Location = p;
rect.Size = s;
Assert.Equal(p, rect.Location);
Assert.Equal(s, rect.Size);
Assert.Equal(width, rect.X);
Assert.Equal(height, rect.Y);
Assert.Equal(x, rect.Width);
Assert.Equal(y, rect.Height);
Assert.Equal(width, rect.Left);
Assert.Equal(height, rect.Top);
Assert.Equal(unchecked(x + width), rect.Right);
Assert.Equal(unchecked(y + height), rect.Bottom);
}
[Theory]
[InlineData(0, 0)]
[InlineData(int.MaxValue, int.MinValue)]
public static void LocationSetTest(int x, int y)
{
var point = new Point(x, y);
var rect = new Rectangle(10, 10, 10, 10) { Location = point };
Assert.Equal(point, rect.Location);
Assert.Equal(point.X, rect.X);
Assert.Equal(point.Y, rect.Y);
}
[Theory]
[InlineData(0, 0)]
[InlineData(int.MaxValue, int.MinValue)]
public static void SizeSetTest(int x, int y)
{
Rectangle first = new Rectangle(1, 1, 0, 100);
Rectangle second = new Rectangle(1, 1, 100, 100);
var size = new Size(x, y);
var rect = new Rectangle(10, 10, 10, 10) { Size = size };
Assert.Equal(size, rect.Size);
Assert.Equal(size.Width, rect.Width);
Assert.Equal(size.Height, rect.Height);
}
[Theory]
[InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)]
[InlineData(int.MaxValue, 0, int.MinValue, 0)]
[InlineData(0, int.MinValue, 0, int.MaxValue)]
[InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)]
public void EqualityTest(int x, int y, int width, int height)
{
var rect1 = new Rectangle(x, y, width, height);
var rect2 = new Rectangle(width / 2, height / 2, x, y);
Assert.NotEqual(first, second);
Assert.True(rect1 != rect2);
Assert.False(rect1 == rect2);
Assert.False(rect1.Equals(rect2));
Assert.False(rect1.Equals((object)rect2));
}
/// <summary>
/// Tests whether the rectangle constructors correctly assign properties.
/// </summary>
[Fact]
public void ConstructorAssignsProperties()
{
Rectangle first = new Rectangle(1, 1, 50, 100);
Assert.Equal(1, first.X);
Assert.Equal(1, first.Y);
Assert.Equal(50, first.Width);
Assert.Equal(100, first.Height);
Assert.Equal(1, first.Top);
Assert.Equal(51, first.Right);
Assert.Equal(101, first.Bottom);
Assert.Equal(1, first.Left);
Rectangle second = new Rectangle(new Point(1, 1), new Size(50, 100));
Assert.Equal(1, second.X);
Assert.Equal(1, second.Y);
Assert.Equal(50, second.Width);
Assert.Equal(100, second.Height);
Assert.Equal(1, second.Top);
Assert.Equal(51, second.Right);
Assert.Equal(101, second.Bottom);
Assert.Equal(1, second.Left);
public static void EqualityTestNotRectangle()
{
var rectangle = new Rectangle(0, 0, 0, 0);
Assert.False(rectangle.Equals(null));
Assert.False(rectangle.Equals(0));
Assert.False(rectangle.Equals(new RectangleF(0, 0, 0, 0)));
}
[Fact]
public static void GetHashCodeTest()
{
var rect1 = new Rectangle(10, 10, 10, 10);
var rect2 = new Rectangle(10, 10, 10, 10);
Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode());
Assert.NotEqual(rect1.GetHashCode(), new Rectangle(20, 10, 10, 10).GetHashCode());
Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 20, 10, 10).GetHashCode());
Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 20, 10).GetHashCode());
Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 10, 20).GetHashCode());
}
[Theory]
[InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)]
[InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)]
[InlineData(0, 0, 0, 0)]
public void RectangleFConversionTest(float x, float y, float width, float height)
{
var rect = new RectangleF(x, y, width, height);
Rectangle rCeiling, rTruncate, rRound;
unchecked
{
rCeiling = new Rectangle((int)Math.Ceiling(x), (int)Math.Ceiling(y),
(int)Math.Ceiling(width), (int)Math.Ceiling(height));
rTruncate = new Rectangle((int)x, (int)y, (int)width, (int)height);
rRound = new Rectangle((int)Math.Round(x), (int)Math.Round(y),
(int)Math.Round(width), (int)Math.Round(height));
}
Assert.Equal(rCeiling, Rectangle.Ceiling(rect));
Assert.Equal(rTruncate, Rectangle.Truncate(rect));
Assert.Equal(rRound, Rectangle.Round(rect));
}
[Theory]
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)]
[InlineData(0, int.MinValue, int.MaxValue, 0)]
public void ContainsTest(int x, int y, int width, int height)
{
var rect = new Rectangle(unchecked(2 * x - width), unchecked(2 * y - height), width, height);
var p = new Point(x, y);
var r = new Rectangle(x, y, width / 2, height / 2);
Assert.False(rect.Contains(x, y));
Assert.False(rect.Contains(p));
Assert.False(rect.Contains(r));
}
[Theory]
[InlineData(0, 0, 0, 0)]
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)]
[InlineData(0, int.MinValue, int.MaxValue, 0)]
public void InflateTest(int x, int y, int width, int height)
{
Rectangle inflatedRect, rect = new Rectangle(x, y, width, height);
unchecked
{
inflatedRect = new Rectangle(x - width, y - height, width + 2 * width, height + 2 * height);
}
Assert.Equal(inflatedRect, Rectangle.Inflate(rect, width, height));
rect.Inflate(width, height);
Assert.Equal(inflatedRect, rect);
var s = new Size(x, y);
unchecked
{
inflatedRect = new Rectangle(rect.X - x, rect.Y - y, rect.Width + 2 * x, rect.Height + 2 * y);
}
rect.Inflate(s);
Assert.Equal(inflatedRect, rect);
}
[Theory]
[InlineData(0, 0, 0, 0)]
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)]
[InlineData(0, int.MinValue, int.MaxValue, 0)]
public void IntersectTest(int x, int y, int width, int height)
{
var rect = new Rectangle(x, y, width, height);
var expectedRect = Rectangle.Intersect(rect, rect);
rect.Intersect(rect);
Assert.Equal(expectedRect, rect);
Assert.False(rect.IntersectsWith(expectedRect));
}
[Fact]
public static void IntersectIntersectingRectsTest()
{
var rect1 = new Rectangle(0, 0, 5, 5);
var rect2 = new Rectangle(1, 1, 3, 3);
var expected = new Rectangle(1, 1, 3, 3);
Assert.Equal(expected, Rectangle.Intersect(rect1, rect2));
}
[Theory]
[InlineData(0, 0, 0, 0)]
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)]
[InlineData(int.MaxValue, 0, 0, int.MaxValue)]
[InlineData(0, int.MinValue, int.MaxValue, 0)]
public void UnionTest(int x, int y, int width, int height)
{
var a = new Rectangle(x, y, width, height);
var b = new Rectangle(width, height, x, y);
int x1 = Math.Min(a.X, b.X);
int x2 = Math.Max(a.X + a.Width, b.X + b.Width);
int y1 = Math.Min(a.Y, b.Y);
int y2 = Math.Max(a.Y + a.Height, b.Y + b.Height);
var expectedRectangle = new Rectangle(x1, y1, x2 - x1, y2 - y1);
Assert.Equal(expectedRectangle, Rectangle.Union(a, b));
}
[Theory]
[InlineData(0, 0, 0, 0)]
[InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)]
[InlineData(int.MaxValue, 0, 0, int.MaxValue)]
[InlineData(0, int.MinValue, int.MaxValue, 0)]
public void OffsetTest(int x, int y, int width, int height)
{
var r1 = new Rectangle(x, y, width, height);
var expectedRect = new Rectangle(x + width, y + height, width, height);
var p = new Point(width, height);
r1.Offset(p);
Assert.Equal(expectedRect, r1);
expectedRect.Offset(p);
r1.Offset(width, height);
Assert.Equal(expectedRect, r1);
}
[Fact]
public void ToStringTest()
{
var r = new Rectangle(5, -5, 0, 1);
Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Rectangle [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString());
}
[Fact]
public void ToStringTestEmpty()
{
var r = new Rectangle(0, 0, 0, 0);
Assert.Equal("Rectangle [ Empty ]", r.ToString());
}
}
}

163
tests/ImageSharp.Tests/Numerics/SizeFTests.cs

@ -0,0 +1,163 @@
// <copyright file="SizeTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
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<SizeF> (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<SizeF>).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());
}
}
}

187
tests/ImageSharp.Tests/Numerics/SizeTests.cs

@ -5,6 +5,7 @@
namespace ImageSharp.Tests
{
using System.Globalization;
using Xunit;
/// <summary>
@ -12,39 +13,183 @@ namespace ImageSharp.Tests
/// </summary>
public class SizeTests
{
/// <summary>
/// Tests the equality operators for equality.
/// </summary>
[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);
}
/// <summary>
/// Tests the equality operators for inequality.
/// </summary>
[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());
}
/// <summary>
/// Tests whether the size constructor correctly assign properties.
/// </summary>
[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());
}
}
}
Loading…
Cancel
Save