// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace SixLabors.Primitives
{
///
/// Stores a set of four integers that represent the location and size of a rectangle.
///
///
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
/// as it avoids the need to create new values for modification operations.
///
public struct Rectangle : IEquatable
{
///
/// Represents a that has X, Y, Width, and Height values set to zero.
///
public static readonly Rectangle Empty = default(Rectangle);
///
/// Initializes a new instance of the struct.
///
/// The horizontal position of the rectangle.
/// The vertical position of the rectangle.
/// The width of the rectangle.
/// The height of the rectangle.
public Rectangle(int x, int y, int width, int height)
{
this.X = x;
this.Y = y;
this.Width = width;
this.Height = height;
}
///
/// Initializes a new instance of the struct.
///
///
/// The which specifies the rectangles point in a two-dimensional plane.
///
///
/// The which specifies the rectangles height and width.
///
public Rectangle(Point point, Size size)
{
this.X = point.X;
this.Y = point.Y;
this.Width = size.Width;
this.Height = size.Height;
}
///
/// Gets or sets the x-coordinate of this .
///
public int X { get; set; }
///
/// Gets or sets the y-coordinate of this .
///
public int Y { get; set; }
///
/// Gets or sets the width of this .
///
public int Width { get; set; }
///
/// Gets or sets the height of this .
///
public int Height { get; set; }
///
/// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this .
///
[EditorBrowsable(EditorBrowsableState.Never)]
public Point Location
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Point(this.X, this.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.X = value.X;
this.Y = value.Y;
}
}
///
/// Gets or sets the size of this .
///
[EditorBrowsable(EditorBrowsableState.Never)]
public Size Size
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Size(this.Width, this.Height);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.Width = value.Width;
this.Height = value.Height;
}
}
///
/// Gets a value indicating whether this is empty.
///
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
///
/// Gets the y-coordinate of the top edge of this .
///
public int Top => this.Y;
///
/// Gets the x-coordinate of the right edge of this .
///
public int Right
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => unchecked(this.X + this.Width);
}
///
/// Gets the y-coordinate of the bottom edge of this .
///
public int Bottom
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => unchecked(this.Y + this.Height);
}
///
/// Gets the x-coordinate of the left edge of this .
///
public int Left => this.X;
///
/// Creates a with the coordinates of the specified .
///
/// The rectangle
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator RectangleF(Rectangle rectangle) => new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
///
/// Creates a with the coordinates of the specified .
///
/// The rectangle
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Vector4(Rectangle rectangle) => new Vector4(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
///
/// Compares two objects for equality.
///
/// The on the left side of the operand.
/// The on the right side of the operand.
///
/// True if the current left is equal to the parameter; otherwise, false.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Rectangle left, Rectangle right) => left.Equals(right);
///
/// Compares two objects for inequality.
///
/// The on the left side of the operand.
/// The on the right side of the operand.
///
/// True if the current left is unequal to the parameter; otherwise, false.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Rectangle left, Rectangle right) => !left.Equals(right);
///
/// Creates a new with the specified location and size.
/// The left coordinate of the rectangle
/// The top coordinate of the rectangle
/// The right coordinate of the rectangle
/// The bottom coordinate of the rectangle
/// The
[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));
///
/// Returns the center point of the given
///
/// The rectangle
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Center(Rectangle rectangle) => new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
///
/// Creates a rectangle that represents the intersection between and
/// . If there is no intersection, an empty rectangle is returned.
///
/// The first rectangle
/// The second rectangle
/// The
[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;
}
///
/// Creates a that is inflated by the specified amount.
///
/// The rectangle
/// The amount to inflate the width by
/// The amount to inflate the height by
/// A new
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Inflate(Rectangle rectangle, int x, int y)
{
Rectangle r = rectangle;
r.Inflate(x, y);
return r;
}
///
/// Converts a to a by performing a ceiling operation on all the coordinates.
///
/// The rectangle
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Ceiling(RectangleF rectangle)
{
unchecked
{
return new Rectangle(
(int)MathF.Ceiling(rectangle.X),
(int)MathF.Ceiling(rectangle.Y),
(int)MathF.Ceiling(rectangle.Width),
(int)MathF.Ceiling(rectangle.Height));
}
}
///
/// Transforms a rectangle by the given matrix.
///
/// The source rectangle.
/// The transformation matrix.
/// A transformed rectangle.
public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix)
{
PointF bottomRight = Point.Transform(new Point(rectangle.Right, rectangle.Bottom), matrix);
PointF topLeft = Point.Transform(rectangle.Location, matrix);
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
}
///
/// Converts a to a by performing a truncate operation on all the coordinates.
///
/// The rectangle
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Truncate(RectangleF rectangle)
{
unchecked
{
return new Rectangle(
(int)rectangle.X,
(int)rectangle.Y,
(int)rectangle.Width,
(int)rectangle.Height);
}
}
///
/// Converts a to a by performing a round operation on all the coordinates.
///
/// The rectangle
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Round(RectangleF rectangle)
{
unchecked
{
return new Rectangle(
(int)MathF.Round(rectangle.X),
(int)MathF.Round(rectangle.Y),
(int)MathF.Round(rectangle.Width),
(int)MathF.Round(rectangle.Height));
}
}
///
/// Creates a rectangle that represents the union between and .
///
/// The first rectangle
/// The second rectangle
/// The
[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);
}
///
/// Creates a Rectangle that represents the intersection between this Rectangle and the .
///
/// The rectangle
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Intersect(Rectangle rectangle)
{
Rectangle result = Intersect(rectangle, this);
this.X = result.X;
this.Y = result.Y;
this.Width = result.Width;
this.Height = result.Height;
}
///
/// Inflates this by the specified amount.
///
/// The width
/// The height
[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;
}
}
///
/// Inflates this by the specified amount.
///
/// The size
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Inflate(Size size) => this.Inflate(size.Width, size.Height);
///
/// Determines if the specfied point is contained within the rectangular region defined by
/// this .
///
/// The x-coordinate of the given point.
/// The y-coordinate of the given point.
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom;
///
/// Determines if the specified point is contained within the rectangular region defined by this .
///
/// The point
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(Point point) => this.Contains(point.X, point.Y);
///
/// Determines if the rectangular region represented by is entirely contained
/// within the rectangular region represented by this .
///
/// The rectangle
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(Rectangle rectangle) =>
(this.X <= rectangle.X) && (rectangle.Right <= this.Right) &&
(this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom);
///
/// Determines if the specfied intersects the rectangular region defined by
/// this .
///
/// The other Rectange
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IntersectsWith(Rectangle rectangle) =>
(rectangle.X < this.Right) && (this.X < rectangle.Right) &&
(rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom);
///
/// Adjusts the location of this rectangle by the specified amount.
///
/// The point
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(Point point) => this.Offset(point.X, point.Y);
///
/// Adjusts the location of this rectangle by the specified amount.
///
/// The amount to offset the x-coordinate.
/// The amount to offset the y-coordinate.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(int dx, int dy)
{
unchecked
{
this.X += dx;
this.Y += dy;
}
}
///
public override int GetHashCode()
{
return HashHelpers.Combine(
this.X.GetHashCode(),
this.Y.GetHashCode(),
this.Width.GetHashCode(),
this.Height.GetHashCode());
}
///
public override string ToString()
{
return $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
}
///
public override bool Equals(object obj) => obj is Rectangle other && this.Equals(other);
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Rectangle other) => this.X == other.X && this.Y == other.Y && this.Width == other.Width && this.Height == other.Height;
}
}