// 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; } }