Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 4e1fcb3

Browse files
committed
Add Rect::NormalizePoint method
1 parent ec20731 commit 4e1fcb3

File tree

2 files changed

+213
-0
lines changed

2 files changed

+213
-0
lines changed

impeller/geometry/rect.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,35 @@ struct TRect {
181181
return {GetRight(), GetBottom()};
182182
}
183183

184+
/// @brief Computes the normalized location of an absolute point relative
185+
/// to this rectangle and returns it as a relative Scalar Point
186+
/// where (0, 0) represents the origin and (1, 1) represents the
187+
/// lower right corner of the rectangle.
188+
///
189+
/// Empty rectangles produce (0, 0) for all input values as well
190+
/// as infinite rectangles in the case where the computation is
191+
/// mathematically impossible.
192+
template <typename U>
193+
constexpr TPoint<Scalar> NormalizePoint(TPoint<U> absolute) {
194+
if (size.IsEmpty()) {
195+
// empty rects have no interior so the only point that maps correctly
196+
// via the calculations is the origin and the rest produce infinities
197+
// or NaN. To avoid polluting the downstream calculations with values
198+
// that are not finite, all points will be the origin relative to an
199+
// empty rectangle. The checks below would catch this case for zero
200+
// sized empty rects, but not for negative sizes.
201+
return {};
202+
}
203+
Scalar relativeX =
204+
(static_cast<Scalar>(absolute.x) - origin.x) / size.width;
205+
Scalar relativeY =
206+
(static_cast<Scalar>(absolute.y) - origin.y) / size.height;
207+
// An infinite rect can still produce NaN for (infinity / infinity)
208+
return (std::isfinite(relativeX) && std::isfinite(relativeY))
209+
? Point(relativeX, relativeY)
210+
: Point();
211+
}
212+
184213
constexpr std::array<T, 4> GetLTRB() const {
185214
return {GetLeft(), GetTop(), GetRight(), GetBottom()};
186215
}

impeller/geometry/rect_unittests.cc

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,189 @@ TEST(RectTest, RectMakeSize) {
5555
}
5656
}
5757

