| 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
5 #include "flutter/impeller/geometry/round_superellipse_param.h"
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;
164 Scalar g = RoundSuperellipseParam::kGapFactor * 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 };
195 }
196
197 // Compute parameters for a quadrant of a rounded superellipse with asymmetrical
198 // radii.
199 //
200 // The `corner` is the coordinate of the corner point in the same coordinate
201 // space as `center`, which specifies the half size of the bounding box.
202 //
203 // The `sign` is a vector of {±1, ±1} that specifies which quadrant the curve
204 // should be, which should have the same sign as `corner - center` except that
205 // the latter may have a 0.
206 RoundSuperellipseParam::Quadrant ComputeQuadrant(Point center,
207 Point corner,
208 Size in_radii,
209 Size sign) {
210 Point corner_vector = corner - center;
211 Size radii = {std::min(std::abs(in_radii.width), std::abs(corner_vector.x)),
212 std::min(std::abs(in_radii.height), std::abs(corner_vector.y))};
213
214 // The prefix "norm" is short for "normalized", meaning a rounded superellipse
215 // that has a uniform radius. The quadrant is scaled from this normalized one.
216 //
217 // Be extra careful to avoid NaNs in cases that some coordinates of `in_radii`
218 // or `corner_vector` are zero.
219 Scalar norm_radius = radii.MinDimension();
220 Size forward_scale = norm_radius == 0 ? Size{1, 1} : radii / norm_radius;
221 Point norm_half_size = corner_vector.Abs() / forward_scale;
222 Point signed_scale =
223 ReplanceNaNWithDefault(corner_vector / norm_half_size, sign);
224
225 // Each quadrant curve is composed of two octant curves, each of which belongs
226 // to a square-like rounded rectangle. For the two octants to connect at the
227 // circular arc, the centers these two square-like rounded rectangle must be
228 // offset from the quadrant center by a same distance in different directions.
229 // The distance is denoted as `c`.
230 Scalar c = norm_half_size.x - norm_half_size.y;
231
232 return RoundSuperellipseParam::Quadrant{
233 .offset = center,
234 .signed_scale = signed_scale,
235 .top = ComputeOctant(Point{0, -c}, norm_half_size.x, norm_radius),
236 .right = ComputeOctant(Point{c, 0}, norm_half_size.y, norm_radius),
237 };
238 }
239
240 // Checks whether the given point is contained in the first octant of the given
241 // square-like rounded superellipse.
242 //
243 // The first octant refers to the region that spans from 0 to pi/4 starting from
244 // positive Y axis clockwise.
245 //
246 // If the point is not within this octant at all, then this function always
247 // returns true. Otherwise this function returns whether the point is contained
248 // within the rounded superellipse.
249 //
250 // The `param.offset` is ignored. The input point should have been transformed
251 // to the coordinate space where the rounded superellipse is centered at the
252 // origin.
253 bool OctantContains(const RoundSuperellipseParam::Octant& param,
254 const Point& p) {
255 // Check whether the point is within the octant.
256 if (p.x < 0 || p.y < 0 || p.y < p.x) {
257 return true;
258 }
259 // Check if the point is within the superellipsoid segment.
260 if (p.x <= param.circle_start.x) {
261 Point p_se = p / param.se_a;
262 return powf(p_se.x, param.se_n) + powf(p_se.y, param.se_n) <= 1;
263 }
264 Scalar circle_radius =
265 param.circle_start.GetDistanceSquared(param.circle_center);
266 Point p_circle = p - param.circle_center;
267 return p_circle.GetDistanceSquared(Point()) < circle_radius;
268 }
269
270 // Determine if p is inside the corner curve defined by the indicated corner
271 // param.
272 //
273 // The coordinates of p should be within the same coordinate space with
274 // `param.offset`.
275 //
276 // If `check_quadrant` is true, then this function first checks if the point is
277 // within the quadrant of given corner. If not, this function returns true,
278 // otherwise this method continues to check whether the point is contained in
279 // the rounded superellipse.
280 //
281 // If `check_quadrant` is false, then the first step above is skipped, and the
282 // function checks whether the absolute (relative to the center) coordinate of p
283 // is contained in the rounded superellipse.
284 bool CornerContains(const RoundSuperellipseParam::Quadrant& param,
285 const Point& p,
286 bool check_quadrant = true) {
287 Point norm_point = (p - param.offset) / param.signed_scale;
288 if (check_quadrant) {
289 if (norm_point.x < 0 || norm_point.y < 0) {
290 return true;
291 }
292 } else {
293 norm_point = norm_point.Abs();
294 }
295 if (param.top.se_n < 2 || param.right.se_n < 2) {
296 // A rectangular corner. The top and left sides contain the borders
297 // while the bottom and right sides don't (see `Rect.contains`).
298 Scalar x_delta = param.right.offset.x + param.right.se_a - norm_point.x;
299 Scalar y_delta = param.top.offset.y + param.top.se_a - norm_point.y;
300 bool x_within = x_delta > 0 || (x_delta == 0 && param.signed_scale.x < 0);
301 bool y_within = y_delta > 0 || (y_delta == 0 && param.signed_scale.y < 0);
302 return x_within && y_within;
303 }
304 return OctantContains(param.top, norm_point - param.top.offset) &&
305 OctantContains(param.right, Flip(norm_point - param.right.offset));
306 }
307
308 class RoundSuperellipseBuilder {
309 private:
310 // The parameters that describes a conic curve.
311 struct ConicParam {
312 // The end point closer to point A.
313 Point p1;
314 // The control point.
315 Point c;
316 // The end point closer to point J.
317 Point p2;
318 // The conic weight.
319 Scalar weight;
320 };
321
322 public:
323 explicit RoundSuperellipseBuilder(PathReceiver& receiver)
324 : receiver_(receiver) {}
325
326 // Draws an arc representing 1/4 of a rounded superellipse.
327 //
328 // If `reverse` is false, the resulting arc spans from 0 to pi/2, moving
329 // clockwise starting from the positive Y-axis. Otherwise it moves from pi/2
330 // to 0.
331 //
332 // The `scale_sign` is an additional scaling transformation that potentially
333 // flips the result. This is useful for uniform radii where the same quadrant
334 // parameter set should be drawn to 4 quadrants.
335 void AddQuadrant(const RoundSuperellipseParam::Quadrant& param,
336 bool reverse,
337 Point scale_sign = Point(1, 1)) {
338 auto transform = Matrix::MakeTranslateScale(param.signed_scale * scale_sign,
339 param.offset);
340 if (param.top.se_n < 2 || param.right.se_n < 2) {
341 receiver_.LineTo(transform * (param.top.offset +
342 Point(param.top.se_a, param.top.se_a)));
343 if (!reverse) {
344 receiver_.LineTo(transform *
345 (param.right.offset + Point(param.right.se_a, 0)));
346 } else {
347 receiver_.LineTo(transform *
348 (param.top.offset + Point(0, param.top.se_a)));
349 }
350 return;
351 }
352 if (!reverse) {
353 AddOctant(param.top, /*reverse=*/false, /*flip=*/false, transform);
354 AddOctant(param.right, /*reverse=*/true, /*flip=*/true, transform);
355 } else {
356 AddOctant(param.right, /*reverse=*/false, /*flip=*/true, transform);
357 AddOctant(param.top, /*reverse=*/true, /*flip=*/false, transform);
358 }
359 }
360
361 private:
362 std::tuple<ConicParam, ConicParam> SuperellipseArcPoints(
363 const RoundSuperellipseParam::Octant& param) {
364 // The superellipse arc consists of two conic curves: A-H and H-J.
365 Point A = {0, param.se_a};
366 const Point& J = param.circle_start;
367
368 std::tuple<Scalar, Scalar, Scalar> factors = SuperellipseBezierFactors(
369 param.se_n, J.x / param.se_a, J.y / param.se_a);
370 Scalar weight1 = std::get<0>(factors);
371 Scalar weight2 = std::get<1>(factors);
372 Scalar yHOverA = std::get<2>(factors);
373
374 Point H = {
375 powf(1.f - powf(yHOverA, param.se_n), 1.f / param.se_n) * param.se_a,
376 yHOverA * param.se_a};
377
378 Scalar kA = 0;
379 Scalar kJ = -powf(J.x / J.y, param.se_n - 1.f);
380 Scalar kH = -powf(H.x / H.y, param.se_n - 1.f);
381
382 // The control points are determined by the intersection of the tangents for
383 // smoothness.
384 return {ConicParam{.p1 = A,
385 .c = Intersection(A, kA, H, kH),
386 .p2 = H,
387 .weight = weight1},
388 ConicParam{.p1 = H,
389 .c = Intersection(H, kH, J, kJ),
390 .p2 = J,
391 .weight = weight2}};
392 }
393
394 std::array<Point, 4> CircularArcPoints(
395 const RoundSuperellipseParam::Octant& param) {
396 Point start_vector = param.circle_start - param.circle_center;
397 Point end_vector =
398 start_vector.Rotate(Radians(-param.circle_max_angle.radians));
399 Point circle_end = param.circle_center + end_vector;
400 Point start_tangent = Point{start_vector.y, -start_vector.x}.Normalize();
401 Point end_tangent = Point{-end_vector.y, end_vector.x}.Normalize();
402 Scalar bezier_factor = std::tan(param.circle_max_angle.radians / 4) * 4 / 3;
403 Scalar radius = start_vector.GetLength();
404
405 return std::array<Point, 4>{
406 param.circle_start,
407 param.circle_start + start_tangent * bezier_factor * radius,
408 circle_end + end_tangent * bezier_factor * radius, circle_end};
409 };
410
411 // Draws an arc representing 1/8 of a rounded superellipse.
412 //
413 // If `reverse` is false, the resulting arc spans from 0 to pi/4, moving
414 // clockwise starting from the positive Y-axis. Otherwise it moves from pi/4
415 // to 0.
416 //
417 // If `flip` is true, all points have their X and Y coordinates swapped,
418 // effectively mirrowing each point by the y=x line.
419 //
420 // All points are transformed by `external_transform` after the optional
421 // flipping before being used as control points for the cubic curves.
422 void AddOctant(const RoundSuperellipseParam::Octant& param,
423 bool reverse,
424 bool flip,
425 const Matrix& external_transform) {
426 Matrix transform =
427 external_transform * Matrix::MakeTranslation(param.offset);
428 if (flip) {
429 transform = transform * kFlip;
430 }
431
432 auto circle_points = CircularArcPoints(param);
433 auto se_conics = SuperellipseArcPoints(param);
434
435 if (!reverse) {
436 receiver_.ConicTo(transform * std::get<0>(se_conics).c,
437 transform * std::get<0>(se_conics).p2,
438 std::get<0>(se_conics).weight);
439 receiver_.ConicTo(transform * std::get<1>(se_conics).c,
440 transform * std::get<1>(se_conics).p2,
441 std::get<1>(se_conics).weight);
442 receiver_.CubicTo(transform * circle_points[1],
443 transform * circle_points[2],
444 transform * circle_points[3]);
445 } else {
446 receiver_.CubicTo(transform * circle_points[2],
447 transform * circle_points[1],
448 transform * circle_points[0]);
449 receiver_.ConicTo(transform * std::get<1>(se_conics).c,
450 transform * std::get<1>(se_conics).p1,
451 std::get<1>(se_conics).weight);
452 receiver_.ConicTo(transform * std::get<0>(se_conics).c,
453 transform * std::get<0>(se_conics).p1,
454 std::get<0>(se_conics).weight);
455 }
456 };
457
458 // Get the Bezier factor for the superellipse arc in a rounded superellipse.
459 //
460 // The resulting tuple consists of:
461 // [0] weight1, weight for the conic A-H
462 // [1] weight2, weight for the conic H-J
463 // [2] yHOverA
464 std::tuple<Scalar, Scalar, Scalar> SuperellipseBezierFactors(Scalar n,
465 Scalar xJOverA,
466 Scalar yJOverA) {
467 // Precomputed (factor1, factor2) tuples for interpolation and
468 // extrapolation.
469 //
470 // These factors are normalized conic weights defined as:
471 // weight1 = factor1 * sqrt(n)
472 // weight2 = factor2 * xJOverA
473 //
474 // Rationale: Empirical analysis shows these quotients converge to stable
475 // constants as n -> infinity. Normalizing by sqrt(n) and xJOverA ensures
476 // the table remains well-behaved for linear extrapolation when 'n' exceeds
477 // the precomputed range.
478 //
479 // Optimal weights were originally derived via brute-force search for
480 // minimal curve distance on a rounded superellipse.
481 constexpr Scalar kPrecomputedVariables[][2] = {/*n= 2.0*/ {0.7078, 8.3194},
482 /*n= 3.0*/ {0.7895, 2.4523},
483 /*n= 4.0*/ {0.8379, 1.8528},
484 /*n= 5.0*/ {0.8701, 1.6891},
485 /*n= 6.0*/ {0.8932, 1.5806},
486 /*n= 7.0*/ {0.9107, 1.5043},
487 /*n= 8.0*/ {0.9244, 1.4470},
488 /*n= 9.0*/ {0.9355, 1.4037},
489 /*n=10.0*/ {0.9448, 1.3701},
490 /*n=11.0*/ {0.9526, 1.3431},
491 /*n=12.0*/ {0.9594, 1.3212},
492 /*n=13.0*/ {0.9653, 1.3032},
493 /*n=14.0*/ {0.9705, 1.2880}};
494 constexpr size_t kNumRecords =
495 sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]);
496 constexpr Scalar kStep = 1.00f;
497 constexpr Scalar kMinN = 2.00f;
498 constexpr Scalar kMaxN = kMinN + (kNumRecords - 1) * kStep;
499
500 if (n >= kMaxN) {
501 // The optimized factors stabilize as n grows large.
502 n = kMaxN;
503 }
504
505 // Compute weight1 and weight 2
506
507 Scalar steps = std::clamp<Scalar>((n - kMinN) / kStep, 0, kNumRecords - 1);
508 size_t left = std::clamp<size_t>(static_cast<size_t>(std::floor(steps)), 0,
509 kNumRecords - 2);
510 Scalar frac = steps - left;
511
512 Scalar weight1 = (1.f - frac) * kPrecomputedVariables[left][0] +
513 frac * kPrecomputedVariables[left + 1][0] * sqrt(n);
514 Scalar weight2 = (1.f - frac) * kPrecomputedVariables[left][1] +
515 frac * kPrecomputedVariables[left + 1][1] * xJOverA;
516
517 // Compute yHOverA
518
519 // H, the splitting point between the two conic curves, is picked by its y
520 // coordinate proportionally between A and J.
521 //
522 // The proportion between (yA-yH) and (yH-yJ) is positively correlated to n,
523 // so that when n increases, H moves closer to A. This is because the
524 // flatter segment of the superellipse curve is much harder to approximate.
525 // The exact formula of sqrt(n) is found empirically.
526 Scalar yH_proportion = sqrt(n);
527 constexpr Scalar yAOverA = 1.0;
528 Scalar yHOverA =
529 (yAOverA * yH_proportion + yJOverA) / (yH_proportion + 1.0f);
530
531 return {weight1, weight2, yHOverA};
532 }
533
534 // Find the intersection point of two lines defined by two points and their
535 // slopes.
536 static Point Intersection(Point p1, Scalar k1, Point p2, Scalar k2) {
537 if (std::fabs(k1 - k2) < kEhCloseEnough) {
538 return (p1 + p2) / 2;
539 }
540
541 // Line 1: y - y1 = k1(x - x1)
542 // Line 2: y - y2 = k2(x - x2)
543 // => x = (k1*x1 - k2*x2 + y2 - y1) / (k1 - k2)
544
545 Scalar x = (k1 * p1.x - k2 * p2.x + p2.y - p1.y) / (k1 - k2);
546 Scalar y = k1 * (x - p1.x) + p1.y;
547
548 return Point(x, y);
549 }
550
551 PathReceiver& receiver_;
552
553 // A matrix that swaps the coordinates of a point.
554 // clang-format off
555 static constexpr Matrix kFlip = Matrix(
556 0.0f, 1.0f, 0.0f, 0.0f,
557 1.0f, 0.0f, 0.0f, 0.0f,
558 0.0f, 0.0f, 1.0f, 0.0f,
559 0.0f, 0.0f, 0.0f, 1.0f);
560 // clang-format on
561 };
562
563 } // namespace
564
565 RoundSuperellipseParam RoundSuperellipseParam::MakeBoundsRadius(
566 const Rect& bounds,
567 Scalar radius) {
568 return RoundSuperellipseParam{
569 .top_right = ComputeQuadrant(bounds.GetCenter(), bounds.GetRightTop(),
570 {radius, radius}, {-1, 1}),
571 .all_corners_same = true,
572 };
573 }
574
575 RoundSuperellipseParam RoundSuperellipseParam::MakeBoundsRadii(
576 const Rect& bounds,
577 const RoundingRadii& radii) {
578 if (radii.AreAllCornersSame() && !radii.top_left.IsEmpty()) {
579 // Having four empty corners indicate a rectangle, which needs special
580 // treatment on border containment and therefore is not `all_corners_same`.
581 return RoundSuperellipseParam{
582 .top_right = ComputeQuadrant(bounds.GetCenter(), bounds.GetRightTop(),
583 radii.top_right, {-1, 1}),
584 .all_corners_same = true,
585 };
586 }
587 Scalar top_split = Split(bounds.GetLeft(), bounds.GetRight(),
588 radii.top_left.width, radii.top_right.width);
589 Scalar right_split = Split(bounds.GetTop(), bounds.GetBottom(),
590 radii.top_right.height, radii.bottom_right.height);
591 Scalar bottom_split =
592 Split(bounds.GetLeft(), bounds.GetRight(), radii.bottom_left.width,
593 radii.bottom_right.width);
594 Scalar left_split = Split(bounds.GetTop(), bounds.GetBottom(),
595 radii.top_left.height, radii.bottom_left.height);
596
597 return RoundSuperellipseParam{
598 .top_right =
599 ComputeQuadrant(Point{top_split, right_split}, bounds.GetRightTop(),
600 radii.top_right, {1, -1}),
601 .bottom_right =
602 ComputeQuadrant(Point{bottom_split, right_split},
603 bounds.GetRightBottom(), radii.bottom_right, {1, 1}),
604 .bottom_left =
605 ComputeQuadrant(Point{bottom_split, left_split},
606 bounds.GetLeftBottom(), radii.bottom_left, {-1, 1}),
607 .top_left =
608 ComputeQuadrant(Point{top_split, left_split}, bounds.GetLeftTop(),
609 radii.top_left, {-1, -1}),
610 .all_corners_same = false,
611 };
612 }
613
614 void RoundSuperellipseParam::Dispatch(PathReceiver& path_receiver) const {
615 RoundSuperellipseBuilder builder(path_receiver);
616
617 Point start = top_right.offset +
618 top_right.signed_scale *
619 (top_right.top.offset + Point(0, top_right.top.se_a));
620 path_receiver.MoveTo(start, true);
621
622 if (all_corners_same) {
623 builder.AddQuadrant(top_right, /*reverse=*/false, Point(1, 1));
624 builder.AddQuadrant(top_right, /*reverse=*/true, Point(1, -1));
625 builder.AddQuadrant(top_right, /*reverse=*/false, Point(-1, -1));
626 builder.AddQuadrant(top_right, /*reverse=*/true, Point(-1, 1));
627 } else {
628 builder.AddQuadrant(top_right, /*reverse=*/false);
629 builder.AddQuadrant(bottom_right, /*reverse=*/true);
630 builder.AddQuadrant(bottom_left, /*reverse=*/false);
631 builder.AddQuadrant(top_left, /*reverse=*/true);
632 }
633
634 path_receiver.LineTo(start);
635 path_receiver.Close();
636 }
637
638 bool RoundSuperellipseParam::Contains(const Point& point) const {
639 if (all_corners_same) {
640 return CornerContains(top_right, point, /*check_quadrant=*/false);
641 }
642 return CornerContains(top_right, point) &&
643 CornerContains(bottom_right, point) &&
644 CornerContains(bottom_left, point) && CornerContains(top_left, point);
645 }
646
647 } // namespace impeller
impeller::PathReceiver
Collection of functions to receive path segments from the underlying path representation via the DlPa...
Definition: path_source.h:42
impeller::PathReceiver::LineTo
virtual void LineTo(const Point &p2)=0
impeller::PathReceiver::Close
virtual void Close()=0
impeller::PathReceiver::MoveTo
virtual void MoveTo(const Point &p2, bool will_be_closed)=0
x
int32_t x
Definition: dl_golden_unittests.cc:458
transform
Matrix transform
Definition: gaussian_blur_filter_contents.cc:312
impeller::saturated::b
SI b
Definition: saturated_math.h:87
impeller
Definition: allocation.cc:12
impeller::Scalar
float Scalar
Definition: scalar.h:19
impeller::kEhCloseEnough
constexpr float kEhCloseEnough
Definition: constants.h:57
impeller::CornerContains
static bool CornerContains(const Point &p, const Point &corner, const Point &direction, const Size &radii)
Definition: round_rect.cc:30
impeller::Point
TPoint< Scalar > Point
Definition: point.h:425
impeller::Size
TSize< Scalar > Size
Definition: size.h:159
p2
Point p2
Definition: round_superellipse_param.cc:317
weight
Scalar weight
Definition: round_superellipse_param.cc:319
p1
Point p1
Definition: round_superellipse_param.cc:313
c
Point c
Definition: round_superellipse_param.cc:315
round_superellipse_param.h
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
impeller::Matrix::MakeTranslateScale
static constexpr Matrix MakeTranslateScale(const Vector3 &s, const Vector3 &t)
Definition: matrix.h:113
impeller::RoundSuperellipseParam::Octant::offset
Point offset
Definition: round_superellipse_param.h:31
impeller::RoundSuperellipseParam::Octant::se_a
Scalar se_a
Definition: round_superellipse_param.h:34
impeller::RoundSuperellipseParam::Quadrant::signed_scale
Point signed_scale
Definition: round_superellipse_param.h:78
impeller::RoundSuperellipseParam::Quadrant::top
Octant top
Definition: round_superellipse_param.h:82
impeller::RoundSuperellipseParam::Quadrant::offset
Point offset
Definition: round_superellipse_param.h:63
impeller::RoundSuperellipseParam
Definition: round_superellipse_param.h:18
impeller::RoundSuperellipseParam::top_left
Quadrant top_left
Definition: round_superellipse_param.h:92
impeller::RoundSuperellipseParam::MakeBoundsRadii
static RoundSuperellipseParam MakeBoundsRadii(const Rect &bounds, const RoundingRadii &radii)
Definition: round_superellipse_param.cc:575
impeller::RoundSuperellipseParam::Dispatch
void Dispatch(PathReceiver &receiver) const
Definition: round_superellipse_param.cc:614
impeller::RoundSuperellipseParam::Contains
bool Contains(const Point &point) const
Definition: round_superellipse_param.cc:638
impeller::RoundSuperellipseParam::bottom_left
Quadrant bottom_left
Definition: round_superellipse_param.h:91
impeller::RoundSuperellipseParam::kGapFactor
static constexpr Scalar kGapFactor
Definition: round_superellipse_param.h:127
impeller::RoundSuperellipseParam::all_corners_same
bool all_corners_same
Definition: round_superellipse_param.h:95
impeller::RoundSuperellipseParam::top_right
Quadrant top_right
Definition: round_superellipse_param.h:89
impeller::RoundSuperellipseParam::MakeBoundsRadius
static RoundSuperellipseParam MakeBoundsRadius(const Rect &bounds, Scalar radius)
Definition: round_superellipse_param.cc:565
impeller::RoundSuperellipseParam::bottom_right
Quadrant bottom_right
Definition: round_superellipse_param.h:90
impeller::RoundingRadii
Definition: rounding_radii.h:14
impeller::RoundingRadii::top_right
Size top_right
Definition: rounding_radii.h:16
impeller::RoundingRadii::bottom_left
Size bottom_left
Definition: rounding_radii.h:17
impeller::RoundingRadii::bottom_right
Size bottom_right
Definition: rounding_radii.h:18
impeller::RoundingRadii::top_left
Size top_left
Definition: rounding_radii.h:15
impeller::RoundingRadii::AreAllCornersSame
constexpr bool AreAllCornersSame(Scalar tolerance=kEhCloseEnough) const
Definition: rounding_radii.h:54
impeller::TPoint< Scalar >
impeller::TPoint::Abs
constexpr TPoint Abs() const
Definition: point.h:293
impeller::TPoint::Rotate
constexpr TPoint Rotate(const Radians &angle) const
Definition: point.h:314
impeller::TPoint::y
Type y
Definition: point.h:32
impeller::TPoint::x
Type x
Definition: point.h:31
impeller::TPoint::GetDistanceSquared
constexpr Type GetDistanceSquared(const TPoint &p) const
Definition: point.h:181
impeller::TRect< Scalar >
impeller::TRect::GetBottom
constexpr auto GetBottom() const
Definition: rect.h:357
impeller::TRect::GetTop
constexpr auto GetTop() const
Definition: rect.h:353
impeller::TRect::GetLeft
constexpr auto GetLeft() const
Definition: rect.h:351
impeller::TRect::GetRight
constexpr auto GetRight() const
Definition: rect.h:355
impeller::TRect::GetLeftBottom
constexpr TPoint< T > GetLeftBottom() const
Definition: rect.h:367
impeller::TRect::GetRightTop
constexpr TPoint< T > GetRightTop() const
Definition: rect.h:363
impeller::TRect::GetRightBottom
constexpr TPoint< T > GetRightBottom() const
Definition: rect.h:371
impeller::TRect::GetCenter
constexpr Point GetCenter() const
Get the center point as a |Point|.
Definition: rect.h:382
impeller::TRect::GetLeftTop
constexpr TPoint< T > GetLeftTop() const
Definition: rect.h:359
impeller::TSize::height
Type height
Definition: size.h:29
impeller::TSize::width
Type width
Definition: size.h:28
impeller::TSize::IsEmpty
constexpr bool IsEmpty() const
Returns true if either of the width or height are 0, negative, or NaN.
Definition: size.h:123
start
const size_t start
Definition: tessellator_libtess.cc:62