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