diff --git a/impeller/geometry/geometry_unittests.cc b/impeller/geometry/geometry_unittests.cc index 72d09f1ab5b21..9bafff468b1d8 100644 --- a/impeller/geometry/geometry_unittests.cc +++ b/impeller/geometry/geometry_unittests.cc @@ -1369,6 +1369,65 @@ TEST(GeometryTest, RectIntersectsWithRect) { } } +TEST(GeometryTest, RectCutout) { + // No cutout. + { + Rect a(0, 0, 100, 100); + Rect b(0, 0, 50, 50); + auto u = a.Cutout(b); + ASSERT_TRUE(u.has_value()); + ASSERT_RECT_NEAR(u.value(), a); + } + + // Full cutout. + { + Rect a(0, 0, 100, 100); + Rect b(-10, -10, 120, 120); + auto u = a.Cutout(b); + ASSERT_FALSE(u.has_value()); + } + + // Cutout from top. + { + auto a = Rect::MakeLTRB(0, 0, 100, 100); + auto b = Rect::MakeLTRB(-10, -10, 110, 90); + auto u = a.Cutout(b); + auto expected = Rect::MakeLTRB(0, 90, 100, 100); + ASSERT_TRUE(u.has_value()); + ASSERT_RECT_NEAR(u.value(), expected); + } + + // Cutout from bottom. + { + auto a = Rect::MakeLTRB(0, 0, 100, 100); + auto b = Rect::MakeLTRB(-10, 10, 110, 110); + auto u = a.Cutout(b); + auto expected = Rect::MakeLTRB(0, 0, 100, 10); + ASSERT_TRUE(u.has_value()); + ASSERT_RECT_NEAR(u.value(), expected); + } + + // Cutout from left. + { + auto a = Rect::MakeLTRB(0, 0, 100, 100); + auto b = Rect::MakeLTRB(-10, -10, 90, 110); + auto u = a.Cutout(b); + auto expected = Rect::MakeLTRB(90, 0, 100, 100); + ASSERT_TRUE(u.has_value()); + ASSERT_RECT_NEAR(u.value(), expected); + } + + // Cutout from right. + { + auto a = Rect::MakeLTRB(0, 0, 100, 100); + auto b = Rect::MakeLTRB(10, -10, 110, 110); + auto u = a.Cutout(b); + auto expected = Rect::MakeLTRB(0, 0, 10, 100); + ASSERT_TRUE(u.has_value()); + ASSERT_RECT_NEAR(u.value(), expected); + } +} + TEST(GeometryTest, RectContainsPoint) { { // Origin is inclusive diff --git a/impeller/geometry/rect.h b/impeller/geometry/rect.h index 803000bc007df..3c836226b1cde 100644 --- a/impeller/geometry/rect.h +++ b/impeller/geometry/rect.h @@ -204,6 +204,39 @@ struct TRect { constexpr bool IntersectsWithRect(const TRect& o) const { return Intersection(o).has_value(); } + + /// @brief Returns the new boundary rectangle that would result from the + /// rectangle being cutout by a second rectangle. + constexpr std::optional> Cutout(const TRect& o) const { + const auto& [a_left, a_top, a_right, a_bottom] = GetLTRB(); // Source rect. + const auto& [b_left, b_top, b_right, b_bottom] = o.GetLTRB(); // Cutout. + if (b_left <= a_left && b_right >= a_right) { + if (b_top <= a_top && b_bottom >= a_bottom) { + // Full cutout. + return std::nullopt; + } + if (b_top <= a_top) { + // Cuts off the top. + return TRect::MakeLTRB(a_left, b_bottom, a_right, a_bottom); + } + if (b_bottom >= b_bottom) { + // Cuts out the bottom. + return TRect::MakeLTRB(a_left, a_top, a_right, b_top); + } + } + if (b_top <= a_top && b_bottom >= a_bottom) { + if (b_left <= a_left) { + // Cuts out the left. + return TRect::MakeLTRB(b_right, a_top, a_right, a_bottom); + } + if (b_right >= a_right) { + // Cuts out the right. + return TRect::MakeLTRB(a_left, a_top, b_left, a_bottom); + } + } + + return *this; + } }; using Rect = TRect;