xref: /aosp_15_r20/external/skia/src/base/SkBezierCurves.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2012 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkBezierCurves.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkFloatingPoint.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkPoint_impl.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkCubics.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkQuads.h"
15*c8dee2aaSAndroid Build Coastguard Worker 
16*c8dee2aaSAndroid Build Coastguard Worker #include <cstddef>
17*c8dee2aaSAndroid Build Coastguard Worker 
interpolate(double A,double B,double t)18*c8dee2aaSAndroid Build Coastguard Worker static inline double interpolate(double A, double B, double t) {
19*c8dee2aaSAndroid Build Coastguard Worker     return A + (B - A) * t;
20*c8dee2aaSAndroid Build Coastguard Worker }
21*c8dee2aaSAndroid Build Coastguard Worker 
EvalAt(const double curve[8],double t)22*c8dee2aaSAndroid Build Coastguard Worker std::array<double, 2> SkBezierCubic::EvalAt(const double curve[8], double t) {
23*c8dee2aaSAndroid Build Coastguard Worker     const auto in_X = [&curve](size_t n) { return curve[2*n]; };
24*c8dee2aaSAndroid Build Coastguard Worker     const auto in_Y = [&curve](size_t n) { return curve[2*n + 1]; };
25*c8dee2aaSAndroid Build Coastguard Worker 
26*c8dee2aaSAndroid Build Coastguard Worker     // Two semi-common fast paths
27*c8dee2aaSAndroid Build Coastguard Worker     if (t == 0) {
28*c8dee2aaSAndroid Build Coastguard Worker         return {in_X(0), in_Y(0)};
29*c8dee2aaSAndroid Build Coastguard Worker     }
30*c8dee2aaSAndroid Build Coastguard Worker     if (t == 1) {
31*c8dee2aaSAndroid Build Coastguard Worker         return {in_X(3), in_Y(3)};
32*c8dee2aaSAndroid Build Coastguard Worker     }
33*c8dee2aaSAndroid Build Coastguard Worker     // X(t) = X_0*(1-t)^3 + 3*X_1*t(1-t)^2 + 3*X_2*t^2(1-t) + X_3*t^3
34*c8dee2aaSAndroid Build Coastguard Worker     // Y(t) = Y_0*(1-t)^3 + 3*Y_1*t(1-t)^2 + 3*Y_2*t^2(1-t) + Y_3*t^3
35*c8dee2aaSAndroid Build Coastguard Worker     // Some compilers are smart enough and have sufficient registers/intrinsics to write optimal
36*c8dee2aaSAndroid Build Coastguard Worker     // code from
37*c8dee2aaSAndroid Build Coastguard Worker     //    double one_minus_t = 1 - t;
38*c8dee2aaSAndroid Build Coastguard Worker     //    double a = one_minus_t * one_minus_t * one_minus_t;
39*c8dee2aaSAndroid Build Coastguard Worker     //    double b = 3 * one_minus_t * one_minus_t * t;
40*c8dee2aaSAndroid Build Coastguard Worker     //    double c = 3 * one_minus_t * t * t;
41*c8dee2aaSAndroid Build Coastguard Worker     //    double d = t * t * t;
42*c8dee2aaSAndroid Build Coastguard Worker     // However, some (e.g. when compiling for ARM) fail to do so, so we use this form
43*c8dee2aaSAndroid Build Coastguard Worker     // to help more compilers generate smaller/faster ASM. https://godbolt.org/z/M6jG9x45c
44*c8dee2aaSAndroid Build Coastguard Worker     double one_minus_t = 1 - t;
45*c8dee2aaSAndroid Build Coastguard Worker     double one_minus_t_squared = one_minus_t * one_minus_t;
46*c8dee2aaSAndroid Build Coastguard Worker     double a = (one_minus_t_squared * one_minus_t);
47*c8dee2aaSAndroid Build Coastguard Worker     double b = 3 * one_minus_t_squared * t;
48*c8dee2aaSAndroid Build Coastguard Worker     double t_squared = t * t;
49*c8dee2aaSAndroid Build Coastguard Worker     double c = 3 * one_minus_t * t_squared;
50*c8dee2aaSAndroid Build Coastguard Worker     double d = t_squared * t;
51*c8dee2aaSAndroid Build Coastguard Worker 
52*c8dee2aaSAndroid Build Coastguard Worker     return {a * in_X(0) + b * in_X(1) + c * in_X(2) + d * in_X(3),
53*c8dee2aaSAndroid Build Coastguard Worker             a * in_Y(0) + b * in_Y(1) + c * in_Y(2) + d * in_Y(3)};
54*c8dee2aaSAndroid Build Coastguard Worker }
55*c8dee2aaSAndroid Build Coastguard Worker 
56*c8dee2aaSAndroid Build Coastguard Worker // Perform subdivision using De Casteljau's algorithm, that is, repeated linear
57*c8dee2aaSAndroid Build Coastguard Worker // interpolation between adjacent points.
Subdivide(const double curve[8],double t,double twoCurves[14])58*c8dee2aaSAndroid Build Coastguard Worker void SkBezierCubic::Subdivide(const double curve[8], double t,
59*c8dee2aaSAndroid Build Coastguard Worker                               double twoCurves[14]) {
60*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(0.0 <= t && t <= 1.0);
61*c8dee2aaSAndroid Build Coastguard Worker     // We split the curve "in" into two curves "alpha" and "beta"
62*c8dee2aaSAndroid Build Coastguard Worker     const auto in_X = [&curve](size_t n) { return curve[2*n]; };
63*c8dee2aaSAndroid Build Coastguard Worker     const auto in_Y = [&curve](size_t n) { return curve[2*n + 1]; };
64*c8dee2aaSAndroid Build Coastguard Worker     const auto alpha_X = [&twoCurves](size_t n) -> double& { return twoCurves[2*n]; };
65*c8dee2aaSAndroid Build Coastguard Worker     const auto alpha_Y = [&twoCurves](size_t n) -> double& { return twoCurves[2*n + 1]; };
66*c8dee2aaSAndroid Build Coastguard Worker     const auto beta_X = [&twoCurves](size_t n) -> double& { return twoCurves[2*n + 6]; };
67*c8dee2aaSAndroid Build Coastguard Worker     const auto beta_Y = [&twoCurves](size_t n) -> double& { return twoCurves[2*n + 7]; };
68*c8dee2aaSAndroid Build Coastguard Worker 
69*c8dee2aaSAndroid Build Coastguard Worker     alpha_X(0) = in_X(0);
70*c8dee2aaSAndroid Build Coastguard Worker     alpha_Y(0) = in_Y(0);
71*c8dee2aaSAndroid Build Coastguard Worker 
72*c8dee2aaSAndroid Build Coastguard Worker     beta_X(3) = in_X(3);
73*c8dee2aaSAndroid Build Coastguard Worker     beta_Y(3) = in_Y(3);
74*c8dee2aaSAndroid Build Coastguard Worker 
75*c8dee2aaSAndroid Build Coastguard Worker     double x01 = interpolate(in_X(0), in_X(1), t);
76*c8dee2aaSAndroid Build Coastguard Worker     double y01 = interpolate(in_Y(0), in_Y(1), t);
77*c8dee2aaSAndroid Build Coastguard Worker     double x12 = interpolate(in_X(1), in_X(2), t);
78*c8dee2aaSAndroid Build Coastguard Worker     double y12 = interpolate(in_Y(1), in_Y(2), t);
79*c8dee2aaSAndroid Build Coastguard Worker     double x23 = interpolate(in_X(2), in_X(3), t);
80*c8dee2aaSAndroid Build Coastguard Worker     double y23 = interpolate(in_Y(2), in_Y(3), t);
81*c8dee2aaSAndroid Build Coastguard Worker 
82*c8dee2aaSAndroid Build Coastguard Worker     alpha_X(1) = x01;
83*c8dee2aaSAndroid Build Coastguard Worker     alpha_Y(1) = y01;
84*c8dee2aaSAndroid Build Coastguard Worker 
85*c8dee2aaSAndroid Build Coastguard Worker     beta_X(2) = x23;
86*c8dee2aaSAndroid Build Coastguard Worker     beta_Y(2) = y23;
87*c8dee2aaSAndroid Build Coastguard Worker 
88*c8dee2aaSAndroid Build Coastguard Worker     alpha_X(2) = interpolate(x01, x12, t);
89*c8dee2aaSAndroid Build Coastguard Worker     alpha_Y(2) = interpolate(y01, y12, t);
90*c8dee2aaSAndroid Build Coastguard Worker 
91*c8dee2aaSAndroid Build Coastguard Worker     beta_X(1) = interpolate(x12, x23, t);
92*c8dee2aaSAndroid Build Coastguard Worker     beta_Y(1) = interpolate(y12, y23, t);
93*c8dee2aaSAndroid Build Coastguard Worker 
94*c8dee2aaSAndroid Build Coastguard Worker     alpha_X(3) /*= beta_X(0) */ = interpolate(alpha_X(2), beta_X(1), t);
95*c8dee2aaSAndroid Build Coastguard Worker     alpha_Y(3) /*= beta_Y(0) */ = interpolate(alpha_Y(2), beta_Y(1), t);
96*c8dee2aaSAndroid Build Coastguard Worker }
97*c8dee2aaSAndroid Build Coastguard Worker 
ConvertToPolynomial(const double curve[8],bool yValues)98*c8dee2aaSAndroid Build Coastguard Worker std::array<double, 4> SkBezierCubic::ConvertToPolynomial(const double curve[8], bool yValues) {
99*c8dee2aaSAndroid Build Coastguard Worker     const double* offset_curve = yValues ? curve + 1 : curve;
100*c8dee2aaSAndroid Build Coastguard Worker     const auto P = [&offset_curve](size_t n) { return offset_curve[2*n]; };
101*c8dee2aaSAndroid Build Coastguard Worker     // A cubic Bézier curve is interpolated as follows:
102*c8dee2aaSAndroid Build Coastguard Worker     //  c(t) = (1 - t)^3 P_0 + 3t(1 - t)^2 P_1 + 3t^2 (1 - t) P_2 + t^3 P_3
103*c8dee2aaSAndroid Build Coastguard Worker     //       = (-P_0 + 3P_1 + -3P_2 + P_3) t^3 + (3P_0 - 6P_1 + 3P_2) t^2 +
104*c8dee2aaSAndroid Build Coastguard Worker     //         (-3P_0 + 3P_1) t + P_0
105*c8dee2aaSAndroid Build Coastguard Worker     // Where P_N is the Nth point. The second step expands the polynomial and groups
106*c8dee2aaSAndroid Build Coastguard Worker     // by powers of t. The desired output is a cubic formula, so we just need to
107*c8dee2aaSAndroid Build Coastguard Worker     // combine the appropriate points to make the coefficients.
108*c8dee2aaSAndroid Build Coastguard Worker     std::array<double, 4> results;
109*c8dee2aaSAndroid Build Coastguard Worker     results[0] = -P(0) + 3*P(1) - 3*P(2) + P(3);
110*c8dee2aaSAndroid Build Coastguard Worker     results[1] = 3*P(0) - 6*P(1) + 3*P(2);
111*c8dee2aaSAndroid Build Coastguard Worker     results[2] = -3*P(0) + 3*P(1);
112*c8dee2aaSAndroid Build Coastguard Worker     results[3] = P(0);
113*c8dee2aaSAndroid Build Coastguard Worker     return results;
114*c8dee2aaSAndroid Build Coastguard Worker }
115*c8dee2aaSAndroid Build Coastguard Worker 
116*c8dee2aaSAndroid Build Coastguard Worker namespace {
117*c8dee2aaSAndroid Build Coastguard Worker struct DPoint {
DPoint__anon14c903270a11::DPoint118*c8dee2aaSAndroid Build Coastguard Worker     DPoint(double x_, double y_) : x{x_}, y{y_} {}
DPoint__anon14c903270a11::DPoint119*c8dee2aaSAndroid Build Coastguard Worker     DPoint(SkPoint p) : x{p.fX}, y{p.fY} {}
120*c8dee2aaSAndroid Build Coastguard Worker     double x, y;
121*c8dee2aaSAndroid Build Coastguard Worker };
122*c8dee2aaSAndroid Build Coastguard Worker 
operator -(DPoint a)123*c8dee2aaSAndroid Build Coastguard Worker DPoint operator- (DPoint a) {
124*c8dee2aaSAndroid Build Coastguard Worker     return {-a.x, -a.y};
125*c8dee2aaSAndroid Build Coastguard Worker }
126*c8dee2aaSAndroid Build Coastguard Worker 
operator +(DPoint a,DPoint b)127*c8dee2aaSAndroid Build Coastguard Worker DPoint operator+ (DPoint a, DPoint b) {
128*c8dee2aaSAndroid Build Coastguard Worker     return {a.x + b.x, a.y + b.y};
129*c8dee2aaSAndroid Build Coastguard Worker }
130*c8dee2aaSAndroid Build Coastguard Worker 
operator -(DPoint a,DPoint b)131*c8dee2aaSAndroid Build Coastguard Worker DPoint operator- (DPoint a, DPoint b) {
132*c8dee2aaSAndroid Build Coastguard Worker     return {a.x - b.x, a.y - b.y};
133*c8dee2aaSAndroid Build Coastguard Worker }
134*c8dee2aaSAndroid Build Coastguard Worker 
operator *(double s,DPoint a)135*c8dee2aaSAndroid Build Coastguard Worker DPoint operator* (double s, DPoint a) {
136*c8dee2aaSAndroid Build Coastguard Worker     return {s * a.x, s * a.y};
137*c8dee2aaSAndroid Build Coastguard Worker }
138*c8dee2aaSAndroid Build Coastguard Worker 
139*c8dee2aaSAndroid Build Coastguard Worker // Pin to 0 or 1 if within half a float ulp of 0 or 1.
pinTRange(double t)140*c8dee2aaSAndroid Build Coastguard Worker double pinTRange(double t) {
141*c8dee2aaSAndroid Build Coastguard Worker     // The ULPs around 0 are tiny compared to the ULPs around 1. Shift to 1 to use the same
142*c8dee2aaSAndroid Build Coastguard Worker     // size ULPs.
143*c8dee2aaSAndroid Build Coastguard Worker     if (sk_double_to_float(t + 1.0) == 1.0f) {
144*c8dee2aaSAndroid Build Coastguard Worker         return 0.0;
145*c8dee2aaSAndroid Build Coastguard Worker     } else if (sk_double_to_float(t) == 1.0f) {
146*c8dee2aaSAndroid Build Coastguard Worker         return 1.0;
147*c8dee2aaSAndroid Build Coastguard Worker     }
148*c8dee2aaSAndroid Build Coastguard Worker     return t;
149*c8dee2aaSAndroid Build Coastguard Worker }
150*c8dee2aaSAndroid Build Coastguard Worker }  // namespace
151*c8dee2aaSAndroid Build Coastguard Worker 
152*c8dee2aaSAndroid Build Coastguard Worker SkSpan<const float>
IntersectWithHorizontalLine(SkSpan<const SkPoint> controlPoints,float yIntercept,float * intersectionStorage)153*c8dee2aaSAndroid Build Coastguard Worker SkBezierCubic::IntersectWithHorizontalLine(
154*c8dee2aaSAndroid Build Coastguard Worker         SkSpan<const SkPoint> controlPoints, float yIntercept, float* intersectionStorage) {
155*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(controlPoints.size() >= 4);
156*c8dee2aaSAndroid Build Coastguard Worker     const DPoint P0 = controlPoints[0],
157*c8dee2aaSAndroid Build Coastguard Worker                  P1 = controlPoints[1],
158*c8dee2aaSAndroid Build Coastguard Worker                  P2 = controlPoints[2],
159*c8dee2aaSAndroid Build Coastguard Worker                  P3 = controlPoints[3];
160*c8dee2aaSAndroid Build Coastguard Worker 
161*c8dee2aaSAndroid Build Coastguard Worker     const DPoint A =   -P0 + 3*P1 - 3*P2 + P3,
162*c8dee2aaSAndroid Build Coastguard Worker                  B =  3*P0 - 6*P1 + 3*P2,
163*c8dee2aaSAndroid Build Coastguard Worker                  C = -3*P0 + 3*P1,
164*c8dee2aaSAndroid Build Coastguard Worker                  D =    P0;
165*c8dee2aaSAndroid Build Coastguard Worker 
166*c8dee2aaSAndroid Build Coastguard Worker     return Intersect(A.x, B.x, C.x, D.x, A.y, B.y, C.y, D.y, yIntercept, intersectionStorage);
167*c8dee2aaSAndroid Build Coastguard Worker }
168*c8dee2aaSAndroid Build Coastguard Worker 
169*c8dee2aaSAndroid Build Coastguard Worker SkSpan<const float>
Intersect(double AX,double BX,double CX,double DX,double AY,double BY,double CY,double DY,float toIntersect,float intersectionsStorage[3])170*c8dee2aaSAndroid Build Coastguard Worker SkBezierCubic::Intersect(double AX, double BX, double CX, double DX,
171*c8dee2aaSAndroid Build Coastguard Worker                          double AY, double BY, double CY, double DY,
172*c8dee2aaSAndroid Build Coastguard Worker                          float toIntersect, float intersectionsStorage[3]) {
173*c8dee2aaSAndroid Build Coastguard Worker     double roots[3];
174*c8dee2aaSAndroid Build Coastguard Worker     SkSpan<double> ts = SkSpan(roots,
175*c8dee2aaSAndroid Build Coastguard Worker                                SkCubics::RootsReal(AY, BY, CY, DY - toIntersect, roots));
176*c8dee2aaSAndroid Build Coastguard Worker 
177*c8dee2aaSAndroid Build Coastguard Worker     int intersectionCount = 0;
178*c8dee2aaSAndroid Build Coastguard Worker     for (double t : ts) {
179*c8dee2aaSAndroid Build Coastguard Worker         const double pinnedT = pinTRange(t);
180*c8dee2aaSAndroid Build Coastguard Worker         if (0 <= pinnedT && pinnedT <= 1) {
181*c8dee2aaSAndroid Build Coastguard Worker             intersectionsStorage[intersectionCount++] = SkCubics::EvalAt(AX, BX, CX, DX, pinnedT);
182*c8dee2aaSAndroid Build Coastguard Worker         }
183*c8dee2aaSAndroid Build Coastguard Worker     }
184*c8dee2aaSAndroid Build Coastguard Worker 
185*c8dee2aaSAndroid Build Coastguard Worker     return {intersectionsStorage, intersectionCount};
186*c8dee2aaSAndroid Build Coastguard Worker }
187*c8dee2aaSAndroid Build Coastguard Worker 
188*c8dee2aaSAndroid Build Coastguard Worker SkSpan<const float>
IntersectWithHorizontalLine(SkSpan<const SkPoint> controlPoints,float yIntercept,float intersectionStorage[2])189*c8dee2aaSAndroid Build Coastguard Worker SkBezierQuad::IntersectWithHorizontalLine(SkSpan<const SkPoint> controlPoints, float yIntercept,
190*c8dee2aaSAndroid Build Coastguard Worker                                           float intersectionStorage[2]) {
191*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(controlPoints.size() >= 3);
192*c8dee2aaSAndroid Build Coastguard Worker     const DPoint p0 = controlPoints[0],
193*c8dee2aaSAndroid Build Coastguard Worker                  p1 = controlPoints[1],
194*c8dee2aaSAndroid Build Coastguard Worker                  p2 = controlPoints[2];
195*c8dee2aaSAndroid Build Coastguard Worker 
196*c8dee2aaSAndroid Build Coastguard Worker     // Calculate A, B, C using doubles to reduce round-off error.
197*c8dee2aaSAndroid Build Coastguard Worker     const DPoint A = p0 - 2 * p1 + p2,
198*c8dee2aaSAndroid Build Coastguard Worker     // Remember we are generating the polynomial in the form A*t^2 -2*B*t + C, so the factor
199*c8dee2aaSAndroid Build Coastguard Worker     // of 2 is not needed and the term is negated. This term for a Bézier curve is usually
200*c8dee2aaSAndroid Build Coastguard Worker     // 2(p1-p0).
201*c8dee2aaSAndroid Build Coastguard Worker                  B = p0 - p1,
202*c8dee2aaSAndroid Build Coastguard Worker                  C = p0;
203*c8dee2aaSAndroid Build Coastguard Worker 
204*c8dee2aaSAndroid Build Coastguard Worker     return Intersect(A.x, B.x, C.x, A.y, B.y, C.y, yIntercept, intersectionStorage);
205*c8dee2aaSAndroid Build Coastguard Worker }
206*c8dee2aaSAndroid Build Coastguard Worker 
Intersect(double AX,double BX,double CX,double AY,double BY,double CY,double yIntercept,float intersectionStorage[2])207*c8dee2aaSAndroid Build Coastguard Worker SkSpan<const float> SkBezierQuad::Intersect(
208*c8dee2aaSAndroid Build Coastguard Worker         double AX, double BX, double CX, double AY, double BY, double CY,
209*c8dee2aaSAndroid Build Coastguard Worker         double yIntercept, float intersectionStorage[2]) {
210*c8dee2aaSAndroid Build Coastguard Worker     auto [discriminant, r0, r1] = SkQuads::Roots(AY, BY, CY - yIntercept);
211*c8dee2aaSAndroid Build Coastguard Worker 
212*c8dee2aaSAndroid Build Coastguard Worker     int intersectionCount = 0;
213*c8dee2aaSAndroid Build Coastguard Worker     // Round the roots to the nearest float to generate the values t. Valid t's are on the
214*c8dee2aaSAndroid Build Coastguard Worker     // domain [0, 1].
215*c8dee2aaSAndroid Build Coastguard Worker     const double t0 = pinTRange(r0);
216*c8dee2aaSAndroid Build Coastguard Worker     if (0 <= t0 && t0 <= 1) {
217*c8dee2aaSAndroid Build Coastguard Worker         intersectionStorage[intersectionCount++] = SkQuads::EvalAt(AX, -2 * BX, CX, t0);
218*c8dee2aaSAndroid Build Coastguard Worker     }
219*c8dee2aaSAndroid Build Coastguard Worker 
220*c8dee2aaSAndroid Build Coastguard Worker     const double t1 = pinTRange(r1);
221*c8dee2aaSAndroid Build Coastguard Worker     if (0 <= t1 && t1 <= 1 && t1 != t0) {
222*c8dee2aaSAndroid Build Coastguard Worker         intersectionStorage[intersectionCount++] = SkQuads::EvalAt(AX, -2 * BX, CX, t1);
223*c8dee2aaSAndroid Build Coastguard Worker     }
224*c8dee2aaSAndroid Build Coastguard Worker 
225*c8dee2aaSAndroid Build Coastguard Worker     return SkSpan{intersectionStorage, intersectionCount};
226*c8dee2aaSAndroid Build Coastguard Worker }
227*c8dee2aaSAndroid Build Coastguard Worker 
228