Flutter Impeller
round_superellipse_unittests.cc
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 #include "gtest/gtest.h"
6 
9 
11 
12 #define CHECK_POINT_WITH_OFFSET(rr, p, outward_offset) \
13  EXPECT_TRUE(rr.Contains(p)); \
14  EXPECT_FALSE(rr.Contains(p + outward_offset));
15 
16 namespace impeller {
17 
18 namespace {
19 
20 // A `PathReceiver` that allows setting callbacks for each kind of path
21 // segments.
22 class SpyPathReceiver : public PathReceiver {
23  public:
24  // For now not all segment types are defined since they're not all used.
25  using LineSegment = std::function<void(const Point&)>;
26  using CubicSegment =
27  std::function<void(const Point&, const Point&, const Point&)>;
28 
29  using ConicSegment = std::function<void(const Point&, const Point&, Scalar)>;
30 
31  void SpyLineTo(LineSegment line_to) { line_to_ = std::move(line_to); }
32 
33  void SpyCubicTo(CubicSegment cubic_to) { cubic_to_ = std::move(cubic_to); }
34 
35  void SpyConicTo(ConicSegment conic_to) { conic_to_ = std::move(conic_to); }
36 
37  // |PathReceiver|
38  void MoveTo(const Point& p2, bool will_be_closed) override {}
39  // |PathReceiver|
40  void LineTo(const Point& p2) override {
41  if (line_to_) {
42  line_to_(p2);
43  }
44  }
45  // |PathReceiver|
46  void QuadTo(const Point& cp, const Point& p2) override {}
47  // |PathReceiver|
48  void CubicTo(const Point& cp1, const Point& cp2, const Point& p2) override {
49  if (cubic_to_) {
50  cubic_to_(cp1, cp2, p2);
51  }
52  }
53  bool ConicTo(const Point& cp, const Point& p2, Scalar weight) override {
54  if (conic_to_) {
55  conic_to_(cp, p2, weight);
56  return true;
57  }
58  return false;
59  }
60  // |PathReceiver|
61  void Close() override {}
62 
63  private:
64  LineSegment line_to_;
65  CubicSegment cubic_to_;
66  ConicSegment conic_to_;
67 };
68 
69 } // namespace
70 
71 namespace testing {
72 
73 TEST(RoundSuperellipseTest, EmptyDeclaration) {
75 
76  EXPECT_TRUE(rse.IsEmpty());
77  EXPECT_FALSE(rse.IsRect());
78  EXPECT_FALSE(rse.IsOval());
79  EXPECT_TRUE(rse.IsFinite());
80  EXPECT_TRUE(rse.GetBounds().IsEmpty());
81  EXPECT_EQ(rse.GetBounds(), Rect());
82  EXPECT_EQ(rse.GetBounds().GetLeft(), 0.0f);
83  EXPECT_EQ(rse.GetBounds().GetTop(), 0.0f);
84  EXPECT_EQ(rse.GetBounds().GetRight(), 0.0f);
85  EXPECT_EQ(rse.GetBounds().GetBottom(), 0.0f);
86  EXPECT_EQ(rse.GetRadii().top_left, Size());
87  EXPECT_EQ(rse.GetRadii().top_right, Size());
88  EXPECT_EQ(rse.GetRadii().bottom_left, Size());
89  EXPECT_EQ(rse.GetRadii().bottom_right, Size());
90  EXPECT_EQ(rse.GetRadii().top_left.width, 0.0f);
91  EXPECT_EQ(rse.GetRadii().top_left.height, 0.0f);
92  EXPECT_EQ(rse.GetRadii().top_right.width, 0.0f);
93  EXPECT_EQ(rse.GetRadii().top_right.height, 0.0f);
94  EXPECT_EQ(rse.GetRadii().bottom_left.width, 0.0f);
95  EXPECT_EQ(rse.GetRadii().bottom_left.height, 0.0f);
96  EXPECT_EQ(rse.GetRadii().bottom_right.width, 0.0f);
97  EXPECT_EQ(rse.GetRadii().bottom_right.height, 0.0f);
98 }
99 
100 TEST(RoundSuperellipseTest, DefaultConstructor) {
102 
103  EXPECT_TRUE(rse.IsEmpty());
104  EXPECT_FALSE(rse.IsRect());
105  EXPECT_FALSE(rse.IsOval());
106  EXPECT_TRUE(rse.IsFinite());
107  EXPECT_TRUE(rse.GetBounds().IsEmpty());
108  EXPECT_EQ(rse.GetBounds(), Rect());
109  EXPECT_EQ(rse.GetRadii().top_left, Size());
110  EXPECT_EQ(rse.GetRadii().top_right, Size());
111  EXPECT_EQ(rse.GetRadii().bottom_left, Size());
112  EXPECT_EQ(rse.GetRadii().bottom_right, Size());
113 }
114 
115 TEST(RoundSuperellipseTest, EmptyRectConstruction) {
116  RoundSuperellipse rse =
117  RoundSuperellipse::MakeRect(Rect::MakeLTRB(20.0f, 20.0f, 20.0f, 20.0f));
118 
119  EXPECT_TRUE(rse.IsEmpty());
120  EXPECT_FALSE(rse.IsRect());
121  EXPECT_FALSE(rse.IsOval());
122  EXPECT_TRUE(rse.IsFinite());
123  EXPECT_TRUE(rse.GetBounds().IsEmpty());
124  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(20.0f, 20.0f, 20.0f, 20.0f));
125  EXPECT_EQ(rse.GetRadii().top_left, Size());
126  EXPECT_EQ(rse.GetRadii().top_right, Size());
127  EXPECT_EQ(rse.GetRadii().bottom_left, Size());
128  EXPECT_EQ(rse.GetRadii().bottom_right, Size());
129 }
130 
131 TEST(RoundSuperellipseTest, RectConstructor) {
132  RoundSuperellipse rse =
133  RoundSuperellipse::MakeRect(Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
134 
135  EXPECT_FALSE(rse.IsEmpty());
136  EXPECT_TRUE(rse.IsRect());
137  EXPECT_FALSE(rse.IsOval());
138  EXPECT_TRUE(rse.IsFinite());
139  EXPECT_FALSE(rse.GetBounds().IsEmpty());
140  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
141  EXPECT_EQ(rse.GetRadii().top_left, Size());
142  EXPECT_EQ(rse.GetRadii().top_right, Size());
143  EXPECT_EQ(rse.GetRadii().bottom_left, Size());
144  EXPECT_EQ(rse.GetRadii().bottom_right, Size());
145 }
146 
147 TEST(RoundSuperellipseTest, InvertedRectConstruction) {
148  RoundSuperellipse rse =
149  RoundSuperellipse::MakeRect(Rect::MakeLTRB(20.0f, 20.0f, 10.0f, 10.0f));
150 
151  EXPECT_FALSE(rse.IsEmpty());
152  EXPECT_TRUE(rse.IsRect());
153  EXPECT_FALSE(rse.IsOval());
154  EXPECT_TRUE(rse.IsFinite());
155  EXPECT_FALSE(rse.GetBounds().IsEmpty());
156  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
157  EXPECT_EQ(rse.GetRadii().top_left, Size());
158  EXPECT_EQ(rse.GetRadii().top_right, Size());
159  EXPECT_EQ(rse.GetRadii().bottom_left, Size());
160  EXPECT_EQ(rse.GetRadii().bottom_right, Size());
161 }
162 
163 TEST(RoundSuperellipseTest, EmptyOvalConstruction) {
165  Rect::MakeLTRB(20.0f, 20.0f, 20.0f, 20.0f), 10.0f, 10.0f);
166 
167  EXPECT_TRUE(rse.IsEmpty());
168  EXPECT_FALSE(rse.IsRect());
169  EXPECT_FALSE(rse.IsOval());
170  EXPECT_TRUE(rse.IsFinite());
171  EXPECT_TRUE(rse.GetBounds().IsEmpty());
172  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(20.0f, 20.0f, 20.0f, 20.0f));
173  EXPECT_EQ(rse.GetRadii().top_left, Size());
174  EXPECT_EQ(rse.GetRadii().top_right, Size());
175  EXPECT_EQ(rse.GetRadii().bottom_left, Size());
176  EXPECT_EQ(rse.GetRadii().bottom_right, Size());
177 }
178 
179 TEST(RoundSuperellipseTest, OvalConstructor) {
180  RoundSuperellipse rse =
181  RoundSuperellipse::MakeOval(Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
182 
183  EXPECT_FALSE(rse.IsEmpty());
184  EXPECT_FALSE(rse.IsRect());
185  EXPECT_TRUE(rse.IsOval());
186  EXPECT_TRUE(rse.IsFinite());
187  EXPECT_FALSE(rse.GetBounds().IsEmpty());
188  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
189  EXPECT_EQ(rse.GetRadii().top_left, Size(5.0f, 5.0f));
190  EXPECT_EQ(rse.GetRadii().top_right, Size(5.0f, 5.0f));
191  EXPECT_EQ(rse.GetRadii().bottom_left, Size(5.0f, 5.0f));
192  EXPECT_EQ(rse.GetRadii().bottom_right, Size(5.0f, 5.0f));
193 }
194 
195 TEST(RoundSuperellipseTest, InvertedOvalConstruction) {
197  Rect::MakeLTRB(20.0f, 20.0f, 10.0f, 10.0f), 10.0f, 10.0f);
198 
199  EXPECT_FALSE(rse.IsEmpty());
200  EXPECT_FALSE(rse.IsRect());
201  EXPECT_TRUE(rse.IsOval());
202  EXPECT_TRUE(rse.IsFinite());
203  EXPECT_FALSE(rse.GetBounds().IsEmpty());
204  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
205  EXPECT_EQ(rse.GetRadii().top_left, Size(5.0f, 5.0f));
206  EXPECT_EQ(rse.GetRadii().top_right, Size(5.0f, 5.0f));
207  EXPECT_EQ(rse.GetRadii().bottom_left, Size(5.0f, 5.0f));
208  EXPECT_EQ(rse.GetRadii().bottom_right, Size(5.0f, 5.0f));
209 }
210 
211 TEST(RoundSuperellipseTest, RectRadiusConstructor) {
213  Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f), 2.0f);
214 
215  EXPECT_FALSE(rse.IsEmpty());
216  EXPECT_FALSE(rse.IsRect());
217  EXPECT_FALSE(rse.IsOval());
218  EXPECT_TRUE(rse.IsFinite());
219  EXPECT_FALSE(rse.GetBounds().IsEmpty());
220  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
221  EXPECT_EQ(rse.GetRadii().top_left, Size(2.0f, 2.0f));
222  EXPECT_EQ(rse.GetRadii().top_right, Size(2.0f, 2.0f));
223  EXPECT_EQ(rse.GetRadii().bottom_left, Size(2.0f, 2.0f));
224  EXPECT_EQ(rse.GetRadii().bottom_right, Size(2.0f, 2.0f));
225 }
226 
227 TEST(RoundSuperellipseTest, RectXYConstructor) {
229  Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f), 2.0f, 3.0f);
230 
231  EXPECT_FALSE(rse.IsEmpty());
232  EXPECT_FALSE(rse.IsRect());
233  EXPECT_FALSE(rse.IsOval());
234  EXPECT_TRUE(rse.IsFinite());
235  EXPECT_FALSE(rse.GetBounds().IsEmpty());
236  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
237  EXPECT_EQ(rse.GetRadii().top_left, Size(2.0f, 3.0f));
238  EXPECT_EQ(rse.GetRadii().top_right, Size(2.0f, 3.0f));
239  EXPECT_EQ(rse.GetRadii().bottom_left, Size(2.0f, 3.0f));
240  EXPECT_EQ(rse.GetRadii().bottom_right, Size(2.0f, 3.0f));
241 }
242 
243 TEST(RoundSuperellipseTest, RectSizeConstructor) {
245  Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f), Size(2.0f, 3.0f));
246 
247  EXPECT_FALSE(rse.IsEmpty());
248  EXPECT_FALSE(rse.IsRect());
249  EXPECT_FALSE(rse.IsOval());
250  EXPECT_TRUE(rse.IsFinite());
251  EXPECT_FALSE(rse.GetBounds().IsEmpty());
252  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
253  EXPECT_EQ(rse.GetRadii().top_left, Size(2.0f, 3.0f));
254  EXPECT_EQ(rse.GetRadii().top_right, Size(2.0f, 3.0f));
255  EXPECT_EQ(rse.GetRadii().bottom_left, Size(2.0f, 3.0f));
256  EXPECT_EQ(rse.GetRadii().bottom_right, Size(2.0f, 3.0f));
257 }
258 
259 TEST(RoundSuperellipseTest, RectRadiiConstructor) {
261  Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f),
262  {
263  .top_left = Size(1.0, 1.5),
264  .top_right = Size(2.0, 2.5f),
265  .bottom_left = Size(3.0, 3.5f),
266  .bottom_right = Size(4.0, 4.5f),
267  });
268 
269  EXPECT_FALSE(rse.IsEmpty());
270  EXPECT_FALSE(rse.IsRect());
271  EXPECT_FALSE(rse.IsOval());
272  EXPECT_TRUE(rse.IsFinite());
273  EXPECT_FALSE(rse.GetBounds().IsEmpty());
274  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f));
275  EXPECT_EQ(rse.GetRadii().top_left, Size(1.0f, 1.5f));
276  EXPECT_EQ(rse.GetRadii().top_right, Size(2.0f, 2.5f));
277  EXPECT_EQ(rse.GetRadii().bottom_left, Size(3.0f, 3.5f));
278  EXPECT_EQ(rse.GetRadii().bottom_right, Size(4.0f, 4.5f));
279 }
280 
281 TEST(RoundSuperellipseTest, RectRadiiOverflowWidthConstructor) {
283  Rect::MakeXYWH(10.0f, 10.0f, 6.0f, 30.0f),
284  {
285  .top_left = Size(1.0f, 2.0f),
286  .top_right = Size(3.0f, 4.0f),
287  .bottom_left = Size(5.0f, 6.0f),
288  .bottom_right = Size(7.0f, 8.0f),
289  });
290  // Largest sum of paired radii widths is the bottom edge which sums to 12
291  // Rect is only 6 wide so all radii are scaled by half
292  // Rect is 30 tall so no scaling should happen due to radii heights
293 
294  EXPECT_FALSE(rse.IsEmpty());
295  EXPECT_FALSE(rse.IsRect());
296  EXPECT_FALSE(rse.IsOval());
297  EXPECT_TRUE(rse.IsFinite());
298  EXPECT_FALSE(rse.GetBounds().IsEmpty());
299  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 16.0f, 40.0f));
300  EXPECT_EQ(rse.GetRadii().top_left, Size(0.5f, 1.0f));
301  EXPECT_EQ(rse.GetRadii().top_right, Size(1.5f, 2.0f));
302  EXPECT_EQ(rse.GetRadii().bottom_left, Size(2.5f, 3.0f));
303  EXPECT_EQ(rse.GetRadii().bottom_right, Size(3.5f, 4.0f));
304 }
305 
306 TEST(RoundSuperellipseTest, RectRadiiOverflowHeightConstructor) {
308  Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 6.0f),
309  {
310  .top_left = Size(1.0f, 2.0f),
311  .top_right = Size(3.0f, 4.0f),
312  .bottom_left = Size(5.0f, 6.0f),
313  .bottom_right = Size(7.0f, 8.0f),
314  });
315  // Largest sum of paired radii heights is the right edge which sums to 12
316  // Rect is only 6 tall so all radii are scaled by half
317  // Rect is 30 wide so no scaling should happen due to radii widths
318 
319  EXPECT_FALSE(rse.IsEmpty());
320  EXPECT_FALSE(rse.IsRect());
321  EXPECT_FALSE(rse.IsOval());
322  EXPECT_TRUE(rse.IsFinite());
323  EXPECT_FALSE(rse.GetBounds().IsEmpty());
324  EXPECT_EQ(rse.GetBounds(), Rect::MakeLTRB(10.0f, 10.0f, 40.0f, 16.0f));
325  EXPECT_EQ(rse.GetRadii().top_left, Size(0.5f, 1.0f));
326  EXPECT_EQ(rse.GetRadii().top_right, Size(1.5f, 2.0f));
327  EXPECT_EQ(rse.GetRadii().bottom_left, Size(2.5f, 3.0f));
328  EXPECT_EQ(rse.GetRadii().bottom_right, Size(3.5f, 4.0f));
329 }
330 
331 TEST(RoundSuperellipseTest, Shift) {
333  Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
334  {
335  .top_left = Size(1.0f, 2.0f),
336  .top_right = Size(3.0f, 4.0f),
337  .bottom_left = Size(5.0f, 6.0f),
338  .bottom_right = Size(7.0f, 8.0f),
339  });
340  RoundSuperellipse shifted = rse.Shift(5.0, 6.0);
341 
342  EXPECT_FALSE(shifted.IsEmpty());
343  EXPECT_FALSE(shifted.IsRect());
344  EXPECT_FALSE(shifted.IsOval());
345  EXPECT_TRUE(shifted.IsFinite());
346  EXPECT_FALSE(shifted.GetBounds().IsEmpty());
347  EXPECT_EQ(shifted.GetBounds(), Rect::MakeLTRB(15.0f, 16.0f, 45.0f, 46.0f));
348  EXPECT_EQ(shifted.GetRadii().top_left, Size(1.0f, 2.0f));
349  EXPECT_EQ(shifted.GetRadii().top_right, Size(3.0f, 4.0f));
350  EXPECT_EQ(shifted.GetRadii().bottom_left, Size(5.0f, 6.0f));
351  EXPECT_EQ(shifted.GetRadii().bottom_right, Size(7.0f, 8.0f));
352 
353  EXPECT_EQ(shifted, RoundSuperellipse::MakeRectRadii(
354  Rect::MakeXYWH(15.0f, 16.0f, 30.0f, 30.0f),
355  {
356  .top_left = Size(1.0f, 2.0f),
357  .top_right = Size(3.0f, 4.0f),
358  .bottom_left = Size(5.0f, 6.0f),
359  .bottom_right = Size(7.0f, 8.0f),
360  }));
361 }
362 
363 TEST(RoundSuperellipseTest, ExpandScalar) {
365  Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
366  {
367  .top_left = Size(1.0f, 2.0f),
368  .top_right = Size(3.0f, 4.0f),
369  .bottom_left = Size(5.0f, 6.0f),
370  .bottom_right = Size(7.0f, 8.0f),
371  });
372  RoundSuperellipse expanded = rse.Expand(5.0);
373 
374  EXPECT_FALSE(expanded.IsEmpty());
375  EXPECT_FALSE(expanded.IsRect());
376  EXPECT_FALSE(expanded.IsOval());
377  EXPECT_TRUE(expanded.IsFinite());
378  EXPECT_FALSE(expanded.GetBounds().IsEmpty());
379  EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(5.0f, 5.0f, 45.0f, 45.0f));
380  EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
381  EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
382  EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
383  EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
384 
385  EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
386  Rect::MakeXYWH(5.0f, 5.0f, 40.0f, 40.0f),
387  {
388  .top_left = Size(1.0f, 2.0f),
389  .top_right = Size(3.0f, 4.0f),
390  .bottom_left = Size(5.0f, 6.0f),
391  .bottom_right = Size(7.0f, 8.0f),
392  }));
393 }
394 
395 TEST(RoundSuperellipseTest, ExpandTwoScalars) {
397  Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
398  {
399  .top_left = Size(1.0f, 2.0f),
400  .top_right = Size(3.0f, 4.0f),
401  .bottom_left = Size(5.0f, 6.0f),
402  .bottom_right = Size(7.0f, 8.0f),
403  });
404  RoundSuperellipse expanded = rse.Expand(5.0, 6.0);
405 
406  EXPECT_FALSE(expanded.IsEmpty());
407  EXPECT_FALSE(expanded.IsRect());
408  EXPECT_FALSE(expanded.IsOval());
409  EXPECT_TRUE(expanded.IsFinite());
410  EXPECT_FALSE(expanded.GetBounds().IsEmpty());
411  EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(5.0f, 4.0f, 45.0f, 46.0f));
412  EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
413  EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
414  EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
415  EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
416 
417  EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
418  Rect::MakeXYWH(5.0f, 4.0f, 40.0f, 42.0f),
419  {
420  .top_left = Size(1.0f, 2.0f),
421  .top_right = Size(3.0f, 4.0f),
422  .bottom_left = Size(5.0f, 6.0f),
423  .bottom_right = Size(7.0f, 8.0f),
424  }));
425 }
426 
427 TEST(RoundSuperellipseTest, ExpandFourScalars) {
429  Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
430  {
431  .top_left = Size(1.0f, 2.0f),
432  .top_right = Size(3.0f, 4.0f),
433  .bottom_left = Size(5.0f, 6.0f),
434  .bottom_right = Size(7.0f, 8.0f),
435  });
436  RoundSuperellipse expanded = rse.Expand(5.0, 6.0, 7.0, 8.0);
437 
438  EXPECT_FALSE(expanded.IsEmpty());
439  EXPECT_FALSE(expanded.IsRect());
440  EXPECT_FALSE(expanded.IsOval());
441  EXPECT_TRUE(expanded.IsFinite());
442  EXPECT_FALSE(expanded.GetBounds().IsEmpty());
443  EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(5.0f, 4.0f, 47.0f, 48.0f));
444  EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
445  EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
446  EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
447  EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
448 
449  EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
450  Rect::MakeXYWH(5.0f, 4.0f, 42.0f, 44.0f),
451  {
452  .top_left = Size(1.0f, 2.0f),
453  .top_right = Size(3.0f, 4.0f),
454  .bottom_left = Size(5.0f, 6.0f),
455  .bottom_right = Size(7.0f, 8.0f),
456  }));
457 }
458 
459 TEST(RoundSuperellipseTest, ContractScalar) {
461  Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
462  {
463  .top_left = Size(1.0f, 2.0f),
464  .top_right = Size(3.0f, 4.0f),
465  .bottom_left = Size(5.0f, 6.0f),
466  .bottom_right = Size(7.0f, 8.0f),
467  });
468  RoundSuperellipse expanded = rse.Expand(-2.0);
469 
470  EXPECT_FALSE(expanded.IsEmpty());
471  EXPECT_FALSE(expanded.IsRect());
472  EXPECT_FALSE(expanded.IsOval());
473  EXPECT_TRUE(expanded.IsFinite());
474  EXPECT_FALSE(expanded.GetBounds().IsEmpty());
475  EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(12.0f, 12.0f, 38.0f, 38.0f));
476  EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
477  EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
478  EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
479  EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
480 
481  EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
482  Rect::MakeXYWH(12.0f, 12.0f, 26.0f, 26.0f),
483  {
484  .top_left = Size(1.0f, 2.0f),
485  .top_right = Size(3.0f, 4.0f),
486  .bottom_left = Size(5.0f, 6.0f),
487  .bottom_right = Size(7.0f, 8.0f),
488  }));
489 }
490 
491 TEST(RoundSuperellipseTest, ContractTwoScalars) {
493  Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
494  {
495  .top_left = Size(1.0f, 2.0f),
496  .top_right = Size(3.0f, 4.0f),
497  .bottom_left = Size(5.0f, 6.0f),
498  .bottom_right = Size(7.0f, 8.0f),
499  });
500  RoundSuperellipse expanded = rse.Expand(-1.0, -2.0);
501 
502  EXPECT_FALSE(expanded.IsEmpty());
503  EXPECT_FALSE(expanded.IsRect());
504  EXPECT_FALSE(expanded.IsOval());
505  EXPECT_TRUE(expanded.IsFinite());
506  EXPECT_FALSE(expanded.GetBounds().IsEmpty());
507  EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(11.0f, 12.0f, 39.0f, 38.0f));
508  EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
509  EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
510  EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
511  EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
512 
513  EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
514  Rect::MakeXYWH(11.0f, 12.0f, 28.0f, 26.0f),
515  {
516  .top_left = Size(1.0f, 2.0f),
517  .top_right = Size(3.0f, 4.0f),
518  .bottom_left = Size(5.0f, 6.0f),
519  .bottom_right = Size(7.0f, 8.0f),
520  }));
521 }
522 
523 TEST(RoundSuperellipseTest, ContractFourScalars) {
525  Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
526  {
527  .top_left = Size(1.0f, 2.0f),
528  .top_right = Size(3.0f, 4.0f),
529  .bottom_left = Size(5.0f, 6.0f),
530  .bottom_right = Size(7.0f, 8.0f),
531  });
532  RoundSuperellipse expanded = rse.Expand(-1.0, -1.5, -2.0, -2.5);
533 
534  EXPECT_FALSE(expanded.IsEmpty());
535  EXPECT_FALSE(expanded.IsRect());
536  EXPECT_FALSE(expanded.IsOval());
537  EXPECT_TRUE(expanded.IsFinite());
538  EXPECT_FALSE(expanded.GetBounds().IsEmpty());
539  EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(11.0f, 11.5f, 38.0f, 37.5f));
540  EXPECT_EQ(expanded.GetRadii().top_left, Size(1.0f, 2.0f));
541  EXPECT_EQ(expanded.GetRadii().top_right, Size(3.0f, 4.0f));
542  EXPECT_EQ(expanded.GetRadii().bottom_left, Size(5.0f, 6.0f));
543  EXPECT_EQ(expanded.GetRadii().bottom_right, Size(7.0f, 8.0f));
544 
545  EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
546  Rect::MakeXYWH(11.0f, 11.5f, 27.0f, 26.0f),
547  {
548  .top_left = Size(1.0f, 2.0f),
549  .top_right = Size(3.0f, 4.0f),
550  .bottom_left = Size(5.0f, 6.0f),
551  .bottom_right = Size(7.0f, 8.0f),
552  }));
553 }
554 
555 TEST(RoundSuperellipseTest, ContractAndRequireRadiiAdjustment) {
557  Rect::MakeXYWH(10.0f, 10.0f, 30.0f, 30.0f),
558  {
559  .top_left = Size(1.0f, 2.0f),
560  .top_right = Size(3.0f, 4.0f),
561  .bottom_left = Size(5.0f, 6.0f),
562  .bottom_right = Size(7.0f, 8.0f),
563  });
564  RoundSuperellipse expanded = rse.Expand(-12.0);
565  // Largest sum of paired radii sizes are the bottom and right edges
566  // both of which sum to 12
567  // Rect was 30x30 reduced by 12 on all sides leaving only 6x6, so all
568  // radii are scaled by half to avoid overflowing the contracted rect
569 
570  EXPECT_FALSE(expanded.IsEmpty());
571  EXPECT_FALSE(expanded.IsRect());
572  EXPECT_FALSE(expanded.IsOval());
573  EXPECT_TRUE(expanded.IsFinite());
574  EXPECT_FALSE(expanded.GetBounds().IsEmpty());
575  EXPECT_EQ(expanded.GetBounds(), Rect::MakeLTRB(22.0f, 22.0f, 28.0f, 28.0f));
576  EXPECT_EQ(expanded.GetRadii().top_left, Size(0.5f, 1.0f));
577  EXPECT_EQ(expanded.GetRadii().top_right, Size(1.5f, 2.0f));
578  EXPECT_EQ(expanded.GetRadii().bottom_left, Size(2.5f, 3.0f));
579  EXPECT_EQ(expanded.GetRadii().bottom_right, Size(3.5f, 4.0f));
580 
581  // In this test, the MakeRectRadii constructor will make the same
582  // adjustment to the radii that the Expand method applied.
583  EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
584  Rect::MakeXYWH(22.0f, 22.0f, 6.0f, 6.0f),
585  {
586  .top_left = Size(1.0f, 2.0f),
587  .top_right = Size(3.0f, 4.0f),
588  .bottom_left = Size(5.0f, 6.0f),
589  .bottom_right = Size(7.0f, 8.0f),
590  }));
591 
592  // In this test, the arguments to the constructor supply the correctly
593  // adjusted radii (though there is no real way to tell other than
594  // the result is the same).
595  EXPECT_EQ(expanded, RoundSuperellipse::MakeRectRadii(
596  Rect::MakeXYWH(22.0f, 22.0f, 6.0f, 6.0f),
597  {
598  .top_left = Size(0.5f, 1.0f),
599  .top_right = Size(1.5f, 2.0f),
600  .bottom_left = Size(2.5f, 3.0f),
601  .bottom_right = Size(3.5f, 4.0f),
602  }));
603 }
604 
605 TEST(RoundSuperellipseTest, NoCornerRoundSuperellipseContains) {
606  Rect bounds = Rect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f);
607  // Rounded superellipses of bounds with no corners contains corners just
608  // barely.
609  auto no_corners = RoundSuperellipse::MakeRectRadii(
610  bounds, RoundingRadii::MakeRadii({0.0f, 0.0f}));
611 
612  EXPECT_TRUE(no_corners.Contains({-50, -50}));
613  // Rectangles have half-in, half-out containment so we need
614  // to be careful about testing containment of right/bottom corners.
615  EXPECT_TRUE(no_corners.Contains({-50, 49.99}));
616  EXPECT_TRUE(no_corners.Contains({49.99, -50}));
617  EXPECT_TRUE(no_corners.Contains({49.99, 49.99}));
618  EXPECT_FALSE(no_corners.Contains({-50.01, -50}));
619  EXPECT_FALSE(no_corners.Contains({-50, -50.01}));
620  EXPECT_FALSE(no_corners.Contains({-50.01, 50}));
621  EXPECT_FALSE(no_corners.Contains({-50, 50.01}));
622  EXPECT_FALSE(no_corners.Contains({50.01, -50}));
623  EXPECT_FALSE(no_corners.Contains({50, -50.01}));
624  EXPECT_FALSE(no_corners.Contains({50.01, 50}));
625  EXPECT_FALSE(no_corners.Contains({50, 50.01}));
626 }
627 
628 TEST(RoundSuperellipseTest, TinyCornerContains) {
629  Rect bounds = Rect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f);
630  // Rounded superellipses of bounds with even the tiniest corners does not
631  // contain corners.
632  auto tiny_corners = RoundSuperellipse::MakeRectRadii(
633  bounds, RoundingRadii::MakeRadii({0.01f, 0.01f}));
634 
635  EXPECT_FALSE(tiny_corners.Contains({-50, -50}));
636  EXPECT_FALSE(tiny_corners.Contains({-50, 50}));
637  EXPECT_FALSE(tiny_corners.Contains({50, -50}));
638  EXPECT_FALSE(tiny_corners.Contains({50, 50}));
639 }
640 
641 TEST(RoundSuperellipseTest, UniformSquareContains) {
642  Rect bounds = Rect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f);
644  bounds, RoundingRadii::MakeRadii({5.0f, 5.0f}));
645 
646 #define CHECK_POINT_AND_MIRRORS(p) \
647  CHECK_POINT_WITH_OFFSET(rr, (p), Point(0.02, 0.02)); \
648  CHECK_POINT_WITH_OFFSET(rr, (p) * Point(1, -1), Point(0.02, -0.02)); \
649  CHECK_POINT_WITH_OFFSET(rr, (p) * Point(-1, 1), Point(-0.02, 0.02)); \
650  CHECK_POINT_WITH_OFFSET(rr, (p) * Point(-1, -1), Point(-0.02, -0.02));
651 
652  CHECK_POINT_AND_MIRRORS(Point(0, 49.995)); // Top
653  CHECK_POINT_AND_MIRRORS(Point(44.245, 49.95)); // Top curve start
654  CHECK_POINT_AND_MIRRORS(Point(45.72, 49.87)); // Top joint
655  CHECK_POINT_AND_MIRRORS(Point(48.53, 48.53)); // Circular arc mid
656  CHECK_POINT_AND_MIRRORS(Point(49.87, 45.72)); // Right joint
657  CHECK_POINT_AND_MIRRORS(Point(49.95, 44.245)); // Right curve start
658  CHECK_POINT_AND_MIRRORS(Point(49.995, 0)); // Right
659 #undef CHECK_POINT_AND_MIRRORS
660 }
661 
662 TEST(RoundSuperellipseTest, UniformEllipticalContains) {
663  Rect bounds = Rect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f);
665  bounds, RoundingRadii::MakeRadii({5.0f, 10.0f}));
666 
667 #define CHECK_POINT_AND_MIRRORS(p) \
668  CHECK_POINT_WITH_OFFSET(rr, (p), Point(0.02, 0.02)); \
669  CHECK_POINT_WITH_OFFSET(rr, (p) * Point(1, -1), Point(0.02, -0.02)); \
670  CHECK_POINT_WITH_OFFSET(rr, (p) * Point(-1, 1), Point(-0.02, 0.02)); \
671  CHECK_POINT_WITH_OFFSET(rr, (p) * Point(-1, -1), Point(-0.02, -0.02));
672 
673  CHECK_POINT_AND_MIRRORS(Point(0, 49.995)); // Top
674  CHECK_POINT_AND_MIRRORS(Point(44.245, 49.911)); // Top curve start
675  CHECK_POINT_AND_MIRRORS(Point(45.72, 49.75)); // Top joint
676  CHECK_POINT_AND_MIRRORS(Point(48.51, 47.07)); // Circular arc mid
677  CHECK_POINT_AND_MIRRORS(Point(49.87, 41.44)); // Right joint
678  CHECK_POINT_AND_MIRRORS(Point(49.95, 38.49)); // Right curve start
679  CHECK_POINT_AND_MIRRORS(Point(49.995, 0)); // Right
680 #undef CHECK_POINT_AND_MIRRORS
681 }
682 
683 TEST(RoundSuperellipseTest, UniformRectangularContains) {
684  // The bounds is not centered at the origin and has unequal height and width.
685  Rect bounds = Rect::MakeLTRB(0.0f, 0.0f, 50.0f, 100.0f);
687  bounds, RoundingRadii::MakeRadii({23.0f, 30.0f}));
688 
689  Point center = bounds.GetCenter();
690 #define CHECK_POINT_AND_MIRRORS(p) \
691  CHECK_POINT_WITH_OFFSET(rr, (p - center) * Point(1, 1) + center, \
692  Point(0.02, 0.02)); \
693  CHECK_POINT_WITH_OFFSET(rr, (p - center) * Point(1, -1) + center, \
694  Point(0.02, -0.02)); \
695  CHECK_POINT_WITH_OFFSET(rr, (p - center) * Point(-1, 1) + center, \
696  Point(-0.02, 0.02)); \
697  CHECK_POINT_WITH_OFFSET(rr, (p - center) * Point(-1, -1) + center, \
698  Point(-0.02, -0.02));
699 
700  CHECK_POINT_AND_MIRRORS(Point(24.99, 99.99)); // Bottom mid edge
701  CHECK_POINT_AND_MIRRORS(Point(29.99, 99.64));
702  CHECK_POINT_AND_MIRRORS(Point(34.99, 98.06));
703  CHECK_POINT_AND_MIRRORS(Point(39.99, 94.73));
704  CHECK_POINT_AND_MIRRORS(Point(44.13, 89.99));
705  CHECK_POINT_AND_MIRRORS(Point(48.46, 79.99));
706  CHECK_POINT_AND_MIRRORS(Point(49.70, 69.99));
707  CHECK_POINT_AND_MIRRORS(Point(49.97, 59.99));
708  CHECK_POINT_AND_MIRRORS(Point(49.99, 49.99)); // Right mid edge
709 
710 #undef CHECK_POINT_AND_MIRRORS
711 }
712 
713 TEST(RoundSuperellipseTest, SlimDiagonalContains) {
714  // This shape has large radii on one diagonal and tiny radii on the other,
715  // resulting in a almond-like shape placed diagonally (NW to SE).
716  Rect bounds = Rect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f);
718  bounds, {
719  .top_left = Size(1.0, 1.0),
720  .top_right = Size(99.0, 99.0),
721  .bottom_left = Size(99.0, 99.0),
722  .bottom_right = Size(1.0, 1.0),
723  });
724 
725  EXPECT_TRUE(rr.Contains(Point{0, 0}));
726  EXPECT_FALSE(rr.Contains(Point{-49.999, -49.999}));
727  EXPECT_FALSE(rr.Contains(Point{-49.999, 49.999}));
728  EXPECT_FALSE(rr.Contains(Point{49.999, 49.999}));
729  EXPECT_FALSE(rr.Contains(Point{49.999, -49.999}));
730 
731  // The pointy ends at the NE and SW corners
732  CHECK_POINT_WITH_OFFSET(rr, Point(-49.70, -49.70), Point(-0.02, -0.02));
733  CHECK_POINT_WITH_OFFSET(rr, Point(49.70, 49.70), Point(0.02, 0.02));
734 
735 // Checks two points symmetrical to the origin.
736 #define CHECK_DIAGONAL_POINTS(p) \
737  CHECK_POINT_WITH_OFFSET(rr, (p), Point(0.02, -0.02)); \
738  CHECK_POINT_WITH_OFFSET(rr, (p) * Point(-1, -1), Point(-0.02, 0.02));
739 
740  // A few other points along the edge
741  CHECK_DIAGONAL_POINTS(Point(-40.0, -49.59));
742  CHECK_DIAGONAL_POINTS(Point(-20.0, -45.64));
743  CHECK_DIAGONAL_POINTS(Point(0.0, -37.01));
744  CHECK_DIAGONAL_POINTS(Point(20.0, -21.96));
745  CHECK_DIAGONAL_POINTS(Point(21.05, -20.92));
746  CHECK_DIAGONAL_POINTS(Point(40.0, 5.68));
747 #undef CHECK_POINT_AND_MIRRORS
748 }
749 
750 TEST(RoundSuperellipseTest, PointsOutsideOfSharpCorner) {
751  Rect bounds = Rect::MakeLTRB(196.0f, 0.0f, 294.0f, 28.0f);
752  // Regression test for a case where RoundSuperellipseParam::Contains
753  // previously failed. Although the bounding rect filter of
754  // `RoundSuperellipse::Contains` would reject this point, this test ensures
755  // the internal logic of RoundSuperellipseParam::Contains is now correct.
757  bounds, {
758  .top_left = Size(0.0, 0.0),
759  .top_right = Size(3.0, 3.0),
760  .bottom_left = Size(0.0, 0.0),
761  .bottom_right = Size(3.0, 3.0),
762  });
763 
764  EXPECT_FALSE(rr.Contains(Point{147.0, 14.0}));
765 }
766 
767 TEST(RoundSuperellipseTest,
768  PathForRectangularRseWithShapeCornersShouldBeWithinBounds) {
769  Rect bounds = Rect::MakeLTRB(34.0f, 242.0f, 766.0f, 358.0f);
770  // Regression test for https://github.com/flutter/flutter/issues/170593.
771  // The issue was caused by incorrect calculation when building paths for
772  // rounded superellipses with sharp corners and unequal width and height.
773  // Since the most obvious symptom of the issue is some points being
774  // incorrectly placed out of bounds, this test case simply verifies that all
775  // points are within the bounds.
776 
778  bounds, {
779  .top_left = Size(14.0, 14.0),
780  .top_right = Size(14.0, 14.0),
781  .bottom_left = Size(0.0, 0.0),
782  .bottom_right = Size(0.0, 0.0),
783  });
784  SpyPathReceiver receiver;
785  receiver.SpyLineTo(
786  [&](const Point& p2) { EXPECT_TRUE(bounds.ContainsInclusive(p2)); });
787  receiver.SpyCubicTo([&](const Point& cp1, const Point& cp2, const Point& p2) {
788  EXPECT_TRUE(bounds.ContainsInclusive(p2));
789  });
790 
791  rr.Dispatch(receiver);
792 }
793 
794 TEST(RoundSuperellipseTest, PathForLongRseShouldBeCorrect) {
795  Rect bounds = Rect::MakeLTRB(0, 0, 300, 100000);
796  // Regression test for https://github.com/flutter/flutter/issues/179875.
797 
798  auto rr = RoundSuperellipseParam::MakeBoundsRadius(bounds, 100);
799  SpyPathReceiver receiver;
800  receiver.SpyLineTo(
801  [&](const Point& p2) { EXPECT_TRUE(bounds.ContainsInclusive(p2)); });
802  receiver.SpyCubicTo([&](const Point& cp1, const Point& cp2, const Point& p2) {
803  EXPECT_TRUE(bounds.ContainsInclusive(cp1));
804  EXPECT_TRUE(bounds.ContainsInclusive(cp2));
805  EXPECT_TRUE(bounds.ContainsInclusive(p2));
806  });
807  receiver.SpyConicTo([&](const Point& cp, const Point& p2, Scalar weight) {
808  EXPECT_TRUE(bounds.ContainsInclusive(cp));
809  EXPECT_TRUE(bounds.ContainsInclusive(p2));
810  });
811 
812  rr.Dispatch(receiver);
813 }
814 
815 } // namespace testing
816 } // namespace impeller
TEST(AllocationSizeTest, CanCreateTypedAllocations)
float Scalar
Definition: scalar.h:19
TPoint< Scalar > Point
Definition: point.h:425
TSize< Scalar > Size
Definition: size.h:159
void MoveTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:20
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:24
void CubicTo(PathBuilder *builder, Scalar x1, Scalar y1, Scalar x2, Scalar y2, Scalar x3, Scalar y3)
Definition: tessellator.cc:28
void Close(PathBuilder *builder)
Definition: tessellator.cc:38
Scalar weight
#define CHECK_DIAGONAL_POINTS(p)
#define CHECK_POINT_WITH_OFFSET(rr, p, outward_offset)
#define CHECK_POINT_AND_MIRRORS(p)
constexpr const RoundingRadii & GetRadii() const
static RoundSuperellipse MakeRectRadii(const Rect &rect, const RoundingRadii &radii)
constexpr bool IsOval() const
constexpr bool IsFinite() const
constexpr bool IsEmpty() const
RoundSuperellipse Expand(Scalar left, Scalar top, Scalar right, Scalar bottom) const
Returns a round rectangle with expanded edges. Negative expansion results in shrinking.
constexpr bool IsRect() const
static RoundSuperellipse MakeOval(const Rect &rect)
RoundSuperellipse Shift(Scalar dx, Scalar dy) const
Returns a new round rectangle translated by the given offset.
static RoundSuperellipse MakeRect(const Rect &rect)
static RoundSuperellipse MakeRectRadius(const Rect &rect, Scalar radius)
constexpr const Rect & GetBounds() const
static RoundSuperellipse MakeRectXY(const Rect &rect, Scalar x_radius, Scalar y_radius)
static RoundSuperellipseParam MakeBoundsRadii(const Rect &bounds, const RoundingRadii &radii)
void Dispatch(PathReceiver &receiver) const
static RoundSuperellipseParam MakeBoundsRadius(const Rect &bounds, Scalar radius)
constexpr static RoundingRadii MakeRadii(Size radii)
constexpr auto GetBottom() const
Definition: rect.h:357
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:250
constexpr auto GetTop() const
Definition: rect.h:353
constexpr bool IsEmpty() const
Returns true if either of the width or height are 0, negative, or NaN.
Definition: rect.h:297
constexpr auto GetLeft() const
Definition: rect.h:351
constexpr auto GetRight() const
Definition: rect.h:355
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition: rect.h:382
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
Type height
Definition: size.h:29
Type width
Definition: size.h:28