| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "flutter/impeller/geometry/round_superellipse_param.h" |
| |
| namespace impeller { |
| |
| namespace { |
| |
| // Return the value that splits the range from `left` to `right` into two |
| // portions whose ratio equals to `ratio_left` : `ratio_right`. |
| Scalar Split(Scalar left, Scalar right, Scalar ratio_left, Scalar ratio_right) { |
| if (ratio_left == 0 && ratio_right == 0) { |
| return (left + right) / 2; |
| } |
| return (left * ratio_right + right * ratio_left) / (ratio_left + ratio_right); |
| } |
| |
| // Return the same Point, but each NaN coordinate is replaced by that of |
| // `default_size`. |
| inline Point ReplanceNaNWithDefault(Point in, Size default_size) { |
| return Point{std::isnan(in.x) ? default_size.width : in.x, |
| std::isnan(in.y) ? default_size.height : in.y}; |
| } |
| |
| // Swap the x and y coordinate of a point. |
| // |
| // Effectively mirrors the point by the y=x line. |
| inline Point Flip(Point a) { |
| return Point{a.y, a.x}; |
| } |
| |
| // A look up table with precomputed variables. |
| // |
| // The columns represent the following variabls respectively: |
| // |
| // * n |
| // * k_xJ, which is defined as 1 / (1 - xJ / a) |
| // |
| // For definition of the variables, see ComputeOctant. |
| constexpr Scalar kPrecomputedVariables[][2] = { |
| /*ratio=2.00*/ {2.00000000, 1.13276676}, |
| /*ratio=2.10*/ {2.18349805, 1.20311921}, |
| /*ratio=2.20*/ {2.33888662, 1.28698796}, |
| /*ratio=2.30*/ {2.48660575, 1.36351941}, |
| /*ratio=2.40*/ {2.62226596, 1.44717976}, |
| /*ratio=2.50*/ {2.75148990, 1.53385819}, |
| /*ratio=3.00*/ {3.36298265, 1.98288283}, |
| /*ratio=3.50*/ {4.08649929, 2.23811846}, |
| /*ratio=4.00*/ {4.85481134, 2.47563463}, |
| /*ratio=4.50*/ {5.62945551, 2.72948597}, |
| /*ratio=5.00*/ {6.43023796, 2.98020421}}; |
| |
| constexpr Scalar kMinRatio = 2.00; |
| |
| // The curve is split into 3 parts: |
| // * The first part uses a denser look up table. |
| // * The second part uses a sparser look up table. |
| // * The third part uses a straight line. |
| constexpr Scalar kFirstStepInverse = 10; // = 1 / 0.10 |
| constexpr Scalar kFirstMaxRatio = 2.50; |
| constexpr Scalar kFirstNumRecords = 6; |
| |
| constexpr Scalar kSecondStepInverse = 2; // = 1 / 0.50 |
| constexpr Scalar kSecondMaxRatio = 5.00; |
| |
| constexpr Scalar kThirdNSlope = 1.559599389; |
| constexpr Scalar kThirdKxjSlope = 0.522807185; |
| |
| constexpr size_t kNumRecords = |
| sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]); |
| |
| // Compute the `n` and `xJ / a` for the given ratio. |
| std::array<Scalar, 2> ComputeNAndXj(Scalar ratio) { |
| if (ratio > kSecondMaxRatio) { |
| Scalar n = kThirdNSlope * (ratio - kSecondMaxRatio) + |
| kPrecomputedVariables[kNumRecords - 1][0]; |
| Scalar k_xJ = kThirdKxjSlope * (ratio - kSecondMaxRatio) + |
| kPrecomputedVariables[kNumRecords - 1][1]; |
| return {n, 1 - 1 / k_xJ}; |
| } |
| ratio = std::clamp(ratio, kMinRatio, kSecondMaxRatio); |
| Scalar steps; |
| if (ratio < kFirstMaxRatio) { |
| steps = (ratio - kMinRatio) * kFirstStepInverse; |
| } else { |
| steps = |
| (ratio - kFirstMaxRatio) * kSecondStepInverse + kFirstNumRecords - 1; |
| } |
| |
| size_t left = std::clamp<size_t>(static_cast<size_t>(std::floor(steps)), 0, |
| kNumRecords - 2); |
| Scalar frac = steps - left; |
| |
| Scalar n = (1 - frac) * kPrecomputedVariables[left][0] + |
| frac * kPrecomputedVariables[left + 1][0]; |
| Scalar k_xJ = (1 - frac) * kPrecomputedVariables[left][1] + |
| frac * kPrecomputedVariables[left + 1][1]; |
| return {n, 1 - 1 / k_xJ}; |
| } |
| |
| // Find the center of the circle that passes the given two points and have the |
| // given radius. |
| Point FindCircleCenter(Point a, Point b, Scalar r) { |
| /* Denote the middle point of A and B as M. The key is to find the center of |
| * the circle. |
| * A --__ |
| * / ⟍ `、 |
| * / M ⟍\ |
| * / ⟋ B |
| * / ⟋ ↗ |
| * / ⟋ |
| * / ⟋ r |
| * C ᜱ ↙ |
| */ |
| |
| Point a_to_b = b - a; |
| Point m = (a + b) / 2; |
| Point c_to_m = Point(-a_to_b.y, a_to_b.x); |
| Scalar distance_am = a_to_b.GetLength() / 2; |
| Scalar distance_cm = sqrt(r * r - distance_am * distance_am); |
| return m - distance_cm * c_to_m.Normalize(); |
| } |
| |
| // Compute parameters for a square-like rounded superellipse with a symmetrical |
| // radius. |
| RoundSuperellipseParam::Octant ComputeOctant(Point center, |
| Scalar a, |
| Scalar radius) { |
| /* The following figure shows the first quadrant of a square-like rounded |
| * superellipse. |
| * |
| * superelipse |
| * A ↓ circular arc |
| * ---------...._J ↙ |
| * | / `⟍ M (where x=y) |
| * | / ⟋ ⟍ |
| * | / ⟋ \ |
| * | / ⟋ | |
| * | ᜱD | |
| * | ⟋ | |
| * | ⟋ | |
| * |⟋ | |
| * +--------------------| A' |
| * O |
| * ←-------- a ---------→ |
| */ |
| |
| if (radius <= kEhCloseEnough) { |
| // Corners with really small radii are treated as sharp corners, since they |
| // might lead to NaNs due to `ratio` being too large. |
| return RoundSuperellipseParam::Octant{ |
| .offset = center, |
| |
| .se_a = a, |
| .se_n = 0, |
| |
| .circle_start = {a, a}, |
| }; |
| } |
| |
| Scalar ratio = a * 2 / radius; |
| Scalar g = RoundSuperellipseParam::kGapFactor * radius; |
| |
| auto precomputed_vars = ComputeNAndXj(ratio); |
| Scalar n = precomputed_vars[0]; |
| Scalar xJ = precomputed_vars[1] * a; |
| Scalar yJ = pow(1 - pow(precomputed_vars[1], n), 1 / n) * a; |
| Scalar max_theta = asinf(pow(precomputed_vars[1], n / 2)); |
| |
| Scalar tan_phiJ = pow(xJ / yJ, n - 1); |
| Scalar d = (xJ - tan_phiJ * yJ) / (1 - tan_phiJ); |
| Scalar R = (a - d - g) * sqrt(2); |
| |
| Point pointM{a - g, a - g}; |
| Point pointJ = Point{xJ, yJ}; |
| Point circle_center = |
| radius == 0 ? pointM : FindCircleCenter(pointJ, pointM, R); |
| Radians circle_max_angle = |
| radius == 0 ? Radians(0) |
| : (pointM - circle_center).AngleTo(pointJ - circle_center); |
| |
| return RoundSuperellipseParam::Octant{ |
| .offset = center, |
| |
| .se_a = a, |
| .se_n = n, |
| .se_max_theta = max_theta, |
| |
| .circle_start = pointJ, |
| .circle_center = circle_center, |
| .circle_max_angle = circle_max_angle, |
| }; |
| } |
| |
| // Compute parameters for a quadrant of a rounded superellipse with asymmetrical |
| // radii. |
| // |
| // The `corner` is the coordinate of the corner point in the same coordinate |
| // space as `center`, which specifies the half size of the bounding box. |
| // |
| // The `sign` is a vector of {±1, ±1} that specifies which quadrant the curve |
| // should be, which should have the same sign as `corner - center` except that |
| // the latter may have a 0. |
| RoundSuperellipseParam::Quadrant ComputeQuadrant(Point center, |
| Point corner, |
| Size in_radii, |
| Size sign) { |
| Point corner_vector = corner - center; |
| Size radii = {std::min(std::abs(in_radii.width), std::abs(corner_vector.x)), |
| std::min(std::abs(in_radii.height), std::abs(corner_vector.y))}; |
| |
| // The prefix "norm" is short for "normalized", meaning a rounded superellipse |
| // that has a uniform radius. The quadrant is scaled from this normalized one. |
| // |
| // Be extra careful to avoid NaNs in cases that some coordinates of `in_radii` |
| // or `corner_vector` are zero. |
| Scalar norm_radius = radii.MinDimension(); |
| Size forward_scale = norm_radius == 0 ? Size{1, 1} : radii / norm_radius; |
| Point norm_half_size = corner_vector.Abs() / forward_scale; |
| Point signed_scale = |
| ReplanceNaNWithDefault(corner_vector / norm_half_size, sign); |
| |
| // Each quadrant curve is composed of two octant curves, each of which belongs |
| // to a square-like rounded rectangle. For the two octants to connect at the |
| // circular arc, the centers these two square-like rounded rectangle must be |
| // offset from the quadrant center by a same distance in different directions. |
| // The distance is denoted as `c`. |
| Scalar c = norm_half_size.x - norm_half_size.y; |
| |
| return RoundSuperellipseParam::Quadrant{ |
| .offset = center, |
| .signed_scale = signed_scale, |
| .top = ComputeOctant(Point{0, -c}, norm_half_size.x, norm_radius), |
| .right = ComputeOctant(Point{c, 0}, norm_half_size.y, norm_radius), |
| }; |
| } |
| |
| // Checks whether the given point is contained in the first octant of the given |
| // square-like rounded superellipse. |
| // |
| // The first octant refers to the region that spans from 0 to pi/4 starting from |
| // positive Y axis clockwise. |
| // |
| // If the point is not within this octant at all, then this function always |
| // returns true. Otherwise this function returns whether the point is contained |
| // within the rounded superellipse. |
| // |
| // The `param.offset` is ignored. The input point should have been transformed |
| // to the coordinate space where the rounded superellipse is centered at the |
| // origin. |
| bool OctantContains(const RoundSuperellipseParam::Octant& param, |
| const Point& p) { |
| // Check whether the point is within the octant. |
| if (p.x < 0 || p.y < 0 || p.y < p.x) { |
| return true; |
| } |
| // Check if the point is within the superellipsoid segment. |
| if (p.x <= param.circle_start.x) { |
| Point p_se = p / param.se_a; |
| return powf(p_se.x, param.se_n) + powf(p_se.y, param.se_n) <= 1; |
| } |
| Scalar circle_radius = |
| param.circle_start.GetDistanceSquared(param.circle_center); |
| Point p_circle = p - param.circle_center; |
| return p_circle.GetDistanceSquared(Point()) < circle_radius; |
| } |
| |
| // Determine if p is inside the corner curve defined by the indicated corner |
| // param. |
| // |
| // The coordinates of p should be within the same coordinate space with |
| // `param.offset`. |
| // |
| // If `check_quadrant` is true, then this function first checks if the point is |
| // within the quadrant of given corner. If not, this function returns true, |
| // otherwise this method continues to check whether the point is contained in |
| // the rounded superellipse. |
| // |
| // If `check_quadrant` is false, then the first step above is skipped, and the |
| // function checks whether the absolute (relative to the center) coordinate of p |
| // is contained in the rounded superellipse. |
| bool CornerContains(const RoundSuperellipseParam::Quadrant& param, |
| const Point& p, |
| bool check_quadrant = true) { |
| Point norm_point = (p - param.offset) / param.signed_scale; |
| if (check_quadrant) { |
| if (norm_point.x < 0 || norm_point.y < 0) { |
| return true; |
| } |
| } else { |
| norm_point = norm_point.Abs(); |
| } |
| if (param.top.se_n < 2 || param.right.se_n < 2) { |
| // A rectangular corner. The top and left sides contain the borders |
| // while the bottom and right sides don't (see `Rect.contains`). |
| Scalar x_delta = param.right.offset.x + param.right.se_a - norm_point.x; |
| Scalar y_delta = param.top.offset.y + param.top.se_a - norm_point.y; |
| bool x_within = x_delta > 0 || (x_delta == 0 && param.signed_scale.x < 0); |
| bool y_within = y_delta > 0 || (y_delta == 0 && param.signed_scale.y < 0); |
| return x_within && y_within; |
| } |
| return OctantContains(param.top, norm_point - param.top.offset) && |
| OctantContains(param.right, Flip(norm_point - param.right.offset)); |
| } |
| |
| class RoundSuperellipseBuilder { |
| private: |
| // The parameters that describes a conic curve. |
| struct ConicParam { |
| // The end point closer to point A. |
| Point p1; |
| // The control point. |
| Point c; |
| // The end point closer to point J. |
| Point p2; |
| // The conic weight. |
| Scalar weight; |
| }; |
| |
| public: |
| explicit RoundSuperellipseBuilder(PathReceiver& receiver) |
| : receiver_(receiver) {} |
| |
| // Draws an arc representing 1/4 of a rounded superellipse. |
| // |
| // If `reverse` is false, the resulting arc spans from 0 to pi/2, moving |
| // clockwise starting from the positive Y-axis. Otherwise it moves from pi/2 |
| // to 0. |
| // |
| // The `scale_sign` is an additional scaling transformation that potentially |
| // flips the result. This is useful for uniform radii where the same quadrant |
| // parameter set should be drawn to 4 quadrants. |
| void AddQuadrant(const RoundSuperellipseParam::Quadrant& param, |
| bool reverse, |
| Point scale_sign = Point(1, 1)) { |
| auto transform = Matrix::MakeTranslateScale(param.signed_scale * scale_sign, |
| param.offset); |
| if (param.top.se_n < 2 || param.right.se_n < 2) { |
| receiver_.LineTo(transform * (param.top.offset + |
| Point(param.top.se_a, param.top.se_a))); |
| if (!reverse) { |
| receiver_.LineTo(transform * |
| (param.right.offset + Point(param.right.se_a, 0))); |
| } else { |
| receiver_.LineTo(transform * |
| (param.top.offset + Point(0, param.top.se_a))); |
| } |
| return; |
| } |
| if (!reverse) { |
| AddOctant(param.top, /*reverse=*/false, /*flip=*/false, transform); |
| AddOctant(param.right, /*reverse=*/true, /*flip=*/true, transform); |
| } else { |
| AddOctant(param.right, /*reverse=*/false, /*flip=*/true, transform); |
| AddOctant(param.top, /*reverse=*/true, /*flip=*/false, transform); |
| } |
| } |
| |
| private: |
| std::tuple<ConicParam, ConicParam> SuperellipseArcPoints( |
| const RoundSuperellipseParam::Octant& param) { |
| // The superellipse arc consists of two conic curves: A-H and H-J. |
| Point A = {0, param.se_a}; |
| const Point& J = param.circle_start; |
| |
| std::tuple<Scalar, Scalar, Scalar> factors = SuperellipseBezierFactors( |
| param.se_n, J.x / param.se_a, J.y / param.se_a); |
| Scalar weight1 = std::get<0>(factors); |
| Scalar weight2 = std::get<1>(factors); |
| Scalar yHOverA = std::get<2>(factors); |
| |
| Point H = { |
| powf(1.f - powf(yHOverA, param.se_n), 1.f / param.se_n) * param.se_a, |
| yHOverA * param.se_a}; |
| |
| Scalar kA = 0; |
| Scalar kJ = -powf(J.x / J.y, param.se_n - 1.f); |
| Scalar kH = -powf(H.x / H.y, param.se_n - 1.f); |
| |
| // The control points are determined by the intersection of the tangents for |
| // smoothness. |
| return {ConicParam{.p1 = A, |
| .c = Intersection(A, kA, H, kH), |
| .p2 = H, |
| .weight = weight1}, |
| ConicParam{.p1 = H, |
| .c = Intersection(H, kH, J, kJ), |
| .p2 = J, |
| .weight = weight2}}; |
| } |
| |
| std::array<Point, 4> CircularArcPoints( |
| const RoundSuperellipseParam::Octant& param) { |
| Point start_vector = param.circle_start - param.circle_center; |
| Point end_vector = |
| start_vector.Rotate(Radians(-param.circle_max_angle.radians)); |
| Point circle_end = param.circle_center + end_vector; |
| Point start_tangent = Point{start_vector.y, -start_vector.x}.Normalize(); |
| Point end_tangent = Point{-end_vector.y, end_vector.x}.Normalize(); |
| Scalar bezier_factor = std::tan(param.circle_max_angle.radians / 4) * 4 / 3; |
| Scalar radius = start_vector.GetLength(); |
| |
| return std::array<Point, 4>{ |
| param.circle_start, |
| param.circle_start + start_tangent * bezier_factor * radius, |
| circle_end + end_tangent * bezier_factor * radius, circle_end}; |
| }; |
| |
| // Draws an arc representing 1/8 of a rounded superellipse. |
| // |
| // If `reverse` is false, the resulting arc spans from 0 to pi/4, moving |
| // clockwise starting from the positive Y-axis. Otherwise it moves from pi/4 |
| // to 0. |
| // |
| // If `flip` is true, all points have their X and Y coordinates swapped, |
| // effectively mirrowing each point by the y=x line. |
| // |
| // All points are transformed by `external_transform` after the optional |
| // flipping before being used as control points for the cubic curves. |
| void AddOctant(const RoundSuperellipseParam::Octant& param, |
| bool reverse, |
| bool flip, |
| const Matrix& external_transform) { |
| Matrix transform = |
| external_transform * Matrix::MakeTranslation(param.offset); |
| if (flip) { |
| transform = transform * kFlip; |
| } |
| |
| auto circle_points = CircularArcPoints(param); |
| auto se_conics = SuperellipseArcPoints(param); |
| |
| if (!reverse) { |
| receiver_.ConicTo(transform * std::get<0>(se_conics).c, |
| transform * std::get<0>(se_conics).p2, |
| std::get<0>(se_conics).weight); |
| receiver_.ConicTo(transform * std::get<1>(se_conics).c, |
| transform * std::get<1>(se_conics).p2, |
| std::get<1>(se_conics).weight); |
| receiver_.CubicTo(transform * circle_points[1], |
| transform * circle_points[2], |
| transform * circle_points[3]); |
| } else { |
| receiver_.CubicTo(transform * circle_points[2], |
| transform * circle_points[1], |
| transform * circle_points[0]); |
| receiver_.ConicTo(transform * std::get<1>(se_conics).c, |
| transform * std::get<1>(se_conics).p1, |
| std::get<1>(se_conics).weight); |
| receiver_.ConicTo(transform * std::get<0>(se_conics).c, |
| transform * std::get<0>(se_conics).p1, |
| std::get<0>(se_conics).weight); |
| } |
| }; |
| |
| // Get the Bezier factor for the superellipse arc in a rounded superellipse. |
| // |
| // The resulting tuple consists of: |
| // [0] weight1, weight for the conic A-H |
| // [1] weight2, weight for the conic H-J |
| // [2] yHOverA |
| std::tuple<Scalar, Scalar, Scalar> SuperellipseBezierFactors(Scalar n, |
| Scalar xJOverA, |
| Scalar yJOverA) { |
| // Precomputed (factor1, factor2) tuples for interpolation and |
| // extrapolation. |
| // |
| // These factors are normalized conic weights defined as: |
| // weight1 = factor1 * sqrt(n) |
| // weight2 = factor2 * xJOverA |
| // |
| // Rationale: Empirical analysis shows these quotients converge to stable |
| // constants as n -> infinity. Normalizing by sqrt(n) and xJOverA ensures |
| // the table remains well-behaved for linear extrapolation when 'n' exceeds |
| // the precomputed range. |
| // |
| // Optimal weights were originally derived via brute-force search for |
| // minimal curve distance on a rounded superellipse. |
| constexpr Scalar kPrecomputedVariables[][2] = {/*n= 2.0*/ {0.7078, 8.3194}, |
| /*n= 3.0*/ {0.7895, 2.4523}, |
| /*n= 4.0*/ {0.8379, 1.8528}, |
| /*n= 5.0*/ {0.8701, 1.6891}, |
| /*n= 6.0*/ {0.8932, 1.5806}, |
| /*n= 7.0*/ {0.9107, 1.5043}, |
| /*n= 8.0*/ {0.9244, 1.4470}, |
| /*n= 9.0*/ {0.9355, 1.4037}, |
| /*n=10.0*/ {0.9448, 1.3701}, |
| /*n=11.0*/ {0.9526, 1.3431}, |
| /*n=12.0*/ {0.9594, 1.3212}, |
| /*n=13.0*/ {0.9653, 1.3032}, |
| /*n=14.0*/ {0.9705, 1.2880}}; |
| constexpr size_t kNumRecords = |
| sizeof(kPrecomputedVariables) / sizeof(kPrecomputedVariables[0]); |
| constexpr Scalar kStep = 1.00f; |
| constexpr Scalar kMinN = 2.00f; |
| constexpr Scalar kMaxN = kMinN + (kNumRecords - 1) * kStep; |
| |
| if (n >= kMaxN) { |
| // The optimized factors stabilize as n grows large. |
| n = kMaxN; |
| } |
| |
| // Compute weight1 and weight 2 |
| |
| Scalar steps = std::clamp<Scalar>((n - kMinN) / kStep, 0, kNumRecords - 1); |
| size_t left = std::clamp<size_t>(static_cast<size_t>(std::floor(steps)), 0, |
| kNumRecords - 2); |
| Scalar frac = steps - left; |
| |
| Scalar weight1 = (1.f - frac) * kPrecomputedVariables[left][0] + |
| frac * kPrecomputedVariables[left + 1][0] * sqrt(n); |
| Scalar weight2 = (1.f - frac) * kPrecomputedVariables[left][1] + |
| frac * kPrecomputedVariables[left + 1][1] * xJOverA; |
| |
| // Compute yHOverA |
| |
| // H, the splitting point between the two conic curves, is picked by its y |
| // coordinate proportionally between A and J. |
| // |
| // The proportion between (yA-yH) and (yH-yJ) is positively correlated to n, |
| // so that when n increases, H moves closer to A. This is because the |
| // flatter segment of the superellipse curve is much harder to approximate. |
| // The exact formula of sqrt(n) is found empirically. |
| Scalar yH_proportion = sqrt(n); |
| constexpr Scalar yAOverA = 1.0; |
| Scalar yHOverA = |
| (yAOverA * yH_proportion + yJOverA) / (yH_proportion + 1.0f); |
| |
| return {weight1, weight2, yHOverA}; |
| } |
| |
| // Find the intersection point of two lines defined by two points and their |
| // slopes. |
| static Point Intersection(Point p1, Scalar k1, Point p2, Scalar k2) { |
| if (std::fabs(k1 - k2) < kEhCloseEnough) { |
| return (p1 + p2) / 2; |
| } |
| |
| // Line 1: y - y1 = k1(x - x1) |
| // Line 2: y - y2 = k2(x - x2) |
| // => x = (k1*x1 - k2*x2 + y2 - y1) / (k1 - k2) |
| |
| Scalar x = (k1 * p1.x - k2 * p2.x + p2.y - p1.y) / (k1 - k2); |
| Scalar y = k1 * (x - p1.x) + p1.y; |
| |
| return Point(x, y); |
| } |
| |
| PathReceiver& receiver_; |
| |
| // A matrix that swaps the coordinates of a point. |
| // clang-format off |
| static constexpr Matrix kFlip = Matrix( |
| 0.0f, 1.0f, 0.0f, 0.0f, |
| 1.0f, 0.0f, 0.0f, 0.0f, |
| 0.0f, 0.0f, 1.0f, 0.0f, |
| 0.0f, 0.0f, 0.0f, 1.0f); |
| // clang-format on |
| }; |
| |
| } // namespace |
| |
| RoundSuperellipseParam RoundSuperellipseParam::MakeBoundsRadius( |
| const Rect& bounds, |
| Scalar radius) { |
| return RoundSuperellipseParam{ |
| .top_right = ComputeQuadrant(bounds.GetCenter(), bounds.GetRightTop(), |
| {radius, radius}, {-1, 1}), |
| .all_corners_same = true, |
| }; |
| } |
| |
| RoundSuperellipseParam RoundSuperellipseParam::MakeBoundsRadii( |
| const Rect& bounds, |
| const RoundingRadii& radii) { |
| if (radii.AreAllCornersSame() && !radii.top_left.IsEmpty()) { |
| // Having four empty corners indicate a rectangle, which needs special |
| // treatment on border containment and therefore is not `all_corners_same`. |
| return RoundSuperellipseParam{ |
| .top_right = ComputeQuadrant(bounds.GetCenter(), bounds.GetRightTop(), |
| radii.top_right, {-1, 1}), |
| .all_corners_same = true, |
| }; |
| } |
| Scalar top_split = Split(bounds.GetLeft(), bounds.GetRight(), |
| radii.top_left.width, radii.top_right.width); |
| Scalar right_split = Split(bounds.GetTop(), bounds.GetBottom(), |
| radii.top_right.height, radii.bottom_right.height); |
| Scalar bottom_split = |
| Split(bounds.GetLeft(), bounds.GetRight(), radii.bottom_left.width, |
| radii.bottom_right.width); |
| Scalar left_split = Split(bounds.GetTop(), bounds.GetBottom(), |
| radii.top_left.height, radii.bottom_left.height); |
| |
| return RoundSuperellipseParam{ |
| .top_right = |
| ComputeQuadrant(Point{top_split, right_split}, bounds.GetRightTop(), |
| radii.top_right, {1, -1}), |
| .bottom_right = |
| ComputeQuadrant(Point{bottom_split, right_split}, |
| bounds.GetRightBottom(), radii.bottom_right, {1, 1}), |
| .bottom_left = |
| ComputeQuadrant(Point{bottom_split, left_split}, |
| bounds.GetLeftBottom(), radii.bottom_left, {-1, 1}), |
| .top_left = |
| ComputeQuadrant(Point{top_split, left_split}, bounds.GetLeftTop(), |
| radii.top_left, {-1, -1}), |
| .all_corners_same = false, |
| }; |
| } |
| |
| void RoundSuperellipseParam::Dispatch(PathReceiver& path_receiver) const { |
| RoundSuperellipseBuilder builder(path_receiver); |
| |
| Point start = top_right.offset + |
| top_right.signed_scale * |
| (top_right.top.offset + Point(0, top_right.top.se_a)); |
| path_receiver.MoveTo(start, true); |
| |
| if (all_corners_same) { |
| builder.AddQuadrant(top_right, /*reverse=*/false, Point(1, 1)); |
| builder.AddQuadrant(top_right, /*reverse=*/true, Point(1, -1)); |
| builder.AddQuadrant(top_right, /*reverse=*/false, Point(-1, -1)); |
| builder.AddQuadrant(top_right, /*reverse=*/true, Point(-1, 1)); |
| } else { |
| builder.AddQuadrant(top_right, /*reverse=*/false); |
| builder.AddQuadrant(bottom_right, /*reverse=*/true); |
| builder.AddQuadrant(bottom_left, /*reverse=*/false); |
| builder.AddQuadrant(top_left, /*reverse=*/true); |
| } |
| |
| path_receiver.LineTo(start); |
| path_receiver.Close(); |
| } |
| |
| bool RoundSuperellipseParam::Contains(const Point& point) const { |
| if (all_corners_same) { |
| return CornerContains(top_right, point, /*check_quadrant=*/false); |
| } |
| return CornerContains(top_right, point) && |
| CornerContains(bottom_right, point) && |
| CornerContains(bottom_left, point) && CornerContains(top_left, point); |
| } |
| |
| } // namespace impeller |