diff options
Diffstat (limited to 'gfx/2d/BaseRect.h')
-rw-r--r-- | gfx/2d/BaseRect.h | 751 |
1 files changed, 751 insertions, 0 deletions
diff --git a/gfx/2d/BaseRect.h b/gfx/2d/BaseRect.h new file mode 100644 index 0000000000..70d82050b3 --- /dev/null +++ b/gfx/2d/BaseRect.h @@ -0,0 +1,751 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASERECT_H_ +#define MOZILLA_GFX_BASERECT_H_ + +#include <algorithm> +#include <cmath> +#include <ostream> +#include <type_traits> + +#include "mozilla/Assertions.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/gfx/ScaleFactors2D.h" +#include "Types.h" + +namespace mozilla::gfx { + +/** + * Rectangles have two interpretations: a set of (zero-size) points, + * and a rectangular area of the plane. Most rectangle operations behave + * the same no matter what interpretation is being used, but some operations + * differ: + * -- Equality tests behave differently. When a rectangle represents an area, + * all zero-width and zero-height rectangles are equal to each other since they + * represent the empty area. But when a rectangle represents a set of + * mathematical points, zero-width and zero-height rectangles can be unequal. + * -- The union operation can behave differently. When rectangles represent + * areas, taking the union of a zero-width or zero-height rectangle with + * another rectangle can just ignore the empty rectangle. But when rectangles + * represent sets of mathematical points, we may need to extend the latter + * rectangle to include the points of a zero-width or zero-height rectangle. + * + * To ensure that these interpretations are explicitly disambiguated, we + * deny access to the == and != operators and require use of IsEqualEdges and + * IsEqualInterior instead. Similarly we provide separate Union and UnionEdges + * methods. + * + * Do not use this class directly. Subclass it, pass that subclass as the + * Sub parameter, and only use that subclass. + */ +template <class T, class Sub, class Point, class SizeT, class MarginT> +struct BaseRect { + T x, y, width, height; + + // Constructors + BaseRect() : x(0), y(0), width(0), height(0) {} + BaseRect(const Point& aOrigin, const SizeT& aSize) + : x(aOrigin.x), y(aOrigin.y), width(aSize.width), height(aSize.height) {} + BaseRect(T aX, T aY, T aWidth, T aHeight) + : x(aX), y(aY), width(aWidth), height(aHeight) {} + + // Emptiness. An empty rect is one that has no area, i.e. its height or width + // is <= 0. Zero rect is the one with height and width set to zero. Note + // that SetEmpty() may change a rectangle that identified as IsEmpty(). + MOZ_ALWAYS_INLINE bool IsZeroArea() const { + return height == 0 || width == 0; + } + MOZ_ALWAYS_INLINE bool IsEmpty() const { return height <= 0 || width <= 0; } + void SetEmpty() { width = height = 0; } + + // "Finite" means not inf and not NaN + bool IsFinite() const { + using FloatType = + std::conditional_t<std::is_same_v<T, float>, float, double>; + return (std::isfinite(FloatType(x)) && std::isfinite(FloatType(y)) && + std::isfinite(FloatType(width)) && + std::isfinite(FloatType(height))); + } + + // Returns true if this rectangle contains the interior of aRect. Always + // returns true if aRect is empty, and always returns false is aRect is + // nonempty but this rect is empty. + bool Contains(const Sub& aRect) const { + return aRect.IsEmpty() || (x <= aRect.x && aRect.XMost() <= XMost() && + y <= aRect.y && aRect.YMost() <= YMost()); + } + // Returns true if this rectangle contains the point. Points are considered + // in the rectangle if they are on the left or top edge, but outside if they + // are on the right or bottom edge. + MOZ_ALWAYS_INLINE bool Contains(T aX, T aY) const { + return x <= aX && aX < XMost() && y <= aY && aY < YMost(); + } + MOZ_ALWAYS_INLINE bool ContainsX(T aX) const { + return x <= aX && aX < XMost(); + } + MOZ_ALWAYS_INLINE bool ContainsY(T aY) const { + return y <= aY && aY < YMost(); + } + // Returns true if this rectangle contains the point. Points are considered + // in the rectangle if they are on the left or top edge, but outside if they + // are on the right or bottom edge. + bool Contains(const Point& aPoint) const { + return Contains(aPoint.x, aPoint.y); + } + + // Returns true if this rectangle contains the point, considering points on + // all edges of the rectangle to be contained (as compared to Contains() + // which only includes points on the top & left but not bottom & right edges). + MOZ_ALWAYS_INLINE bool ContainsInclusively(const Point& aPoint) const { + return x <= aPoint.x && aPoint.x <= XMost() && y <= aPoint.y && + aPoint.y <= YMost(); + } + + // Intersection. Returns TRUE if the receiver's area has non-empty + // intersection with aRect's area, and FALSE otherwise. + // Always returns false if aRect is empty or 'this' is empty. + bool Intersects(const Sub& aRect) const { + return !IsEmpty() && !aRect.IsEmpty() && x < aRect.XMost() && + aRect.x < XMost() && y < aRect.YMost() && aRect.y < YMost(); + } + // Returns the rectangle containing the intersection of the points + // (including edges) of *this and aRect. If there are no points in that + // intersection, returns an empty rectangle with x/y set to the std::max of + // the x/y of *this and aRect. + // + // Intersection with an empty Rect may not produce an empty Rect if overflow + // occurs. e.g. {INT_MIN, 0, 0, 20} Intersect { 5000, 0, 500, 20 } gives: + // the non-emtpy {5000, 0, 500, 20 } instead of {5000, 0, 0, 0} + [[nodiscard]] Sub Intersect(const Sub& aRect) const { + Sub result; + result.x = std::max<T>(x, aRect.x); + result.y = std::max<T>(y, aRect.y); + result.width = + std::min<T>(x - result.x + width, aRect.x - result.x + aRect.width); + result.height = + std::min<T>(y - result.y + height, aRect.y - result.y + aRect.height); + // See bug 1457110, this function expects to -only- size to 0,0 if the + // width/height is explicitly negative. + if (result.width < 0 || result.height < 0) { + result.SizeTo(0, 0); + } + return result; + } + + // Gives the same results as Intersect() but handles integer overflow + // better. This comes at a tiny cost in performance. + // e.g. {INT_MIN, 0, 0, 20} Intersect { 5000, 0, 500, 20 } gives: + // {5000, 0, 0, 0} + [[nodiscard]] Sub SafeIntersect(const Sub& aRect) const { + Sub result; + result.x = std::max<T>(x, aRect.x); + result.y = std::max<T>(y, aRect.y); + T right = std::min<T>(x + width, aRect.x + aRect.width); + T bottom = std::min<T>(y + height, aRect.y + aRect.height); + // See bug 1457110, this function expects to -only- size to 0,0 if the + // width/height is explicitly negative. + if (right < result.x || bottom < result.y) { + result.width = 0; + result.height = 0; + } else { + result.width = right - result.x; + result.height = bottom - result.y; + } + return result; + } + + // Sets *this to be the rectangle containing the intersection of the points + // (including edges) of *this and aRect. If there are no points in that + // intersection, sets *this to be an empty rectangle with x/y set to the + // std::max of the x/y of *this and aRect. + // + // 'this' can be the same object as either aRect1 or aRect2 + bool IntersectRect(const Sub& aRect1, const Sub& aRect2) { + T newX = std::max<T>(aRect1.x, aRect2.x); + T newY = std::max<T>(aRect1.y, aRect2.y); + width = std::min<T>(aRect1.x - newX + aRect1.width, + aRect2.x - newX + aRect2.width); + height = std::min<T>(aRect1.y - newY + aRect1.height, + aRect2.y - newY + aRect2.height); + x = newX; + y = newY; + if (width <= 0 || height <= 0) { + SizeTo(0, 0); + return false; + } + return true; + } + + // Returns the smallest rectangle that contains both the area of both + // this and aRect. Thus, empty input rectangles are ignored. + // Note: if both rectangles are empty, returns aRect. + // WARNING! This is not safe against overflow, prefer using SafeUnion instead + // when dealing with int-based rects. + [[nodiscard]] Sub Union(const Sub& aRect) const { + if (IsEmpty()) { + return aRect; + } else if (aRect.IsEmpty()) { + return *static_cast<const Sub*>(this); + } else { + return UnionEdges(aRect); + } + } + // Returns the smallest rectangle that contains both the points (including + // edges) of both aRect1 and aRect2. + // Thus, empty input rectangles are allowed to affect the result. + // WARNING! This is not safe against overflow, prefer using SafeUnionEdges + // instead when dealing with int-based rects. + [[nodiscard]] Sub UnionEdges(const Sub& aRect) const { + Sub result; + result.x = std::min(x, aRect.x); + result.y = std::min(y, aRect.y); + result.width = std::max(XMost(), aRect.XMost()) - result.x; + result.height = std::max(YMost(), aRect.YMost()) - result.y; + return result; + } + // Computes the smallest rectangle that contains both the area of both + // aRect1 and aRect2, and fills 'this' with the result. + // Thus, empty input rectangles are ignored. + // If both rectangles are empty, sets 'this' to aRect2. + // + // 'this' can be the same object as either aRect1 or aRect2 + void UnionRect(const Sub& aRect1, const Sub& aRect2) { + *static_cast<Sub*>(this) = aRect1.Union(aRect2); + } + + void OrWith(const Sub& aRect1) { + UnionRect(*static_cast<Sub*>(this), aRect1); + } + + // Computes the smallest rectangle that contains both the points (including + // edges) of both aRect1 and aRect2. + // Thus, empty input rectangles are allowed to affect the result. + // + // 'this' can be the same object as either aRect1 or aRect2 + void UnionRectEdges(const Sub& aRect1, const Sub& aRect2) { + *static_cast<Sub*>(this) = aRect1.UnionEdges(aRect2); + } + + // Expands the rect to include the point + void ExpandToEnclose(const Point& aPoint) { + if (aPoint.x < x) { + width = XMost() - aPoint.x; + x = aPoint.x; + } else if (aPoint.x > XMost()) { + width = aPoint.x - x; + } + if (aPoint.y < y) { + height = YMost() - aPoint.y; + y = aPoint.y; + } else if (aPoint.y > YMost()) { + height = aPoint.y - y; + } + } + + MOZ_ALWAYS_INLINE void SetRect(T aX, T aY, T aWidth, T aHeight) { + x = aX; + y = aY; + width = aWidth; + height = aHeight; + } + MOZ_ALWAYS_INLINE void SetRectX(T aX, T aWidth) { + x = aX; + width = aWidth; + } + MOZ_ALWAYS_INLINE void SetRectY(T aY, T aHeight) { + y = aY; + height = aHeight; + } + MOZ_ALWAYS_INLINE void SetBox(T aX, T aY, T aXMost, T aYMost) { + x = aX; + y = aY; + width = aXMost - aX; + height = aYMost - aY; + } + MOZ_ALWAYS_INLINE void SetNonEmptyBox(T aX, T aY, T aXMost, T aYMost) { + x = aX; + y = aY; + width = std::max(0, aXMost - aX); + height = std::max(0, aYMost - aY); + } + MOZ_ALWAYS_INLINE void SetBoxX(T aX, T aXMost) { + x = aX; + width = aXMost - aX; + } + MOZ_ALWAYS_INLINE void SetBoxY(T aY, T aYMost) { + y = aY; + height = aYMost - aY; + } + void SetRect(const Point& aPt, const SizeT& aSize) { + SetRect(aPt.x, aPt.y, aSize.width, aSize.height); + } + MOZ_ALWAYS_INLINE void GetRect(T* aX, T* aY, T* aWidth, T* aHeight) const { + *aX = x; + *aY = y; + *aWidth = width; + *aHeight = height; + } + + MOZ_ALWAYS_INLINE void MoveTo(T aX, T aY) { + x = aX; + y = aY; + } + MOZ_ALWAYS_INLINE void MoveToX(T aX) { x = aX; } + MOZ_ALWAYS_INLINE void MoveToY(T aY) { y = aY; } + MOZ_ALWAYS_INLINE void MoveTo(const Point& aPoint) { + x = aPoint.x; + y = aPoint.y; + } + MOZ_ALWAYS_INLINE void MoveBy(T aDx, T aDy) { + x += aDx; + y += aDy; + } + MOZ_ALWAYS_INLINE void MoveByX(T aDx) { x += aDx; } + MOZ_ALWAYS_INLINE void MoveByY(T aDy) { y += aDy; } + MOZ_ALWAYS_INLINE void MoveBy(const Point& aPoint) { + x += aPoint.x; + y += aPoint.y; + } + MOZ_ALWAYS_INLINE void SizeTo(T aWidth, T aHeight) { + width = aWidth; + height = aHeight; + } + MOZ_ALWAYS_INLINE void SizeTo(const SizeT& aSize) { + width = aSize.width; + height = aSize.height; + } + + // Variant of MoveBy that ensures that even after translation by a point that + // the rectangle coordinates will still fit within numeric limits. The origin + // and size will be clipped within numeric limits to ensure this. + void SafeMoveByX(T aDx) { + T x2 = XMost(); + if (aDx >= T(0)) { + T limit = std::numeric_limits<T>::max(); + x = limit - aDx < x ? limit : x + aDx; + width = (limit - aDx < x2 ? limit : x2 + aDx) - x; + } else { + T limit = std::numeric_limits<T>::min(); + x = limit - aDx > x ? limit : x + aDx; + width = (limit - aDx > x2 ? limit : x2 + aDx) - x; + } + } + void SafeMoveByY(T aDy) { + T y2 = YMost(); + if (aDy >= T(0)) { + T limit = std::numeric_limits<T>::max(); + y = limit - aDy < y ? limit : y + aDy; + height = (limit - aDy < y2 ? limit : y2 + aDy) - y; + } else { + T limit = std::numeric_limits<T>::min(); + y = limit - aDy > y ? limit : y + aDy; + height = (limit - aDy > y2 ? limit : y2 + aDy) - y; + } + } + void SafeMoveBy(T aDx, T aDy) { + SafeMoveByX(aDx); + SafeMoveByY(aDy); + } + void SafeMoveBy(const Point& aPoint) { SafeMoveBy(aPoint.x, aPoint.y); } + + void Inflate(T aD) { Inflate(aD, aD); } + void Inflate(T aDx, T aDy) { + x -= aDx; + y -= aDy; + width += 2 * aDx; + height += 2 * aDy; + } + void Inflate(const MarginT& aMargin) { + x -= aMargin.left; + y -= aMargin.top; + width += aMargin.LeftRight(); + height += aMargin.TopBottom(); + } + void Inflate(const SizeT& aSize) { Inflate(aSize.width, aSize.height); } + + void Deflate(T aD) { Deflate(aD, aD); } + void Deflate(T aDx, T aDy) { + x += aDx; + y += aDy; + width = std::max(T(0), width - 2 * aDx); + height = std::max(T(0), height - 2 * aDy); + } + void Deflate(const MarginT& aMargin) { + x += aMargin.left; + y += aMargin.top; + width = std::max(T(0), width - aMargin.LeftRight()); + height = std::max(T(0), height - aMargin.TopBottom()); + } + void Deflate(const SizeT& aSize) { Deflate(aSize.width, aSize.height); } + + // Return true if the rectangles contain the same set of points, including + // points on the edges. + // Use when we care about the exact x/y/width/height values being + // equal (i.e. we care about differences in empty rectangles). + bool IsEqualEdges(const Sub& aRect) const { + return x == aRect.x && y == aRect.y && width == aRect.width && + height == aRect.height; + } + MOZ_ALWAYS_INLINE bool IsEqualRect(T aX, T aY, T aW, T aH) { + return x == aX && y == aY && width == aW && height == aH; + } + MOZ_ALWAYS_INLINE bool IsEqualXY(T aX, T aY) { return x == aX && y == aY; } + + MOZ_ALWAYS_INLINE bool IsEqualSize(T aW, T aH) { + return width == aW && height == aH; + } + + // Return true if the rectangles contain the same area of the plane. + // Use when we do not care about differences in empty rectangles. + bool IsEqualInterior(const Sub& aRect) const { + return IsEqualEdges(aRect) || (IsEmpty() && aRect.IsEmpty()); + } + + friend Sub operator+(Sub aSub, const Point& aPoint) { + aSub += aPoint; + return aSub; + } + friend Sub operator-(Sub aSub, const Point& aPoint) { + aSub -= aPoint; + return aSub; + } + friend Sub operator+(Sub aSub, const SizeT& aSize) { + aSub += aSize; + return aSub; + } + friend Sub operator-(Sub aSub, const SizeT& aSize) { + aSub -= aSize; + return aSub; + } + Sub& operator+=(const Point& aPoint) { + MoveBy(aPoint); + return *static_cast<Sub*>(this); + } + Sub& operator-=(const Point& aPoint) { + MoveBy(-aPoint); + return *static_cast<Sub*>(this); + } + Sub& operator+=(const SizeT& aSize) { + width += aSize.width; + height += aSize.height; + return *static_cast<Sub*>(this); + } + Sub& operator-=(const SizeT& aSize) { + width -= aSize.width; + height -= aSize.height; + return *static_cast<Sub*>(this); + } + // Find difference as a Margin + MarginT operator-(const Sub& aRect) const { + return MarginT(aRect.y - y, XMost() - aRect.XMost(), + YMost() - aRect.YMost(), aRect.x - x); + } + + // Helpers for accessing the vertices + Point TopLeft() const { return Point(x, y); } + Point TopRight() const { return Point(XMost(), y); } + Point BottomLeft() const { return Point(x, YMost()); } + Point BottomRight() const { return Point(XMost(), YMost()); } + Point AtCorner(Corner aCorner) const { + switch (aCorner) { + case eCornerTopLeft: + return TopLeft(); + case eCornerTopRight: + return TopRight(); + case eCornerBottomRight: + return BottomRight(); + case eCornerBottomLeft: + return BottomLeft(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + Point CCWCorner(mozilla::Side side) const { + switch (side) { + case eSideTop: + return TopLeft(); + case eSideRight: + return TopRight(); + case eSideBottom: + return BottomRight(); + case eSideLeft: + return BottomLeft(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + Point CWCorner(mozilla::Side side) const { + switch (side) { + case eSideTop: + return TopRight(); + case eSideRight: + return BottomRight(); + case eSideBottom: + return BottomLeft(); + case eSideLeft: + return TopLeft(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + Point Center() const { return Point(x, y) + Point(width, height) / 2; } + SizeT Size() const { return SizeT(width, height); } + + T Area() const { return width * height; } + + // Helper methods for computing the extents + MOZ_ALWAYS_INLINE T X() const { return x; } + MOZ_ALWAYS_INLINE T Y() const { return y; } + MOZ_ALWAYS_INLINE T Width() const { return width; } + MOZ_ALWAYS_INLINE T Height() const { return height; } + MOZ_ALWAYS_INLINE T XMost() const { return x + width; } + MOZ_ALWAYS_INLINE T YMost() const { return y + height; } + + // Set width and height. SizeTo() sets them together. + MOZ_ALWAYS_INLINE void SetWidth(T aWidth) { width = aWidth; } + MOZ_ALWAYS_INLINE void SetHeight(T aHeight) { height = aHeight; } + + // Get the coordinate of the edge on the given side. + T Edge(mozilla::Side aSide) const { + switch (aSide) { + case eSideTop: + return Y(); + case eSideRight: + return XMost(); + case eSideBottom: + return YMost(); + case eSideLeft: + return X(); + } + MOZ_CRASH("GFX: Incomplete switch"); + } + + // Moves one edge of the rect without moving the opposite edge. + void SetLeftEdge(T aX) { + width = XMost() - aX; + x = aX; + } + void SetRightEdge(T aXMost) { width = aXMost - x; } + void SetTopEdge(T aY) { + height = YMost() - aY; + y = aY; + } + void SetBottomEdge(T aYMost) { height = aYMost - y; } + void Swap() { + std::swap(x, y); + std::swap(width, height); + } + + // Round the rectangle edges to integer coordinates, such that the rounded + // rectangle has the same set of pixel centers as the original rectangle. + // Edges at offset 0.5 round up. + // Suitable for most places where integral device coordinates + // are needed, but note that any translation should be applied first to + // avoid pixel rounding errors. + // Note that this is *not* rounding to nearest integer if the values are + // negative. They are always rounding as floor(n + 0.5). See + // https://bugzilla.mozilla.org/show_bug.cgi?id=410748#c14 If you need similar + // method which is using NS_round(), you should create new + // |RoundAwayFromZero()| method. + void Round() { + T x0 = static_cast<T>(std::floor(T(X()) + 0.5f)); + T y0 = static_cast<T>(std::floor(T(Y()) + 0.5f)); + T x1 = static_cast<T>(std::floor(T(XMost()) + 0.5f)); + T y1 = static_cast<T>(std::floor(T(YMost()) + 0.5f)); + + x = x0; + y = y0; + + width = x1 - x0; + height = y1 - y0; + } + + // Snap the rectangle edges to integer coordinates, such that the + // original rectangle contains the resulting rectangle. + void RoundIn() { + T x0 = static_cast<T>(std::ceil(T(X()))); + T y0 = static_cast<T>(std::ceil(T(Y()))); + T x1 = static_cast<T>(std::floor(T(XMost()))); + T y1 = static_cast<T>(std::floor(T(YMost()))); + + x = x0; + y = y0; + + width = x1 - x0; + height = y1 - y0; + } + + // Snap the rectangle edges to integer coordinates, such that the + // resulting rectangle contains the original rectangle. + void RoundOut() { + T x0 = static_cast<T>(std::floor(T(X()))); + T y0 = static_cast<T>(std::floor(T(Y()))); + T x1 = static_cast<T>(std::ceil(T(XMost()))); + T y1 = static_cast<T>(std::ceil(T(YMost()))); + + x = x0; + y = y0; + + width = x1 - x0; + height = y1 - y0; + } + + // Scale 'this' by aScale.xScale and aScale.yScale without doing any rounding. + template <class Src, class Dst> + void Scale(const BaseScaleFactors2D<Src, Dst, T>& aScale) { + Scale(aScale.xScale, aScale.yScale); + } + // Scale 'this' by aScale without doing any rounding. + void Scale(T aScale) { Scale(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, without doing any rounding. + void Scale(T aXScale, T aYScale) { + x = x * aXScale; + y = y * aYScale; + width = width * aXScale; + height = height * aYScale; + } + // Scale 'this' by aScale, converting coordinates to integers so that the + // result is the smallest integer-coordinate rectangle containing the + // unrounded result. Note: this can turn an empty rectangle into a non-empty + // rectangle + void ScaleRoundOut(double aScale) { ScaleRoundOut(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, converting coordinates to integers so + // that the result is the smallest integer-coordinate rectangle containing the + // unrounded result. + // Note: this can turn an empty rectangle into a non-empty rectangle + void ScaleRoundOut(double aXScale, double aYScale) { + T right = static_cast<T>(ceil(double(XMost()) * aXScale)); + T bottom = static_cast<T>(ceil(double(YMost()) * aYScale)); + x = static_cast<T>(floor(double(x) * aXScale)); + y = static_cast<T>(floor(double(y) * aYScale)); + width = right - x; + height = bottom - y; + } + // Scale 'this' by aScale, converting coordinates to integers so that the + // result is the largest integer-coordinate rectangle contained by the + // unrounded result. + void ScaleRoundIn(double aScale) { ScaleRoundIn(aScale, aScale); } + // Scale 'this' by aXScale and aYScale, converting coordinates to integers so + // that the result is the largest integer-coordinate rectangle contained by + // the unrounded result. + void ScaleRoundIn(double aXScale, double aYScale) { + T right = static_cast<T>(floor(double(XMost()) * aXScale)); + T bottom = static_cast<T>(floor(double(YMost()) * aYScale)); + x = static_cast<T>(ceil(double(x) * aXScale)); + y = static_cast<T>(ceil(double(y) * aYScale)); + width = std::max<T>(0, right - x); + height = std::max<T>(0, bottom - y); + } + // Scale 'this' by 1/aScale, converting coordinates to integers so that the + // result is the smallest integer-coordinate rectangle containing the + // unrounded result. Note: this can turn an empty rectangle into a non-empty + // rectangle + void ScaleInverseRoundOut(double aScale) { + ScaleInverseRoundOut(aScale, aScale); + } + // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers + // so that the result is the smallest integer-coordinate rectangle containing + // the unrounded result. Note: this can turn an empty rectangle into a + // non-empty rectangle + void ScaleInverseRoundOut(double aXScale, double aYScale) { + T right = static_cast<T>(ceil(double(XMost()) / aXScale)); + T bottom = static_cast<T>(ceil(double(YMost()) / aYScale)); + x = static_cast<T>(floor(double(x) / aXScale)); + y = static_cast<T>(floor(double(y) / aYScale)); + width = right - x; + height = bottom - y; + } + // Scale 'this' by 1/aScale, converting coordinates to integers so that the + // result is the largest integer-coordinate rectangle contained by the + // unrounded result. + void ScaleInverseRoundIn(double aScale) { + ScaleInverseRoundIn(aScale, aScale); + } + // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers + // so that the result is the largest integer-coordinate rectangle contained by + // the unrounded result. + void ScaleInverseRoundIn(double aXScale, double aYScale) { + T right = static_cast<T>(floor(double(XMost()) / aXScale)); + T bottom = static_cast<T>(floor(double(YMost()) / aYScale)); + x = static_cast<T>(ceil(double(x) / aXScale)); + y = static_cast<T>(ceil(double(y) / aYScale)); + width = std::max<T>(0, right - x); + height = std::max<T>(0, bottom - y); + } + + /** + * Clamp aPoint to this rectangle. It is allowed to end up on any + * edge of the rectangle. + */ + [[nodiscard]] Point ClampPoint(const Point& aPoint) const { + using Coord = decltype(aPoint.x); + return Point(std::max(Coord(x), std::min(Coord(XMost()), aPoint.x)), + std::max(Coord(y), std::min(Coord(YMost()), aPoint.y))); + } + + /** + * Translate this rectangle to be inside aRect. If it doesn't fit inside + * aRect then the dimensions that don't fit will be shrunk so that they + * do fit. The resulting rect is returned. + */ + [[nodiscard]] Sub MoveInsideAndClamp(const Sub& aRect) const { + Sub rect(std::max(aRect.x, x), std::max(aRect.y, y), + std::min(aRect.width, width), std::min(aRect.height, height)); + rect.x = std::min(rect.XMost(), aRect.XMost()) - rect.width; + rect.y = std::min(rect.YMost(), aRect.YMost()) - rect.height; + return rect; + } + + // Returns the largest rectangle that can be represented with 32-bit + // signed integers, centered around a point at 0,0. As BaseRect's represent + // the dimensions as a top-left point with a width and height, the width + // and height will be the largest positive 32-bit value. The top-left + // position coordinate is divided by two to center the rectangle around a + // point at 0,0. + static Sub MaxIntRect() { + return Sub(static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5), + static_cast<T>(-std::numeric_limits<int32_t>::max() * 0.5), + static_cast<T>(std::numeric_limits<int32_t>::max()), + static_cast<T>(std::numeric_limits<int32_t>::max())); + }; + + // Returns a point representing the distance, along each dimension, of the + // given point from this rectangle. The distance along a dimension is defined + // as zero if the point is within the bounds of the rectangle in that + // dimension; otherwise, it's the distance to the closer endpoint of the + // rectangle in that dimension. + Point DistanceTo(const Point& aPoint) const { + return {DistanceFromInterval(aPoint.x, x, XMost()), + DistanceFromInterval(aPoint.y, y, YMost())}; + } + + friend std::ostream& operator<<( + std::ostream& stream, + const BaseRect<T, Sub, Point, SizeT, MarginT>& aRect) { + return stream << "(x=" << aRect.x << ", y=" << aRect.y + << ", w=" << aRect.width << ", h=" << aRect.height << ')'; + } + + private: + // Do not use the default operator== or operator!= ! + // Use IsEqualEdges or IsEqualInterior explicitly. + bool operator==(const Sub& aRect) const { return false; } + bool operator!=(const Sub& aRect) const { return false; } + + // Helper function for DistanceTo() that computes the distance of a + // coordinate along one dimension from an interval in that dimension. + static T DistanceFromInterval(T aCoord, T aIntervalStart, T aIntervalEnd) { + if (aCoord < aIntervalStart) { + return aIntervalStart - aCoord; + } + if (aCoord > aIntervalEnd) { + return aCoord - aIntervalEnd; + } + return 0; + } +}; + +} // namespace mozilla::gfx + +#endif /* MOZILLA_GFX_BASERECT_H_ */ |