Flutter Impeller
rect.h
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef FLUTTER_IMPELLER_GEOMETRY_RECT_H_
6 #define FLUTTER_IMPELLER_GEOMETRY_RECT_H_
7 
8 #include <array>
9 #include <optional>
10 #include <ostream>
11 #include <vector>
12 
13 #include "fml/logging.h"
18 #include "impeller/geometry/size.h"
19 
20 namespace impeller {
21 
22 #define ONLY_ON_FLOAT_M(Modifiers, Return) \
23  template <typename U = T> \
24  Modifiers std::enable_if_t<std::is_floating_point_v<U>, Return>
25 #define ONLY_ON_FLOAT(Return) DL_ONLY_ON_FLOAT_M(, Return)
26 
27 /// Templated struct for holding an axis-aligned rectangle.
28 ///
29 /// Rectangles are defined as 4 axis-aligned edges that might contain
30 /// space. They can be viewed as 2 X coordinates that define the
31 /// left and right edges and 2 Y coordinates that define the top and
32 /// bottom edges; or they can be viewed as an origin and horizontal
33 /// and vertical dimensions (width and height).
34 ///
35 /// When the left and right edges are equal or reversed (right <= left)
36 /// or the top and bottom edges are equal or reversed (bottom <= top),
37 /// the rectangle is considered empty. Considering the rectangle in XYWH
38 /// form, the width and/or the height would be negative or zero. Such
39 /// reversed/empty rectangles contain no space and act as such in the
40 /// methods that operate on them (Intersection, Union, IntersectsWithRect,
41 /// Contains, Cutout, etc.)
42 ///
43 /// Rectangles cannot be modified by any method and a new value can only
44 /// be stored into an existing rect using assignment. This keeps the API
45 /// clean compared to implementations that might have similar methods
46 /// that produce the answer in place, or construct a new object with
47 /// the answer, or place the result in an indicated result object.
48 ///
49 /// Methods that might fail to produce an answer will use |std::optional|
50 /// to indicate that success or failure (see |Intersection| and |CutOut|).
51 /// For convenience, |Intersection| and |Union| both have overloaded
52 /// variants that take |std::optional| arguments and treat them as if
53 /// the argument was an empty rect to allow chaining multiple such methods
54 /// and only needing to check the optional condition of the final result.
55 /// The primary methods also provide |...OrEmpty| overloaded variants that
56 /// translate an empty optional answer into a simple empty rectangle of the
57 /// same type.
58 ///
59 /// Rounding instance methods are not provided as the return value might
60 /// be wanted as another floating point rectangle or sometimes as an integer
61 /// rectangle. Instead a |RoundOut| factory, defined only for floating point
62 /// input rectangles, is provided to provide control over the result type.
63 ///
64 /// NaN and Infinity values
65 ///
66 /// Constructing an LTRB rectangle using Infinity values should work as
67 /// expected with either 0 or +Infinity returned as dimensions depending on
68 /// which side the Infinity values are on and the sign.
69 ///
70 /// Constructing an XYWH rectangle using Infinity values will usually
71 /// not work if the math requires the object to compute a right or bottom
72 /// edge from ([xy] -Infinity + [wh] +Infinity). Other combinations might
73 /// work.
74 ///
75 /// The special factory |MakeMaximum| is provided to construct a rectangle
76 /// of the indicated coordinate type that covers all finite coordinates.
77 /// It does not use infinity values, but rather the largest finite values
78 /// to avoid math that might produce a NaN value from various getters.
79 ///
80 /// Any rectangle that is constructed with, or computed to have a NaN value
81 /// will be considered the same as any empty rectangle.
82 ///
83 /// Empty Rectangle canonical results summary:
84 ///
85 /// Union will ignore any empty rects and return the other rect
86 /// Intersection will return nullopt if either rect is empty
87 /// IntersectsWithRect will return false if either rect is empty
88 /// Cutout will return the source rect if the argument is empty
89 /// Cutout will return nullopt if the source rectangle is empty
90 /// Contains(Point) will return false if the source rectangle is empty
91 /// Contains(Rect) will return false if the source rectangle is empty
92 /// Contains(Rect) will otherwise return true if the argument is empty
93 /// Specifically, EmptyRect.Contains(EmptyRect) returns false
94 ///
95 /// ---------------
96 /// Special notes on problems using the XYWH form of specifying rectangles:
97 ///
98 /// It is possible to have integer rectangles whose dimensions exceed
99 /// the maximum number that their coordinates can represent since
100 /// (MAX_INT - MIN_INT) overflows the representable positive numbers.
101 /// Floating point rectangles technically have a similar issue in that
102 /// overflow can occur, but it will be automatically converted into
103 /// either an infinity, or a finite-overflow value and still be
104 /// representable, just with little to no precision.
105 ///
106 /// Secondly, specifying a rectangle using XYWH leads to cases where the
107 /// math for (x+w) and/or (y+h) are also beyond the maximum representable
108 /// coordinates. For N-bit integer rectangles declared as XYWH, the
109 /// maximum right coordinate will require N+1 signed bits which cannot be
110 /// stored in storage that uses N-bit integers.
111 ///
112 /// Saturated math is used when constructing a rectangle from XYWH values
113 /// and when returning the dimensions of the rectangle. Constructing an
114 /// integer rectangle from values such that xy + wh is beyond the range
115 /// of the integer type will place the right or bottom edges at the maximum
116 /// value for the integer type. Similarly, constructing an integer rectangle
117 /// such that the distance from the left to the right (or top to bottom) is
118 /// greater than the range of the integer type will simply return the
119 /// maximum integer value as the dimension. Floating point rectangles are
120 /// naturally saturated by the rules of IEEE arithmetic.
121 template <class T>
122 struct TRect {
123  private:
124  using Type = T;
125 
126  public:
127  constexpr TRect() : left_(0), top_(0), right_(0), bottom_(0) {}
128 
129  constexpr static TRect MakeLTRB(Type left,
130  Type top,
131  Type right,
132  Type bottom) {
133  return TRect(left, top, right, bottom);
134  }
135 
136  constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height) {
137  return TRect(x, y, saturated::Add(x, width), saturated::Add(y, height));
138  }
139 
140  constexpr static TRect MakeWH(Type width, Type height) {
141  return TRect(0, 0, width, height);
142  }
143 
144  constexpr static TRect MakeOriginSize(const TPoint<Type>& origin,
145  const TSize<Type>& size) {
146  return MakeXYWH(origin.x, origin.y, size.width, size.height);
147  }
148 
149  template <class U>
150  constexpr static TRect MakeSize(const TSize<U>& size) {
151  return TRect(0.0, 0.0, size.width, size.height);
152  }
153 
154  /// Construct the rectangular bounds of a circle with the supplied
155  /// center point and uniform radius.
156  constexpr static TRect MakeCircleBounds(const TPoint<Type>& center,
157  Type radius) {
158  return MakeLTRB(center.x - radius, center.y - radius, //
159  center.x + radius, center.y + radius);
160  }
161 
162  /// Construct the rectangular bounds of a circle with the supplied
163  /// center point and non-uniform horizontal and vertical radii.
164  constexpr static TRect MakeEllipseBounds(const TPoint<Type>& center,
165  const TSize<Type>& radii) {
166  return MakeLTRB(center.x - radii.width, center.y - radii.height, //
167  center.x + radii.width, center.y + radii.height);
168  }
169 
170  /// Construct the rectangular bounds of a circle with the supplied
171  /// center point and non-uniform horizontal and vertical radii.
172  constexpr static TRect MakeEllipseBounds(const TPoint<Type>& center,
173  const TPoint<Type>& radii) {
174  return MakeLTRB(center.x - radii.x, center.y - radii.y, //
175  center.x + radii.x, center.y + radii.y);
176  }
177 
178  /// Construct a floating point rect |Rect| from another Rect of a
179  /// potentially different storage type (eg. |IRect|).
180  template <class U, class FT = T>
181  constexpr static std::enable_if_t<std::is_floating_point_v<FT>, TRect> Make(
182  const TRect<U>& rect) {
183  return MakeLTRB(
184  static_cast<FT>(rect.GetLeft()), static_cast<FT>(rect.GetTop()),
185  static_cast<FT>(rect.GetRight()), static_cast<FT>(rect.GetBottom()));
186  }
187 
188  template <typename U>
189  constexpr static std::optional<TRect> MakePointBounds(const U& value) {
190  return MakePointBounds(value.begin(), value.end());
191  }
192 
193  template <typename PointIter>
194  constexpr static std::optional<TRect> MakePointBounds(const PointIter first,
195  const PointIter last) {
196  if (first == last) {
197  return std::nullopt;
198  }
199  auto left = first->x;
200  auto top = first->y;
201  auto right = first->x;
202  auto bottom = first->y;
203  for (auto it = first + 1; it < last; ++it) {
204  left = std::min(left, it->x);
205  top = std::min(top, it->y);
206  right = std::max(right, it->x);
207  bottom = std::max(bottom, it->y);
208  }
209  return TRect::MakeLTRB(left, top, right, bottom);
210  }
211 
212  [[nodiscard]] constexpr static TRect MakeMaximum() {
213  return TRect::MakeLTRB(std::numeric_limits<Type>::lowest(),
214  std::numeric_limits<Type>::lowest(),
215  std::numeric_limits<Type>::max(),
216  std::numeric_limits<Type>::max());
217  }
218 
219  [[nodiscard]] constexpr bool operator==(const TRect& r) const {
220  return left_ == r.left_ && //
221  top_ == r.top_ && //
222  right_ == r.right_ && //
223  bottom_ == r.bottom_;
224  }
225 
226  [[nodiscard]] constexpr TRect Scale(Type scale) const {
227  return TRect(left_ * scale, //
228  top_ * scale, //
229  right_ * scale, //
230  bottom_ * scale);
231  }
232 
233  [[nodiscard]] constexpr TRect Scale(Type scale_x, Type scale_y) const {
234  return TRect(left_ * scale_x, //
235  top_ * scale_y, //
236  right_ * scale_x, //
237  bottom_ * scale_y);
238  }
239 
240  [[nodiscard]] constexpr TRect Scale(TPoint<T> scale) const {
241  return Scale(scale.x, scale.y);
242  }
243 
244  [[nodiscard]] constexpr TRect Scale(TSize<T> scale) const {
245  return Scale(scale.width, scale.height);
246  }
247 
248  /// @brief Returns true iff the provided point |p| is inside the
249  /// half-open interior of this rectangle.
250  ///
251  /// For purposes of containment, a rectangle contains points
252  /// along the top and left edges but not points along the
253  /// right and bottom edges so that a point is only ever
254  /// considered inside one of two abutting rectangles.
255  [[nodiscard]] constexpr bool Contains(const TPoint<Type>& p) const {
256  return !this->IsEmpty() && //
257  p.x >= left_ && //
258  p.y >= top_ && //
259  p.x < right_ && //
260  p.y < bottom_;
261  }
262 
263  /// @brief Returns true iff the provided point |p| is inside the
264  /// closed-range interior of this rectangle.
265  ///
266  /// Unlike the regular |Contains(TPoint)| method, this method
267  /// considers all points along the boundary of the rectangle
268  /// to be contained within the rectangle - useful for testing
269  /// if vertices that define a filled shape would carry the
270  /// interior of that shape outside the bounds of the rectangle.
271  /// Since both geometries are defining half-open spaces, their
272  /// defining geometry needs to consider their boundaries to
273  /// be equivalent with respect to interior and exterior.
274  [[nodiscard]] constexpr bool ContainsInclusive(const TPoint<Type>& p) const {
275  return !this->IsEmpty() && //
276  p.x >= left_ && //
277  p.y >= top_ && //
278  p.x <= right_ && //
279  p.y <= bottom_;
280  }
281 
282  /// @brief Returns true iff this rectangle is not empty and it also
283  /// contains every point considered inside the provided
284  /// rectangle |o| (as determined by |Contains(TPoint)|).
285  ///
286  /// This is similar to a definition where the result is true iff
287  /// the union of the two rectangles is equal to this rectangle,
288  /// ignoring precision issues with performing those operations
289  /// and assuming that empty rectangles are never equal.
290  ///
291  /// An empty rectangle can contain no other rectangle.
292  ///
293  /// An empty rectangle is, however, contained within any
294  /// other non-empy rectangle as the set of points it contains
295  /// is an empty set and so there are no points to fail the
296  /// containment criteria.
297  [[nodiscard]] constexpr bool Contains(const TRect& o) const {
298  return !this->IsEmpty() && //
299  (o.IsEmpty() || (o.left_ >= left_ && //
300  o.top_ >= top_ && //
301  o.right_ <= right_ && //
302  o.bottom_ <= bottom_));
303  }
304 
305  template <class U, class FT = T>
306  [[nodiscard]] constexpr std::enable_if_t<std::is_floating_point_v<FT>, bool>
307  Contains(const TRect<U>& o) const {
308  return !this->IsEmpty() && //
309  (o.IsEmpty() || (o.GetLeft() >= left_ && //
310  o.GetTop() >= top_ && //
311  o.GetRight() <= right_ && //
312  o.GetBottom() <= bottom_));
313  }
314 
315  /// @brief Returns true if all of the fields of this floating point
316  /// rectangle are finite.
317  ///
318  /// Note that the results of |GetWidth()| and |GetHeight()| may
319  /// still be infinite due to overflow even if the fields themselves
320  /// are finite.
321  ONLY_ON_FLOAT_M([[nodiscard]] constexpr, bool)
322  IsFinite() const {
323  return std::isfinite(left_) && //
324  std::isfinite(top_) && //
325  std::isfinite(right_) && //
326  std::isfinite(bottom_);
327  }
328 
329  /// @brief Returns true if either of the width or height are 0, negative,
330  /// or NaN.
331  [[nodiscard]] constexpr bool IsEmpty() const {
332  // Computing the non-empty condition and negating the result causes any
333  // NaN value to return true - i.e. is considered empty.
334  return !(left_ < right_ && top_ < bottom_);
335  }
336 
337  /// @brief Returns true if width and height are equal and neither is NaN.
338  [[nodiscard]] constexpr bool IsSquare() const {
339  // empty rectangles can technically be "square", but would be
340  // misleading to most callers. Using |IsEmpty| also prevents
341  // "non-empty and non-overflowing" computations from happening
342  // to be equal to "empty and overflowing" results.
343  // (Consider LTRB(10, 15, MAX-2, MIN+2) which is empty, but both
344  // w/h subtractions equal "5").
345  return !IsEmpty() && (right_ - left_) == (bottom_ - top_);
346  }
347 
348  [[nodiscard]] constexpr bool IsMaximum() const {
349  return *this == MakeMaximum();
350  }
351 
352  /// @brief Returns the upper left corner of the rectangle as specified
353  /// by the left/top or x/y values when it was constructed.
354  [[nodiscard]] constexpr TPoint<Type> GetOrigin() const {
355  return {left_, top_};
356  }
357 
358  /// @brief Returns the size of the rectangle which may be negative in
359  /// either width or height and may have been clipped to the
360  /// maximum integer values for integer rects whose size overflows.
361  [[nodiscard]] constexpr TSize<Type> GetSize() const {
362  return {GetWidth(), GetHeight()};
363  }
364 
365  /// @brief Returns the X coordinate of the upper left corner, equivalent
366  /// to |GetOrigin().x|
367  [[nodiscard]] constexpr Type GetX() const { return left_; }
368 
369  /// @brief Returns the Y coordinate of the upper left corner, equivalent
370  /// to |GetOrigin().y|
371  [[nodiscard]] constexpr Type GetY() const { return top_; }
372 
373  /// @brief Returns the width of the rectangle, equivalent to
374  /// |GetSize().width|
375  [[nodiscard]] constexpr Type GetWidth() const {
376  return saturated::Sub(right_, left_);
377  }
378 
379  /// @brief Returns the height of the rectangle, equivalent to
380  /// |GetSize().height|
381  [[nodiscard]] constexpr Type GetHeight() const {
382  return saturated::Sub(bottom_, top_);
383  }
384 
385  [[nodiscard]] constexpr auto GetLeft() const { return left_; }
386 
387  [[nodiscard]] constexpr auto GetTop() const { return top_; }
388 
389  [[nodiscard]] constexpr auto GetRight() const { return right_; }
390 
391  [[nodiscard]] constexpr auto GetBottom() const { return bottom_; }
392 
393  [[nodiscard]] constexpr TPoint<T> GetLeftTop() const { //
394  return {left_, top_};
395  }
396 
397  [[nodiscard]] constexpr TPoint<T> GetRightTop() const {
398  return {right_, top_};
399  }
400 
401  [[nodiscard]] constexpr TPoint<T> GetLeftBottom() const {
402  return {left_, bottom_};
403  }
404 
405  [[nodiscard]] constexpr TPoint<T> GetRightBottom() const {
406  return {right_, bottom_};
407  }
408 
409  /// @brief Get the area of the rectangle, equivalent to |GetSize().Area()|
410  [[nodiscard]] constexpr T Area() const {
411  // TODO(141710): Use saturated math to avoid overflow.
412  return IsEmpty() ? 0 : (right_ - left_) * (bottom_ - top_);
413  }
414 
415  /// @brief Get the center point as a |Point|.
416  [[nodiscard]] constexpr Point GetCenter() const {
417  return {saturated::AverageScalar(left_, right_),
418  saturated::AverageScalar(top_, bottom_)};
419  }
420 
421  [[nodiscard]] constexpr std::array<T, 4> GetLTRB() const {
422  return {left_, top_, right_, bottom_};
423  }
424 
425  /// @brief Get the x, y coordinates of the origin and the width and
426  /// height of the rectangle in an array.
427  [[nodiscard]] constexpr std::array<T, 4> GetXYWH() const {
428  return {left_, top_, GetWidth(), GetHeight()};
429  }
430 
431  /// @brief Get a version of this rectangle that has a non-negative size.
432  [[nodiscard]] constexpr TRect GetPositive() const {
433  if (!IsEmpty()) {
434  return *this;
435  }
436  return {
437  std::min(left_, right_),
438  std::min(top_, bottom_),
439  std::max(left_, right_),
440  std::max(top_, bottom_),
441  };
442  }
443 
444  /// @brief Get the points that represent the 4 corners of this rectangle
445  /// in a Z order that is compatible with triangle strips or a set
446  /// of all zero points if the rectangle is empty.
447  /// The order is: Top left, top right, bottom left, bottom right.
448  [[nodiscard]] constexpr std::array<TPoint<T>, 4> GetPoints() const {
449  if (IsEmpty()) {
450  return {};
451  }
452  return {
453  TPoint{left_, top_},
454  TPoint{right_, top_},
455  TPoint{left_, bottom_},
456  TPoint{right_, bottom_},
457  };
458  }
459 
460  [[nodiscard]] constexpr std::array<TPoint<T>, 4> GetTransformedPoints(
461  const Matrix& transform) const {
462  auto points = GetPoints();
463  for (size_t i = 0; i < points.size(); i++) {
464  points[i] = transform * points[i];
465  }
466  return points;
467  }
468 
469  /// @brief Creates a new bounding box that contains this transformed
470  /// rectangle, clipped against the near clipping plane if
471  /// necessary.
472  [[nodiscard]] constexpr TRect TransformAndClipBounds(
473  const Matrix& transform) const {
474  if (!transform.HasPerspective2D()) {
475  return TransformBounds(transform);
476  }
477 
478  if (IsEmpty()) {
479  return {};
480  }
481 
482  auto ul = transform.TransformHomogenous({left_, top_});
483  auto ur = transform.TransformHomogenous({right_, top_});
484  auto ll = transform.TransformHomogenous({left_, bottom_});
485  auto lr = transform.TransformHomogenous({right_, bottom_});
486 
487  // It can probably be proven that we only ever have 5 points at most
488  // which happens when only 1 corner is clipped and we get 2 points
489  // in return for it as we interpolate against its neighbors.
490  Point points[8];
491  int index = 0;
492 
493  // Process (clip and interpolate) each point against its 2 neighbors:
494  // left, pt, right
495  index = ClipAndInsert(points, index, ll, ul, ur);
496  index = ClipAndInsert(points, index, ul, ur, lr);
497  index = ClipAndInsert(points, index, ur, lr, ll);
498  index = ClipAndInsert(points, index, lr, ll, ul);
499 
500  auto bounds = TRect::MakePointBounds(points, points + index);
501  return bounds.value_or(TRect{});
502  }
503 
504  /// @brief Creates a new bounding box that contains this transformed
505  /// rectangle.
506  [[nodiscard]] constexpr TRect TransformBounds(const Matrix& transform) const {
507  if (IsEmpty()) {
508  return {};
509  }
511  auto bounds = TRect::MakePointBounds(points.begin(), points.end());
512  if (bounds.has_value()) {
513  return bounds.value();
514  }
515  FML_UNREACHABLE();
516  }
517 
518  /// @brief Constructs a Matrix that will map all points in the coordinate
519  /// space of the rectangle into a new normalized coordinate space
520  /// where the upper left corner of the rectangle maps to (0, 0)
521  /// and the lower right corner of the rectangle maps to (1, 1).
522  ///
523  /// Empty and non-finite rectangles will return a zero-scaling
524  /// transform that maps all points to (0, 0).
525  [[nodiscard]] constexpr Matrix GetNormalizingTransform() const {
526  if (!IsEmpty()) {
527  Scalar sx = 1.0 / GetWidth();
528  Scalar sy = 1.0 / GetHeight();
529  Scalar tx = left_ * -sx;
530  Scalar ty = top_ * -sy;
531 
532  // Exclude NaN and infinities and either scale underflowing to zero
533  if (sx != 0.0 && sy != 0.0 && 0.0 * sx * sy * tx * ty == 0.0) {
534  // clang-format off
535  return Matrix( sx, 0.0f, 0.0f, 0.0f,
536  0.0f, sy, 0.0f, 0.0f,
537  0.0f, 0.0f, 1.0f, 0.0f,
538  tx, ty, 0.0f, 1.0f);
539  // clang-format on
540  }
541  }
542 
543  // Map all coordinates to the origin.
544  return Matrix::MakeScale({0.0f, 0.0f, 1.0f});
545  }
546 
547  [[nodiscard]] constexpr TRect Union(const TRect& o) const {
548  if (IsEmpty()) {
549  return o;
550  }
551  if (o.IsEmpty()) {
552  return *this;
553  }
554  return {
555  std::min(left_, o.left_),
556  std::min(top_, o.top_),
557  std::max(right_, o.right_),
558  std::max(bottom_, o.bottom_),
559  };
560  }
561 
562  [[nodiscard]] constexpr std::optional<TRect> Intersection(
563  const TRect& o) const {
564  if (IntersectsWithRect(o)) {
565  return TRect{
566  std::max(left_, o.left_),
567  std::max(top_, o.top_),
568  std::min(right_, o.right_),
569  std::min(bottom_, o.bottom_),
570  };
571  } else {
572  return std::nullopt;
573  }
574  }
575 
576  [[nodiscard]] constexpr TRect IntersectionOrEmpty(const TRect& o) const {
577  return Intersection(o).value_or(TRect());
578  }
579 
580  [[nodiscard]] constexpr bool IntersectsWithRect(const TRect& o) const {
581  return !IsEmpty() && //
582  !o.IsEmpty() && //
583  left_ < o.right_ && //
584  top_ < o.bottom_ && //
585  right_ > o.left_ && //
586  bottom_ > o.top_;
587  }
588 
589  /// @brief Returns the new boundary rectangle that would result from this
590  /// rectangle being cut out by the specified rectangle.
591  [[nodiscard]] constexpr std::optional<TRect<T>> Cutout(const TRect& o) const {
592  if (IsEmpty()) {
593  // This test isn't just a short-circuit, it also prevents the concise
594  // math below from returning the wrong answer on empty rects.
595  // Once we know that this rectangle is not empty, the math below can
596  // only succeed in computing a value if o is also non-empty and non-nan.
597  // Otherwise, the method returns *this by default.
598  return std::nullopt;
599  }
600 
601  const auto& [a_left, a_top, a_right, a_bottom] = GetLTRB(); // Source rect.
602  const auto& [b_left, b_top, b_right, b_bottom] = o.GetLTRB(); // Cutout.
603  if (b_left <= a_left && b_right >= a_right) {
604  if (b_top <= a_top && b_bottom >= a_bottom) {
605  // Full cutout.
606  return std::nullopt;
607  }
608  if (b_top <= a_top && b_bottom > a_top) {
609  // Cuts off the top.
610  return TRect::MakeLTRB(a_left, b_bottom, a_right, a_bottom);
611  }
612  if (b_bottom >= a_bottom && b_top < a_bottom) {
613  // Cuts off the bottom.
614  return TRect::MakeLTRB(a_left, a_top, a_right, b_top);
615  }
616  }
617  if (b_top <= a_top && b_bottom >= a_bottom) {
618  if (b_left <= a_left && b_right > a_left) {
619  // Cuts off the left.
620  return TRect::MakeLTRB(b_right, a_top, a_right, a_bottom);
621  }
622  if (b_right >= a_right && b_left < a_right) {
623  // Cuts off the right.
624  return TRect::MakeLTRB(a_left, a_top, b_left, a_bottom);
625  }
626  }
627 
628  return *this;
629  }
630 
631  [[nodiscard]] constexpr TRect CutoutOrEmpty(const TRect& o) const {
632  return Cutout(o).value_or(TRect());
633  }
634 
635  /// @brief Returns a new rectangle translated by the given offset.
636  [[nodiscard]] constexpr TRect<T> Shift(T dx, T dy) const {
637  return {
638  saturated::Add(left_, dx), //
639  saturated::Add(top_, dy), //
640  saturated::Add(right_, dx), //
641  saturated::Add(bottom_, dy), //
642  };
643  }
644 
645  /// @brief Returns a new rectangle translated by the given offset.
646  [[nodiscard]] constexpr TRect<T> Shift(TPoint<T> offset) const {
647  return Shift(offset.x, offset.y);
648  }
649 
650  /// @brief Returns a rectangle with expanded edges. Negative expansion
651  /// results in shrinking.
652  [[nodiscard]] constexpr TRect<T> Expand(T left,
653  T top,
654  T right,
655  T bottom) const {
656  return {
657  saturated::Sub(left_, left), //
658  saturated::Sub(top_, top), //
659  saturated::Add(right_, right), //
660  saturated::Add(bottom_, bottom), //
661  };
662  }
663 
664  /// @brief Returns a rectangle with expanded edges in all directions.
665  /// Negative expansion results in shrinking.
666  [[nodiscard]] constexpr TRect<T> Expand(T amount) const {
667  return {
668  saturated::Sub(left_, amount), //
669  saturated::Sub(top_, amount), //
670  saturated::Add(right_, amount), //
671  saturated::Add(bottom_, amount), //
672  };
673  }
674 
675  /// @brief Returns a rectangle with expanded edges in all directions.
676  /// Negative expansion results in shrinking.
677  [[nodiscard]] constexpr TRect<T> Expand(T horizontal_amount,
678  T vertical_amount) const {
679  return {
680  saturated::Sub(left_, horizontal_amount), //
681  saturated::Sub(top_, vertical_amount), //
682  saturated::Add(right_, horizontal_amount), //
683  saturated::Add(bottom_, vertical_amount), //
684  };
685  }
686 
687  /// @brief Returns a rectangle with expanded edges in all directions.
688  /// Negative expansion results in shrinking.
689  [[nodiscard]] constexpr TRect<T> Expand(TPoint<T> amount) const {
690  return Expand(amount.x, amount.y);
691  }
692 
693  /// @brief Returns a rectangle with expanded edges in all directions.
694  /// Negative expansion results in shrinking.
695  [[nodiscard]] constexpr TRect<T> Expand(TSize<T> amount) const {
696  return Expand(amount.width, amount.height);
697  }
698 
699  /// @brief Returns a rectangle with edges expanded to satisfy a minimum size
700  /// in a 2D-transformed coordinate space. This function is not valid
701  /// for a matrix with perspective elements.
702  ///
703  /// If the transform has a scaling factor of zero in either dimension,
704  /// it cannot be expanded to the minimum size. This returns nullopt.
705  [[nodiscard]] constexpr std::optional<TRect<T>> ExpandToMinTransformedSize(
706  TSize<T> min_transformed_size,
707  const Matrix& transform) const {
708  FML_DCHECK(!transform.HasPerspective2D());
709 
710  Vector2 transform_scaling = transform.GetBasisScaleXY();
711  if (transform_scaling.x == 0.0f || transform_scaling.y == 0.0f) {
712  return std::nullopt;
713  }
714 
715  Size min_local_size =
716  Size(min_transformed_size.width / transform_scaling.x,
717  min_transformed_size.height / transform_scaling.y);
718  if (!min_local_size.IsFinite()) {
719  return std::nullopt;
720  }
721  Size current_size = GetSize();
722 
723  if (current_size.width >= min_local_size.width &&
724  current_size.height >= min_local_size.height) {
725  // No expansion needed.
726  return *this;
727  }
728 
729  Size expanded_size = current_size.Max(min_local_size);
730  return MakeEllipseBounds(GetCenter(), Point(expanded_size.width * 0.5f,
731  expanded_size.height * 0.5f));
732  }
733 
734  /// @brief Returns a new rectangle that represents the projection of the
735  /// source rectangle onto this rectangle. In other words, the source
736  /// rectangle is redefined in terms of the coordinate space of this
737  /// rectangle.
738  [[nodiscard]] constexpr TRect<T> Project(TRect<T> source) const {
739  if (IsEmpty()) {
740  return {};
741  }
742  return source.Shift(-left_, -top_)
743  .Scale(1.0 / static_cast<Scalar>(GetWidth()),
744  1.0 / static_cast<Scalar>(GetHeight()));
745  }
746 
747  ONLY_ON_FLOAT_M([[nodiscard]] constexpr static, TRect)
748  RoundOut(const TRect<U>& r) {
749  return TRect::MakeLTRB(saturated::Cast<U, Type>(floor(r.GetLeft())),
750  saturated::Cast<U, Type>(floor(r.GetTop())),
751  saturated::Cast<U, Type>(ceil(r.GetRight())),
752  saturated::Cast<U, Type>(ceil(r.GetBottom())));
753  }
754 
755  ONLY_ON_FLOAT_M([[nodiscard]] constexpr static, TRect)
756  RoundIn(const TRect<U>& r) {
757  return TRect::MakeLTRB(saturated::Cast<U, Type>(ceil(r.GetLeft())),
758  saturated::Cast<U, Type>(ceil(r.GetTop())),
759  saturated::Cast<U, Type>(floor(r.GetRight())),
760  saturated::Cast<U, Type>(floor(r.GetBottom())));
761  }
762 
763  ONLY_ON_FLOAT_M([[nodiscard]] constexpr static, TRect)
764  Round(const TRect<U>& r) {
765  return TRect::MakeLTRB(saturated::Cast<U, Type>(round(r.GetLeft())),
766  saturated::Cast<U, Type>(round(r.GetTop())),
767  saturated::Cast<U, Type>(round(r.GetRight())),
768  saturated::Cast<U, Type>(round(r.GetBottom())));
769  }
770 
771  [[nodiscard]] constexpr static TRect Union(const TRect& a,
772  const std::optional<TRect> b) {
773  return b.has_value() ? a.Union(b.value()) : a;
774  }
775 
776  [[nodiscard]] constexpr static TRect Union(const std::optional<TRect> a,
777  const TRect& b) {
778  return a.has_value() ? a->Union(b) : b;
779  }
780 
781  [[nodiscard]] constexpr static std::optional<TRect> Union(
782  const std::optional<TRect> a,
783  const std::optional<TRect> b) {
784  return a.has_value() ? Union(a.value(), b) : b;
785  }
786 
787  [[nodiscard]] constexpr static std::optional<TRect> Intersection(
788  const TRect& a,
789  const std::optional<TRect> b) {
790  return b.has_value() ? a.Intersection(b.value()) : a;
791  }
792 
793  [[nodiscard]] constexpr static std::optional<TRect> Intersection(
794  const std::optional<TRect> a,
795  const TRect& b) {
796  return a.has_value() ? a->Intersection(b) : b;
797  }
798 
799  [[nodiscard]] constexpr static std::optional<TRect> Intersection(
800  const std::optional<TRect> a,
801  const std::optional<TRect> b) {
802  return a.has_value() ? Intersection(a.value(), b) : b;
803  }
804 
805  private:
806  constexpr TRect(Type left, Type top, Type right, Type bottom)
807  : left_(left), top_(top), right_(right), bottom_(bottom) {}
808 
809  Type left_;
810  Type top_;
811  Type right_;
812  Type bottom_;
813 
814  static constexpr Scalar kMinimumHomogenous = 1.0f / (1 << 14);
815 
816  // Clip p against the near clipping plane (W = kMinimumHomogenous)
817  // and interpolate a crossing point against the nearby neighbors
818  // left and right if p is clipped and either of them is not.
819  // This method can produce 0, 1, or 2 points per call depending on
820  // how many of the points are clipped.
821  // 0 - all points are clipped
822  // 1 - p is unclipped OR
823  // p is clipped and exactly one of the neighbors is not
824  // 2 - p is clipped and both neighbors are not
825  static constexpr int ClipAndInsert(Point clipped[],
826  int index,
827  const Vector3& left,
828  const Vector3& p,
829  const Vector3& right) {
830  if (p.z >= kMinimumHomogenous) {
831  clipped[index++] = {p.x / p.z, p.y / p.z};
832  } else {
833  index = InterpolateAndInsert(clipped, index, p, left);
834  index = InterpolateAndInsert(clipped, index, p, right);
835  }
836  return index;
837  }
838 
839  // Interpolate (a clipped) point p against one of its neighbors
840  // and insert the point into the array where the line between them
841  // veers from clipped space to unclipped, if such a point exists.
842  static constexpr int InterpolateAndInsert(Point clipped[],
843  int index,
844  const Vector3& p,
845  const Vector3& neighbor) {
846  if (neighbor.z >= kMinimumHomogenous) {
847  auto t = (kMinimumHomogenous - p.z) / (neighbor.z - p.z);
848  clipped[index++] = {
849  ((1.0f - t) * p.x + t * neighbor.x) / kMinimumHomogenous,
850  ((1.0f - t) * p.y + t * neighbor.y) / kMinimumHomogenous,
851  };
852  }
853  return index;
854  }
855 };
856 
860 using IRect = IRect64;
861 
862 #undef ONLY_ON_FLOAT
863 #undef ONLY_ON_FLOAT_M
864 
865 } // namespace impeller
866 
867 namespace std {
868 
869 template <class T>
870 inline std::ostream& operator<<(std::ostream& out,
871  const impeller::TRect<T>& r) {
872  out << "(" << r.GetLeftTop() << " => " << r.GetRightBottom() << ")";
873  return out;
874 }
875 
876 } // namespace std
877 
878 #endif // FLUTTER_IMPELLER_GEOMETRY_RECT_H_
int32_t value
int32_t x
float Scalar
Definition: scalar.h:19
TPoint< Scalar > Point
Definition: point.h:426
TSize< Scalar > Size
Definition: size.h:159
TRect< int64_t > IRect64
Definition: rect.h:859
Definition: comparable.h:93
std::ostream & operator<<(std::ostream &out, const impeller::Arc &a)
Definition: arc.h:141
#define ONLY_ON_FLOAT_M(Modifiers, Return)
Definition: rect.h:22
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
constexpr TRect< T > Expand(T left, T top, T right, T bottom) const
Returns a rectangle with expanded edges. Negative expansion results in shrinking.
Definition: rect.h:652
constexpr auto GetBottom() const
Definition: rect.h:391
constexpr Type GetY() const
Returns the Y coordinate of the upper left corner, equivalent to |GetOrigin().y|.
Definition: rect.h:371
constexpr TRect TransformBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle.
Definition: rect.h:506
constexpr TRect< T > Project(TRect< T > source) const
Returns a new rectangle that represents the projection of the source rectangle onto this rectangle....
Definition: rect.h:738
constexpr bool ContainsInclusive(const TPoint< Type > &p) const
Returns true iff the provided point |p| is inside the closed-range interior of this rectangle.
Definition: rect.h:274
constexpr auto GetTop() const
Definition: rect.h:387
constexpr Type GetHeight() const
Returns the height of the rectangle, equivalent to |GetSize().height|.
Definition: rect.h:381
constexpr TPoint< Type > GetOrigin() const
Returns the upper left corner of the rectangle as specified by the left/top or x/y values when it was...
Definition: rect.h:354
constexpr TRect Scale(TPoint< T > scale) const
Definition: rect.h:240
constexpr TRect< T > Expand(TPoint< T > amount) const
Returns a rectangle with expanded edges in all directions. Negative expansion results in shrinking.
Definition: rect.h:689
constexpr std::optional< TRect< T > > ExpandToMinTransformedSize(TSize< T > min_transformed_size, const Matrix &transform) const
Returns a rectangle with edges expanded to satisfy a minimum size in a 2D-transformed coordinate spac...
Definition: rect.h:705
constexpr bool IsMaximum() const
Definition: rect.h:348
constexpr std::optional< TRect > Intersection(const TRect &o) const
Definition: rect.h:562
constexpr bool IsEmpty() const
Returns true if either of the width or height are 0, negative, or NaN.
Definition: rect.h:331
constexpr T Area() const
Get the area of the rectangle, equivalent to |GetSize().Area()|.
Definition: rect.h:410
constexpr static TRect MakeOriginSize(const TPoint< Type > &origin, const TSize< Type > &size)
Definition: rect.h:144
constexpr bool Contains(const TPoint< Type > &p) const
Returns true iff the provided point |p| is inside the half-open interior of this rectangle.
Definition: rect.h:255
constexpr static TRect Union(const TRect &a, const std::optional< TRect > b)
Definition: rect.h:771
constexpr static TRect MakeCircleBounds(const TPoint< Type > &center, Type radius)
Definition: rect.h:156
constexpr TRect Union(const TRect &o) const
Definition: rect.h:547
constexpr TRect Scale(Type scale_x, Type scale_y) const
Definition: rect.h:233
constexpr std::array< TPoint< T >, 4 > GetPoints() const
Get the points that represent the 4 corners of this rectangle in a Z order that is compatible with tr...
Definition: rect.h:448
constexpr bool IntersectsWithRect(const TRect &o) const
Definition: rect.h:580
constexpr auto GetLeft() const
Definition: rect.h:385
RoundIn(const TRect< U > &r)
Definition: rect.h:756
constexpr TRect CutoutOrEmpty(const TRect &o) const
Definition: rect.h:631
constexpr static TRect MakeWH(Type width, Type height)
Definition: rect.h:140
constexpr static std::optional< TRect > MakePointBounds(const U &value)
Definition: rect.h:189
constexpr TSize< Type > GetSize() const
Returns the size of the rectangle which may be negative in either width or height and may have been c...
Definition: rect.h:361
constexpr TRect< T > Expand(T horizontal_amount, T vertical_amount) const
Returns a rectangle with expanded edges in all directions. Negative expansion results in shrinking.
Definition: rect.h:677
Round(const TRect< U > &r)
Definition: rect.h:764
RoundOut(const TRect< U > &r)
Definition: rect.h:748
constexpr TRect GetPositive() const
Get a version of this rectangle that has a non-negative size.
Definition: rect.h:432
constexpr TRect Scale(TSize< T > scale) const
Definition: rect.h:244
constexpr Type GetX() const
Returns the X coordinate of the upper left corner, equivalent to |GetOrigin().x|.
Definition: rect.h:367
constexpr auto GetRight() const
Definition: rect.h:389
constexpr bool IsSquare() const
Returns true if width and height are equal and neither is NaN.
Definition: rect.h:338
constexpr static std::optional< TRect > Intersection(const std::optional< TRect > a, const std::optional< TRect > b)
Definition: rect.h:799
constexpr bool Contains(const TRect &o) const
Returns true iff this rectangle is not empty and it also contains every point considered inside the p...
Definition: rect.h:297
IsFinite() const
Returns true if all of the fields of this floating point rectangle are finite.
Definition: rect.h:322
constexpr Matrix GetNormalizingTransform() const
Constructs a Matrix that will map all points in the coordinate space of the rectangle into a new norm...
Definition: rect.h:525
constexpr static std::optional< TRect > Union(const std::optional< TRect > a, const std::optional< TRect > b)
Definition: rect.h:781
constexpr TRect Scale(Type scale) const
Definition: rect.h:226
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr std::enable_if_t< std::is_floating_point_v< FT >, bool > Contains(const TRect< U > &o) const
Definition: rect.h:307
constexpr TPoint< T > GetLeftBottom() const
Definition: rect.h:401
constexpr std::array< TPoint< T >, 4 > GetTransformedPoints(const Matrix &transform) const
Definition: rect.h:460
constexpr TRect TransformAndClipBounds(const Matrix &transform) const
Creates a new bounding box that contains this transformed rectangle, clipped against the near clippin...
Definition: rect.h:472
constexpr static TRect Union(const std::optional< TRect > a, const TRect &b)
Definition: rect.h:776
constexpr TRect< T > Shift(TPoint< T > offset) const
Returns a new rectangle translated by the given offset.
Definition: rect.h:646
constexpr TPoint< T > GetRightTop() const
Definition: rect.h:397
constexpr TRect< T > Expand(T amount) const
Returns a rectangle with expanded edges in all directions. Negative expansion results in shrinking.
Definition: rect.h:666
constexpr static std::optional< TRect > MakePointBounds(const PointIter first, const PointIter last)
Definition: rect.h:194
constexpr TRect< T > Shift(T dx, T dy) const
Returns a new rectangle translated by the given offset.
Definition: rect.h:636
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:150
constexpr Type GetWidth() const
Returns the width of the rectangle, equivalent to |GetSize().width|.
Definition: rect.h:375
constexpr TPoint< T > GetRightBottom() const
Definition: rect.h:405
constexpr std::array< T, 4 > GetLTRB() const
Definition: rect.h:421
constexpr static std::enable_if_t< std::is_floating_point_v< FT >, TRect > Make(const TRect< U > &rect)
Definition: rect.h:181
constexpr TRect< T > Expand(TSize< T > amount) const
Returns a rectangle with expanded edges in all directions. Negative expansion results in shrinking.
Definition: rect.h:695
constexpr static TRect MakeEllipseBounds(const TPoint< Type > &center, const TPoint< Type > &radii)
Definition: rect.h:172
constexpr static std::optional< TRect > Intersection(const TRect &a, const std::optional< TRect > b)
Definition: rect.h:787
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition: rect.h:416
constexpr TRect IntersectionOrEmpty(const TRect &o) const
Definition: rect.h:576
constexpr TRect()
Definition: rect.h:127
constexpr static std::optional< TRect > Intersection(const std::optional< TRect > a, const TRect &b)
Definition: rect.h:793
constexpr std::optional< TRect< T > > Cutout(const TRect &o) const
Returns the new boundary rectangle that would result from this rectangle being cut out by the specifi...
Definition: rect.h:591
constexpr TPoint< T > GetLeftTop() const
Definition: rect.h:393
constexpr bool operator==(const TRect &r) const
Definition: rect.h:219
constexpr static TRect MakeEllipseBounds(const TPoint< Type > &center, const TSize< Type > &radii)
Definition: rect.h:164
constexpr std::array< T, 4 > GetXYWH() const
Get the x, y coordinates of the origin and the width and height of the rectangle in an array.
Definition: rect.h:427
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
constexpr static TRect MakeMaximum()
Definition: rect.h:212
constexpr TSize Max(const TSize &o) const
Definition: size.h:97
Type height
Definition: size.h:29
Type width
Definition: size.h:28
IsFinite() const
Definition: size.h:126
std::vector< Point > points