Flutter Impeller
round_superellipse_param.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 
6 
7 namespace impeller {
8 
9 namespace {
10 
11 // Return the value that splits the range from `left` to `right` into two
12 // portions whose ratio equals to `ratio_left` : `ratio_right`.
13 Scalar Split(Scalar left, Scalar right, Scalar ratio_left, Scalar ratio_right) {
14  if (ratio_left == 0 && ratio_right == 0) {
15  return (left + right) / 2;
16  }
17  return (left * ratio_right + right * ratio_left) / (ratio_left + ratio_right);
18 }
19 
20 // Return the same Point, but each NaN coordinate is replaced by that of
21 // `default_size`.
22 inline Point ReplanceNaNWithDefault(Point in, Size default_size) {
23  return Point{std::isnan(in.x) ? default_size.width : in.x,
24  std::isnan(in.y) ? default_size.height : in.y};
25 }
26 
27 // Swap the x and y coordinate of a point.
28 //
29 // Effectively mirrors the point by the y=x line.
30 inline Point Flip(Point a) {
31  return Point{a.y, a.x};
32 }
33 
34 // A look up table with precomputed variables.
35 //
36 // The columns represent the following variabls respectively:
37 //
38 // * n
39 // * k_xJ, which is defined as 1 / (1 - xJ / a)
40 //
41 // For definition of the variables, see ComputeOctant.
42 constexpr Scalar kPrecomputedVariables[][2] = {
43  /*ratio=2.00*/ {2.00000000, 1.13276676},
44  /*ratio=2.10*/ {2.18349805, 1.20311921},
45  /*ratio=2.20*/ {2.33888662, 1.28698796},
46  /*ratio=2.30*/ {2.48660575, 1.36351941},
47  /*ratio=2.40*/ {2.62226596, 1.44717976},
48  /*ratio=2.50*/ {2.75148990, 1.53385819},
49  /*ratio=3.00*/ {3.36298265, 1.98288283},
50  /*ratio=3.50*/ {4.08649929, 2.23811846},
51  /*ratio=4.00*/ {4.85481134, 2.47563463},
52  /*ratio=4.50*/ {5.62945551, 2.72948597},
53  /*ratio=5.00*/ {6.43023796, 2.98020421}};
54 
55 constexpr Scalar kMinRatio = 2.00;
56 
57 // The curve is split into 3 parts:
58 // * The first part uses a denser look up table.
59 // * The second part uses a sparser look up table.
60 // * The third part uses a straight line.
61 constexpr Scalar kFirstStepInverse = 10; // = 1 / 0.10
62 constexpr Scalar kFirstMaxRatio = 2.50;
63 constexpr Scalar kFirstNumRecords = 6;
64 
65 constexpr Scalar kSecondStepInverse = 2; // = 1 / 0.50
66 constexpr Scalar kSecondMaxRatio = 5.00;
67 
68 constexpr Scalar kThirdNSlope = 1.559599389;
69 constexpr Scalar kThirdKxjSlope = 0.522807185;
70 
71 constexpr size_t kNumRecords =
72  sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]);
73 
74 // Compute the `n` and `xJ / a` for the given ratio.
75 std::array<Scalar, 2> ComputeNAndXj(Scalar ratio) {
76  if (ratio > kSecondMaxRatio) {
77  Scalar n = kThirdNSlope * (ratio - kSecondMaxRatio) +
78  kPrecomputedVariables[kNumRecords - 1][0];
79  Scalar k_xJ = kThirdKxjSlope * (ratio - kSecondMaxRatio) +
80  kPrecomputedVariables[kNumRecords - 1][1];
81  return {n, 1 - 1 / k_xJ};
82  }
83  ratio = std::clamp(ratio, kMinRatio, kSecondMaxRatio);
84  Scalar steps;
85  if (ratio < kFirstMaxRatio) {
86  steps = (ratio - kMinRatio) * kFirstStepInverse;
87  } else {
88  steps =
89  (ratio - kFirstMaxRatio) * kSecondStepInverse + kFirstNumRecords - 1;
90  }
91 
92  size_t left = std::clamp<size_t>(static_cast<size_t>(std::floor(steps)), 0,
93  kNumRecords - 2);
94  Scalar frac = steps - left;
95 
96  Scalar n = (1 - frac) * kPrecomputedVariables[left][0] +
97  frac * kPrecomputedVariables[left + 1][0];
98  Scalar k_xJ = (1 - frac) * kPrecomputedVariables[left][1] +
99  frac * kPrecomputedVariables[left + 1][1];
100  return {n, 1 - 1 / k_xJ};
101 }
102 
103 // Find the center of the circle that passes the given two points and have the
104 // given radius.
105 Point FindCircleCenter(Point a, Point b, Scalar r) {
106  /* Denote the middle point of A and B as M. The key is to find the center of
107  * the circle.
108  * A --__
109  * / ⟍ `、
110  * / M ⟍\
111  * / ⟋ B
112  * / ⟋ ↗
113  * / ⟋
114  * / ⟋ r
115  * C ᜱ ↙
116  */
117 
118  Point a_to_b = b - a;
119  Point m = (a + b) / 2;
120  Point c_to_m = Point(-a_to_b.y, a_to_b.x);
121  Scalar distance_am = a_to_b.GetLength() / 2;
122  Scalar distance_cm = sqrt(r * r - distance_am * distance_am);
123  return m - distance_cm * c_to_m.Normalize();
124 }
125 
126 // Compute parameters for a square-like rounded superellipse with a symmetrical
127 // radius.
128 RoundSuperellipseParam::Octant ComputeOctant(Point center,
129  Scalar a,
130  Scalar radius) {
131  /* The following figure shows the first quadrant of a square-like rounded
132  * superellipse.
133  *
134  * superelipse
135  * A ↓ circular arc
136  * ---------...._J ↙
137  * | / `⟍ M (where x=y)
138  * | / ⟋ ⟍
139  * | / ⟋ \
140  * | / ⟋ |
141  * | ᜱD |
142  * | ⟋ |
143  * | ⟋ |
144  * |⟋ |
145  * +--------------------| A'
146  * O
147  * ←-------- a ---------→
148  */
149 
150  if (radius <= kEhCloseEnough) {
151  // Corners with really small radii are treated as sharp corners, since they
152  // might lead to NaNs due to `ratio` being too large.
153  return RoundSuperellipseParam::Octant{
154  .offset = center,
155 
156  .se_a = a,
157  .se_n = 0,
158 
159  .circle_start = {a, a},
160  };
161  }
162 
163  Scalar ratio = a * 2 / radius;
165 
166  auto precomputed_vars = ComputeNAndXj(ratio);
167  Scalar n = precomputed_vars[0];
168  Scalar xJ = precomputed_vars[1] * a;
169  Scalar yJ = pow(1 - pow(precomputed_vars[1], n), 1 / n) * a;
170  Scalar max_theta = asinf(pow(precomputed_vars[1], n / 2));
171 
172  Scalar tan_phiJ = pow(xJ / yJ, n - 1);
173  Scalar d = (xJ - tan_phiJ * yJ) / (1 - tan_phiJ);
174  Scalar R = (a - d - g) * sqrt(2);
175 
176  Point pointM{a - g, a - g};
177  Point pointJ = Point{xJ, yJ};
178  Point circle_center =
179  radius == 0 ? pointM : FindCircleCenter(pointJ, pointM, R);
180  Radians circle_max_angle =
181  radius == 0 ? Radians(0)
182  : (pointM - circle_center).AngleTo(pointJ - circle_center);
183 
184  return RoundSuperellipseParam::Octant{
185  .offset = center,
186 
187  .se_a = a,
188  .se_n = n,
189  .se_max_theta = max_theta,
190 
191  .circle_start = pointJ,
192  .circle_center = circle_center,
193  .circle_max_angle = circle_max_angle,
194  .circle_radius = R,
195  };
196 }
197 
198 // Compute parameters for a quadrant of a rounded superellipse with asymmetrical
199 // radii.
200 //
201 // The `corner` is the coordinate of the corner point in the same coordinate
202 // space as `center`, which specifies the half size of the bounding box.
203 //
204 // The `sign` is a vector of {±1, ±1} that specifies which quadrant the curve
205 // should be, which should have the same sign as `corner - center` except that
206 // the latter may have a 0.
207 RoundSuperellipseParam::Quadrant ComputeQuadrant(Point center,
208  Point corner,
209  Size in_radii,
210  Size sign) {
211  Point corner_vector = corner - center;
212  Size radii = {std::min(std::abs(in_radii.width), std::abs(corner_vector.x)),
213  std::min(std::abs(in_radii.height), std::abs(corner_vector.y))};
214 
215  // The prefix "norm" is short for "normalized", meaning a rounded superellipse
216  // that has a uniform radius. The quadrant is scaled from this normalized one.
217  //
218  // Be extra careful to avoid NaNs in cases that some coordinates of `in_radii`
219  // or `corner_vector` are zero.
220  Scalar norm_radius = radii.MinDimension();
221  Size forward_scale = norm_radius == 0 ? Size{1, 1} : radii / norm_radius;
222  Point norm_half_size = corner_vector.Abs() / forward_scale;
223  Point signed_scale =
224  ReplanceNaNWithDefault(corner_vector / norm_half_size, sign);
225 
226  // Each quadrant curve is composed of two octant curves, each of which belongs
227  // to a square-like rounded rectangle. For the two octants to connect at the
228  // circular arc, the centers these two square-like rounded rectangle must be
229  // offset from the quadrant center by a same distance in different directions.
230  // The distance is denoted as `c`.
231  Scalar c = norm_half_size.x - norm_half_size.y;
232 
233  return RoundSuperellipseParam::Quadrant{
234  .offset = center,
235  .signed_scale = signed_scale,
236  .top = ComputeOctant(Point{0, -c}, norm_half_size.x, norm_radius),
237  .right = ComputeOctant(Point{c, 0}, norm_half_size.y, norm_radius),
238  };
239 }
240 
241 // Checks whether the given point is contained in the first octant of the given
242 // square-like rounded superellipse.
243 //
244 // The first octant refers to the region that spans from 0 to pi/4 starting from
245 // positive Y axis clockwise.
246 //
247 // If the point is not within this octant at all, then this function always
248 // returns true. Otherwise this function returns whether the point is contained
249 // within the rounded superellipse.
250 //
251 // The `param.offset` is ignored. The input point should have been transformed
252 // to the coordinate space where the rounded superellipse is centered at the
253 // origin.
254 bool OctantContains(const RoundSuperellipseParam::Octant& param,
255  const Point& p) {
256  // Check whether the point is within the octant.
257  if (p.x < 0 || p.y < 0 || p.y < p.x) {
258  return true;
259  }
260  // Check if the point is within the superellipsoid segment.
261  if (p.x <= param.circle_start.x) {
262  Point p_se = p / param.se_a;
263  return powf(p_se.x, param.se_n) + powf(p_se.y, param.se_n) <= 1;
264  }
265  Scalar circle_radius =
266  param.circle_start.GetDistanceSquared(param.circle_center);
267  Point p_circle = p - param.circle_center;
268  return p_circle.GetDistanceSquared(Point()) < circle_radius;
269 }
270 
271 // Determine if p is inside the corner curve defined by the indicated corner
272 // param.
273 //
274 // The coordinates of p should be within the same coordinate space with
275 // `param.offset`.
276 //
277 // If `check_quadrant` is true, then this function first checks if the point is
278 // within the quadrant of given corner. If not, this function returns true,
279 // otherwise this method continues to check whether the point is contained in
280 // the rounded superellipse.
281 //
282 // If `check_quadrant` is false, then the first step above is skipped, and the
283 // function checks whether the absolute (relative to the center) coordinate of p
284 // is contained in the rounded superellipse.
285 bool CornerContains(const RoundSuperellipseParam::Quadrant& param,
286  const Point& p,
287  bool check_quadrant = true) {
288  Point norm_point = (p - param.offset) / param.signed_scale;
289  if (check_quadrant) {
290  if (norm_point.x < 0 || norm_point.y < 0) {
291  return true;
292  }
293  } else {
294  norm_point = norm_point.Abs();
295  }
296  if (param.top.se_n < 2 || param.right.se_n < 2) {
297  // A rectangular corner. The top and left sides contain the borders
298  // while the bottom and right sides don't (see `Rect.contains`).
299  Scalar x_delta = param.right.offset.x + param.right.se_a - norm_point.x;
300  Scalar y_delta = param.top.offset.y + param.top.se_a - norm_point.y;
301  bool x_within = x_delta > 0 || (x_delta == 0 && param.signed_scale.x < 0);
302  bool y_within = y_delta > 0 || (y_delta == 0 && param.signed_scale.y < 0);
303  return x_within && y_within;
304  }
305  return OctantContains(param.top, norm_point - param.top.offset) &&
306  OctantContains(param.right, Flip(norm_point - param.right.offset));
307 }
308 
309 class RoundSuperellipseBuilder {
310  private:
311  // The parameters that describes a conic curve.
312  struct ConicParam {
313  // The end point closer to point A.
315  // The control point.
317  // The end point closer to point J.
319  // The conic weight.
321  };
322 
323  public:
324  explicit RoundSuperellipseBuilder(PathReceiver& receiver)
325  : receiver_(receiver) {}
326 
327  // Draws an arc representing 1/4 of a rounded superellipse.
328  //
329  // If `reverse` is false, the resulting arc spans from 0 to pi/2, moving
330  // clockwise starting from the positive Y-axis. Otherwise it moves from pi/2
331  // to 0.
332  //
333  // The `scale_sign` is an additional scaling transformation that potentially
334  // flips the result. This is useful for uniform radii where the same quadrant
335  // parameter set should be drawn to 4 quadrants.
336  void AddQuadrant(const RoundSuperellipseParam::Quadrant& param,
337  bool reverse,
338  Point scale_sign = Point(1, 1)) {
339  auto transform = Matrix::MakeTranslateScale(param.signed_scale * scale_sign,
340  param.offset);
341  if (param.top.se_n < 2 || param.right.se_n < 2) {
342  receiver_.LineTo(transform * (param.top.offset +
343  Point(param.top.se_a, param.top.se_a)));
344  if (!reverse) {
345  receiver_.LineTo(transform *
346  (param.right.offset + Point(param.right.se_a, 0)));
347  } else {
348  receiver_.LineTo(transform *
349  (param.top.offset + Point(0, param.top.se_a)));
350  }
351  return;
352  }
353  if (!reverse) {
354  AddOctant(param.top, /*reverse=*/false, /*flip=*/false, transform);
355  AddOctant(param.right, /*reverse=*/true, /*flip=*/true, transform);
356  } else {
357  AddOctant(param.right, /*reverse=*/false, /*flip=*/true, transform);
358  AddOctant(param.top, /*reverse=*/true, /*flip=*/false, transform);
359  }
360  }
361 
362  private:
363  std::tuple<ConicParam, ConicParam> SuperellipseArcPoints(
364  const RoundSuperellipseParam::Octant& param) {
365  // The superellipse arc consists of two conic curves: A-H and H-J.
366  Point A = {0, param.se_a};
367  const Point& J = param.circle_start;
368 
369  std::tuple<Scalar, Scalar, Scalar> factors = SuperellipseBezierFactors(
370  param.se_n, J.x / param.se_a, J.y / param.se_a);
371  Scalar weight1 = std::get<0>(factors);
372  Scalar weight2 = std::get<1>(factors);
373  Scalar yHOverA = std::get<2>(factors);
374 
375  Point H = {
376  powf(1.f - powf(yHOverA, param.se_n), 1.f / param.se_n) * param.se_a,
377  yHOverA * param.se_a};
378 
379  Scalar kA = 0;
380  Scalar kJ = -powf(J.x / J.y, param.se_n - 1.f);
381  Scalar kH = -powf(H.x / H.y, param.se_n - 1.f);
382 
383  // The control points are determined by the intersection of the tangents for
384  // smoothness.
385  return {ConicParam{.p1 = A,
386  .c = Intersection(A, kA, H, kH),
387  .p2 = H,
388  .weight = weight1},
389  ConicParam{.p1 = H,
390  .c = Intersection(H, kH, J, kJ),
391  .p2 = J,
392  .weight = weight2}};
393  }
394 
395  std::array<Point, 4> CircularArcPoints(
396  const RoundSuperellipseParam::Octant& param) {
397  Point start_vector = param.circle_start - param.circle_center;
398  Point end_vector =
399  start_vector.Rotate(Radians(-param.circle_max_angle.radians));
400  Point circle_end = param.circle_center + end_vector;
401  Point start_tangent = Point{start_vector.y, -start_vector.x}.Normalize();
402  Point end_tangent = Point{-end_vector.y, end_vector.x}.Normalize();
403  Scalar bezier_factor = std::tan(param.circle_max_angle.radians / 4) * 4 / 3;
404  Scalar radius = start_vector.GetLength();
405 
406  return std::array<Point, 4>{
407  param.circle_start,
408  param.circle_start + start_tangent * bezier_factor * radius,
409  circle_end + end_tangent * bezier_factor * radius, circle_end};
410  };
411 
412  // Draws an arc representing 1/8 of a rounded superellipse.
413  //
414  // If `reverse` is false, the resulting arc spans from 0 to pi/4, moving
415  // clockwise starting from the positive Y-axis. Otherwise it moves from pi/4
416  // to 0.
417  //
418  // If `flip` is true, all points have their X and Y coordinates swapped,
419  // effectively mirrowing each point by the y=x line.
420  //
421  // All points are transformed by `external_transform` after the optional
422  // flipping before being used as control points for the cubic curves.
423  void AddOctant(const RoundSuperellipseParam::Octant& param,
424  bool reverse,
425  bool flip,
426  const Matrix& external_transform) {
427  Matrix transform =
428  external_transform * Matrix::MakeTranslation(param.offset);
429  if (flip) {
430  transform = transform * kFlip;
431  }
432 
433  auto circle_points = CircularArcPoints(param);
434  auto se_conics = SuperellipseArcPoints(param);
435 
436  if (!reverse) {
437  receiver_.ConicTo(transform * std::get<0>(se_conics).c,
438  transform * std::get<0>(se_conics).p2,
439  std::get<0>(se_conics).weight);
440  receiver_.ConicTo(transform * std::get<1>(se_conics).c,
441  transform * std::get<1>(se_conics).p2,
442  std::get<1>(se_conics).weight);
443  receiver_.CubicTo(transform * circle_points[1],
444  transform * circle_points[2],
445  transform * circle_points[3]);
446  } else {
447  receiver_.CubicTo(transform * circle_points[2],
448  transform * circle_points[1],
449  transform * circle_points[0]);
450  receiver_.ConicTo(transform * std::get<1>(se_conics).c,
451  transform * std::get<1>(se_conics).p1,
452  std::get<1>(se_conics).weight);
453  receiver_.ConicTo(transform * std::get<0>(se_conics).c,
454  transform * std::get<0>(se_conics).p1,
455  std::get<0>(se_conics).weight);
456  }
457  };
458 
459  // Get the Bezier factor for the superellipse arc in a rounded superellipse.
460  //
461  // The resulting tuple consists of:
462  // [0] weight1, weight for the conic A-H
463  // [1] weight2, weight for the conic H-J
464  // [2] yHOverA
465  std::tuple<Scalar, Scalar, Scalar> SuperellipseBezierFactors(Scalar n,
466  Scalar xJOverA,
467  Scalar yJOverA) {
468  // Precomputed (factor1, factor2) tuples for interpolation and
469  // extrapolation.
470  //
471  // These factors are normalized conic weights defined as:
472  // weight1 = factor1 * sqrt(n)
473  // weight2 = factor2 * xJOverA
474  //
475  // Rationale: Empirical analysis shows these quotients converge to stable
476  // constants as n -> infinity. Normalizing by sqrt(n) and xJOverA ensures
477  // the table remains well-behaved for linear extrapolation when 'n' exceeds
478  // the precomputed range.
479  //
480  // Optimal weights were originally derived via brute-force search for
481  // minimal curve distance on a rounded superellipse.
482  constexpr Scalar kPrecomputedVariables[][2] = {/*n= 2.0*/ {0.7078, 8.3194},
483  /*n= 3.0*/ {0.7895, 2.4523},
484  /*n= 4.0*/ {0.8379, 1.8528},
485  /*n= 5.0*/ {0.8701, 1.6891},
486  /*n= 6.0*/ {0.8932, 1.5806},
487  /*n= 7.0*/ {0.9107, 1.5043},
488  /*n= 8.0*/ {0.9244, 1.4470},
489  /*n= 9.0*/ {0.9355, 1.4037},
490  /*n=10.0*/ {0.9448, 1.3701},
491  /*n=11.0*/ {0.9526, 1.3431},
492  /*n=12.0*/ {0.9594, 1.3212},
493  /*n=13.0*/ {0.9653, 1.3032},
494  /*n=14.0*/ {0.9705, 1.2880}};
495  constexpr size_t kNumRecords =
496  sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]);
497  constexpr Scalar kStep = 1.00f;
498  constexpr Scalar kMinN = 2.00f;
499  constexpr Scalar kMaxN = kMinN + (kNumRecords - 1) * kStep;
500 
501  if (n >= kMaxN) {
502  // The optimized factors stabilize as n grows large.
503  n = kMaxN;
504  }
505 
506  // Compute weight1 and weight 2
507 
508  Scalar steps = std::clamp<Scalar>((n - kMinN) / kStep, 0, kNumRecords - 1);
509  size_t left = std::clamp<size_t>(static_cast<size_t>(std::floor(steps)), 0,
510  kNumRecords - 2);
511  Scalar frac = steps - left;
512 
513  Scalar weight1 = (1.f - frac) * kPrecomputedVariables[left][0] +
514  frac * kPrecomputedVariables[left + 1][0] * sqrt(n);
515  Scalar weight2 = (1.f - frac) * kPrecomputedVariables[left][1] +
516  frac * kPrecomputedVariables[left + 1][1] * xJOverA;
517 
518  // Compute yHOverA
519 
520  // H, the splitting point between the two conic curves, is picked by its y
521  // coordinate proportionally between A and J.
522  //
523  // The proportion between (yA-yH) and (yH-yJ) is positively correlated to n,
524  // so that when n increases, H moves closer to A. This is because the
525  // flatter segment of the superellipse curve is much harder to approximate.
526  // The exact formula of sqrt(n) is found empirically.
527  Scalar yH_proportion = sqrt(n);
528  constexpr Scalar yAOverA = 1.0;
529  Scalar yHOverA =
530  (yAOverA * yH_proportion + yJOverA) / (yH_proportion + 1.0f);
531 
532  return {weight1, weight2, yHOverA};
533  }
534 
535  // Find the intersection point of two lines defined by two points and their
536  // slopes.
537  static Point Intersection(Point p1, Scalar k1, Point p2, Scalar k2) {
538  if (std::fabs(k1 - k2) < kEhCloseEnough) {
539  return (p1 + p2) / 2;
540  }
541 
542  // Line 1: y - y1 = k1(x - x1)
543  // Line 2: y - y2 = k2(x - x2)
544  // => x = (k1*x1 - k2*x2 + y2 - y1) / (k1 - k2)
545 
546  Scalar x = (k1 * p1.x - k2 * p2.x + p2.y - p1.y) / (k1 - k2);
547  Scalar y = k1 * (x - p1.x) + p1.y;
548 
549  return Point(x, y);
550  }
551 
552  PathReceiver& receiver_;
553 
554  // A matrix that swaps the coordinates of a point.
555  // clang-format off
556  static constexpr Matrix kFlip = Matrix(
557  0.0f, 1.0f, 0.0f, 0.0f,
558  1.0f, 0.0f, 0.0f, 0.0f,
559  0.0f, 0.0f, 1.0f, 0.0f,
560  0.0f, 0.0f, 0.0f, 1.0f);
561  // clang-format on
562 };
563 
564 } // namespace
565 
567  const Rect& bounds,
568  Scalar radius) {
569  return RoundSuperellipseParam{
570  .top_right = ComputeQuadrant(bounds.GetCenter(), bounds.GetRightTop(),
571  {radius, radius}, {-1, 1}),
572  .all_corners_same = true,
573  };
574 }
575 
577  const Rect& bounds,
578  const RoundingRadii& radii) {
579  if (radii.AreAllCornersSame() && !radii.top_left.IsEmpty()) {
580  // Having four empty corners indicate a rectangle, which needs special
581  // treatment on border containment and therefore is not `all_corners_same`.
582  return RoundSuperellipseParam{
583  .top_right = ComputeQuadrant(bounds.GetCenter(), bounds.GetRightTop(),
584  radii.top_right, {-1, 1}),
585  .all_corners_same = true,
586  .top_split = bounds.GetCenter().x,
587  .bottom_split = bounds.GetCenter().x,
588  .left_split = bounds.GetCenter().y,
589  .right_split = bounds.GetCenter().y,
590  };
591  }
592  Scalar top_split = Split(bounds.GetLeft(), bounds.GetRight(),
593  radii.top_left.width, radii.top_right.width);
594  Scalar right_split = Split(bounds.GetTop(), bounds.GetBottom(),
595  radii.top_right.height, radii.bottom_right.height);
597  Split(bounds.GetLeft(), bounds.GetRight(), radii.bottom_left.width,
598  radii.bottom_right.width);
599  Scalar left_split = Split(bounds.GetTop(), bounds.GetBottom(),
600  radii.top_left.height, radii.bottom_left.height);
601 
602  return RoundSuperellipseParam{
603  .top_right =
604  ComputeQuadrant(Point{top_split, right_split}, bounds.GetRightTop(),
605  radii.top_right, {1, -1}),
606  .bottom_right =
607  ComputeQuadrant(Point{bottom_split, right_split},
608  bounds.GetRightBottom(), radii.bottom_right, {1, 1}),
609  .bottom_left =
610  ComputeQuadrant(Point{bottom_split, left_split},
611  bounds.GetLeftBottom(), radii.bottom_left, {-1, 1}),
612  .top_left =
613  ComputeQuadrant(Point{top_split, left_split}, bounds.GetLeftTop(),
614  radii.top_left, {-1, -1}),
615  .all_corners_same = false,
616  .top_split = top_split,
617  .bottom_split = bottom_split,
618  .left_split = left_split,
619  .right_split = right_split,
620  };
621 }
622 
623 void RoundSuperellipseParam::Dispatch(PathReceiver& path_receiver) const {
624  RoundSuperellipseBuilder builder(path_receiver);
625 
629  path_receiver.MoveTo(start, true);
630 
631  if (all_corners_same) {
632  builder.AddQuadrant(top_right, /*reverse=*/false, Point(1, 1));
633  builder.AddQuadrant(top_right, /*reverse=*/true, Point(1, -1));
634  builder.AddQuadrant(top_right, /*reverse=*/false, Point(-1, -1));
635  builder.AddQuadrant(top_right, /*reverse=*/true, Point(-1, 1));
636  } else {
637  builder.AddQuadrant(top_right, /*reverse=*/false);
638  builder.AddQuadrant(bottom_right, /*reverse=*/true);
639  builder.AddQuadrant(bottom_left, /*reverse=*/false);
640  builder.AddQuadrant(top_left, /*reverse=*/true);
641  }
642 
643  path_receiver.LineTo(start);
644  path_receiver.Close();
645 }
646 
647 bool RoundSuperellipseParam::Contains(const Point& point) const {
648  if (all_corners_same) {
649  return CornerContains(top_right, point, /*check_quadrant=*/false);
650  }
651  return CornerContains(top_right, point) &&
652  CornerContains(bottom_right, point) &&
654 }
655 
656 } // namespace impeller
Collection of functions to receive path segments from the underlying path representation via the DlPa...
Definition: path_source.h:42
virtual void LineTo(const Point &p2)=0
virtual void Close()=0
virtual void MoveTo(const Point &p2, bool will_be_closed)=0
int32_t x
float Scalar
Definition: scalar.h:19
constexpr float kEhCloseEnough
Definition: constants.h:57
static bool CornerContains(const Point &p, const Point &corner, const Point &direction, const Size &radii)
Definition: round_rect.cc:30
TPoint< Scalar > Point
Definition: point.h:426
TSize< Scalar > Size
Definition: size.h:159
Scalar weight
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
static constexpr Matrix MakeTranslateScale(const Vector3 &s, const Vector3 &t)
Definition: matrix.h:113
static RoundSuperellipseParam MakeBoundsRadii(const Rect &bounds, const RoundingRadii &radii)
void Dispatch(PathReceiver &receiver) const
bool Contains(const Point &point) const
static RoundSuperellipseParam MakeBoundsRadius(const Rect &bounds, Scalar radius)
constexpr bool AreAllCornersSame(Scalar tolerance=kEhCloseEnough) const
constexpr TPoint Abs() const
Definition: point.h:294
constexpr TPoint Rotate(const Radians &angle) const
Definition: point.h:315
constexpr Type GetDistanceSquared(const TPoint &p) const
Definition: point.h:181
constexpr auto GetBottom() const
Definition: rect.h:391
constexpr auto GetTop() const
Definition: rect.h:387
constexpr auto GetLeft() const
Definition: rect.h:385
constexpr auto GetRight() const
Definition: rect.h:389
constexpr TPoint< T > GetLeftBottom() const
Definition: rect.h:401
constexpr TPoint< T > GetRightTop() const
Definition: rect.h:397
constexpr TPoint< T > GetRightBottom() const
Definition: rect.h:405
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition: rect.h:416
constexpr TPoint< T > GetLeftTop() const
Definition: rect.h:393
Type height
Definition: size.h:29
Type width
Definition: size.h:28
constexpr bool IsEmpty() const
Returns true if either of the width or height are 0, negative, or NaN.
Definition: size.h:123
const size_t start