58+
TEST(RectTest, NormalizePoint) {
59+
// Tests finite rects including:
60+
// - points at the corners, inside, and outside
61+
// - rects that are non-empty or empty through either zero or
62+
// negative width and/or height
63+
// - all combinations of integer and scalar rects and points.
64+
65+
// Tests one rectangle in all 4 combinations of integer and scalar data
66+
// and also against NaN point values
67+
auto test_one = [](int64_t l, int64_t t, int64_t r, int64_t b, //
68+
const std::string& rect_desc, //
69+
int64_t px, int64_t py, //
70+
const std::string& pt_desc, //
71+
Point expected) {
72+
// Scalar point inside Scalar rect
73+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(Point(px, py)),
74+
expected)
75+
<< "Point(" << pt_desc << ") in " << rect_desc << " Rect";
76+
// Scalar point inside Integer rect
77+
ASSERT_EQ(IRect::MakeLTRB(l, t, r, b).NormalizePoint(Point(px, py)),
78+
expected)
79+
<< "Point(" << pt_desc << ") in " << rect_desc << " IRect";
80+
// Integer point inside Scalar rect
81+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(IPoint(px, py)),
82+
expected)
83+
<< "IPoint(" << pt_desc << ") in " << rect_desc << " Rect";
84+
// Integer point inside Integer rect
85+
ASSERT_EQ(IRect::MakeLTRB(l, t, r, b).NormalizePoint(IPoint(px, py)),
86+
expected)
87+
<< "IPoint(" << pt_desc << ") in " << rect_desc << " IRect";
88+
89+
auto nan = std::numeric_limits<Scalar>::quiet_NaN();
90+
auto nan_x = Point(nan, py);
91+
auto nan_y = Point(px, nan);
92+
auto nan_p = Point(nan, nan);
93+
// Nan Scalar point inside Scalar and integer rects
94+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(nan_x), Point())
95+
<< "Point(NaN x) in " << rect_desc << " Rect";
96+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(nan_y), Point())
97+
<< "Point(NaN y) in " << rect_desc << " Rect";
98+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(nan_p), Point())
99+
<< "Point(NaN x&y) in " << rect_desc << " Rect";
100+
ASSERT_EQ(IRect::MakeLTRB(l, t, r, b).NormalizePoint(nan_x), Point())
101+
<< "Point(NaN x) in " << rect_desc << " Rect";
102+
ASSERT_EQ(IRect::MakeLTRB(l, t, r, b).NormalizePoint(nan_y), Point())
103+
<< "Point(NaN y) in " << rect_desc << " Rect";
104+
ASSERT_EQ(IRect::MakeLTRB(l, t, r, b).NormalizePoint(nan_p), Point())
105+
<< "Point(NaN x&y) in " << rect_desc << " Rect";
106+
};
107+
108+
// Tests a rectangle using test_one both normally and all variants of
109+
// being empty by reversing the lr and tb points.
110+
auto test = [&test_one](int64_t l, int64_t t, int64_t r, int64_t b, //
111+
int64_t px, int64_t py, //
112+
const std::string& pt_desc, Point expected) {
113+
test_one(l, t, r, b, "non-empty", px, py, pt_desc, expected);
114+
test_one(l, t, l, b, "LR empty", px, py, pt_desc, Point());
115+
test_one(r, t, l, b, "LR reversed", px, py, pt_desc, Point());
116+
test_one(l, t, r, t, "TB empty", px, py, pt_desc, Point());
117+
test_one(l, b, r, t, "TB reversed", px, py, pt_desc, Point());
118+
test_one(l, t, l, t, "all empty", px, py, pt_desc, Point());
119+
test_one(r, b, l, t, "all reversed", px, py, pt_desc, Point());
120+
};
121+
122+
test(100, 100, 200, 200, 100, 100, "UL", Point(0, 0));
123+
test(100, 100, 200, 200, 200, 100, "UR", Point(1, 0));
124+
test(100, 100, 200, 200, 200, 200, "LR", Point(1, 1));
125+
test(100, 100, 200, 200, 100, 200, "LL", Point(0, 1));
126+
test(100, 100, 200, 200, 150, 150, "Center", Point(0.5, 0.5));
127+
test(100, 100, 200, 200, 0, 0, "outside UL", Point(-1, -1));
128+
test(100, 100, 200, 200, 300, 0, "outside UR", Point(2, -1));
129+
test(100, 100, 200, 200, 300, 300, "outside LR", Point(2, 2));
130+
test(100, 100, 200, 200, 0, 300, "outside LL", Point(-1, 2));
131+
132+
// We can't test the true min and max due to overflow of the xywh
133+
// internal representation, but we can test with half their values.
134+
// When TRect is converted to ltrb notation, we can relax this
135+
// restriction.
136+
int64_t min_int = std::numeric_limits<int64_t>::min() / 2;
137+
int64_t max_int = std::numeric_limits<int64_t>::max() / 2;
138+
test(min_int, 100, max_int, 200, 0, 150, "max int center", Point(0.5, 0.5));
139+
}
140+
141+
TEST(RectTest, NormalizePointToNonFiniteRects) {
142+
// Tests non-finite Scalar rects including:
143+
// - points at the corners, inside, and outside
144+
// - rects that are non-empty or empty through either zero or
145+
// negative width and/or height
146+
147+
// Tests one rectangle against supplied point values and NaN replacements.
148+
auto test = [](Scalar l, Scalar t, Scalar r, Scalar b, //
149+
Scalar px, Scalar py, //
150+
const std::string& pt_desc, //
151+
Point expected) {
152+
auto nan = std::numeric_limits<Scalar>::quiet_NaN();
153+
auto inf = std::numeric_limits<Scalar>::infinity();
154+
155+
// Scalar point inside Scalar rect
156+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(Point(px, py)),
157+
expected)
158+
<< "Point(" << pt_desc << ") in Rect";
159+
// Scalar point inside Scalar rect with NaN left
160+
ASSERT_EQ(Rect::MakeLTRB(nan, t, r, b).NormalizePoint(Point(px, py)),
161+
Point())
162+
<< "Point(" << pt_desc << ") in Rect NaN Left";
163+
// Scalar point inside Scalar rect with NaN top
164+
ASSERT_EQ(Rect::MakeLTRB(l, nan, r, b).NormalizePoint(Point(px, py)),
165+
Point())
166+
<< "Point(" << pt_desc << ") in Rect NaN Top";
167+
// Scalar point inside Scalar rect with NaN right
168+
ASSERT_EQ(Rect::MakeLTRB(l, t, nan, b).NormalizePoint(Point(px, py)),
169+
Point())
170+
<< "Point(" << pt_desc << ") in Rect NaN Left";
171+
// Scalar point inside Scalar rect with NaN bottom
172+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, nan).NormalizePoint(Point(px, py)),
173+
Point())
174+
<< "Point(" << pt_desc << ") in Rect NaN Top";
175+
// Scalar point inside Scalar rect with infinite left
176+
ASSERT_EQ(Rect::MakeLTRB(-inf, t, r, b).NormalizePoint(Point(px, py)),
177+
Point())
178+
<< "Point(" << pt_desc << ") in Rect -Inf Left";
179+
// Scalar point inside Scalar rect with infinite top
180+
ASSERT_EQ(Rect::MakeLTRB(l, -inf, r, b).NormalizePoint(Point(px, py)),
181+
Point())
182+
<< "Point(" << pt_desc << ") in Rect -Inf Top";
183+
// Scalar point inside Scalar rect with infinite right
184+
ASSERT_EQ(Rect::MakeLTRB(l, t, inf, b).NormalizePoint(Point(px, py)),
185+
Point(0, expected.y))
186+
<< "Point(" << pt_desc << ") in Rect Inf Right";
187+
// Scalar point inside Scalar rect with infinite bottom
188+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, inf).NormalizePoint(Point(px, py)),
189+
Point(expected.x, 0))
190+
<< "Point(" << pt_desc << ") in Rect Inf Bottom";
191+
192+
// Testing with NaN points
193+
auto nan_x = Point(nan, py);
194+
auto nan_y = Point(px, nan);
195+
auto nan_p = Point(nan, nan);
196+
// Nan Scalar point inside Scalar rect
197+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(nan_x), Point())
198+
<< "Point(NaN x) in Rect";
199+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(nan_y), Point())
200+
<< "Point(NaN y) in Rect";
201+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(nan_p), Point())
202+
<< "Point(NaN x&y) in Rect";
203+
204+
// Testing with infinite points
205+
auto inf_x = Point(inf, py);
206+
auto inf_y = Point(px, inf);
207+
auto inf_p = Point(inf, inf);
208+
// Infinite Scalar point inside Scalar rect
209+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(inf_x), Point())
210+
<< "Point(Infinite x) in Rect";
211+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(inf_y), Point())
212+
<< "Point(Infinite y) in Rect";
213+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(inf_p), Point())
214+
<< "Point(Infinite x&y) in Rect";
215+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(-inf_x), Point())
216+
<< "Point(-Infinite x) in Rect";
217+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(-inf_y), Point())
218+
<< "Point(-Infinite y) in Rect";
219+
ASSERT_EQ(Rect::MakeLTRB(l, t, r, b).NormalizePoint(-inf_p), Point())
220+
<< "Point(-Infinite x&y) in Rect";
221+
};
222+
223+
test(100, 100, 200, 200, 100, 100, "UL", Point(0, 0));
224+
test(100, 100, 200, 200, 200, 100, "UR", Point(1, 0));
225+
test(100, 100, 200, 200, 200, 200, "LR", Point(1, 1));
226+
test(100, 100, 200, 200, 100, 200, "LL", Point(0, 1));
227+
test(100, 100, 200, 200, 150, 150, "Center", Point(0.5, 0.5));
228+
test(100, 100, 200, 200, 0, 0, "outside UL", Point(-1, -1));
229+
test(100, 100, 200, 200, 300, 0, "outside UR", Point(2, -1));
230+
test(100, 100, 200, 200, 300, 300, "outside LR", Point(2, 2));
231+
test(100, 100, 200, 200, 0, 300, "outside LL", Point(-1, 2));
232+
233+
// We can't test the true min and max due to overflow of the xywh
234+
// internal representation, but we can test with half their values.
235+
// When TRect is converted to ltrb notation, we can relax this
236+
// restriction.
237+
int64_t min_int = std::numeric_limits<int64_t>::min() / 2;
238+
int64_t max_int = std::numeric_limits<int64_t>::max() / 2;
239+
test(min_int, 100, max_int, 200, 0, 150, "max int center", Point(0.5, 0.5));
240+
}
241+
58242
} // namespace testing
59243
} // namespace impeller

0 commit comments

Comments
 (0)