xref: /aosp_15_r20/external/skia/tools/viewer/FitCubicToCircleSlide.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2020 Google Inc.
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 "include/core/SkCanvas.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkFont.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPaint.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPath.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTArray.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "tools/fonts/FontToolUtils.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "tools/viewer/ClickHandlerSlide.h"
15*c8dee2aaSAndroid Build Coastguard Worker 
16*c8dee2aaSAndroid Build Coastguard Worker #include <tuple>
17*c8dee2aaSAndroid Build Coastguard Worker 
18*c8dee2aaSAndroid Build Coastguard Worker using namespace skia_private;
19*c8dee2aaSAndroid Build Coastguard Worker 
20*c8dee2aaSAndroid Build Coastguard Worker // Math constants are not always defined.
21*c8dee2aaSAndroid Build Coastguard Worker #ifndef M_PI
22*c8dee2aaSAndroid Build Coastguard Worker #define M_PI 3.14159265358979323846264338327950288
23*c8dee2aaSAndroid Build Coastguard Worker #endif
24*c8dee2aaSAndroid Build Coastguard Worker 
25*c8dee2aaSAndroid Build Coastguard Worker #ifndef M_SQRT2
26*c8dee2aaSAndroid Build Coastguard Worker #define M_SQRT2 1.41421356237309504880168872420969808
27*c8dee2aaSAndroid Build Coastguard Worker #endif
28*c8dee2aaSAndroid Build Coastguard Worker 
29*c8dee2aaSAndroid Build Coastguard Worker constexpr static int kCenterX = 300;
30*c8dee2aaSAndroid Build Coastguard Worker constexpr static int kCenterY = 325;
31*c8dee2aaSAndroid Build Coastguard Worker constexpr static int kRadius = 250;
32*c8dee2aaSAndroid Build Coastguard Worker 
33*c8dee2aaSAndroid Build Coastguard Worker // This sample fits a cubic to the arc between two interactive points on a circle. It also finds the
34*c8dee2aaSAndroid Build Coastguard Worker // T-coordinate of max error, and outputs it and its value in pixels. (It turns out that max error
35*c8dee2aaSAndroid Build Coastguard Worker // always occurs at T=0.21132486540519.)
36*c8dee2aaSAndroid Build Coastguard Worker //
37*c8dee2aaSAndroid Build Coastguard Worker // Press 'E' to iteratively cut the arc in half and report the improvement in max error after each
38*c8dee2aaSAndroid Build Coastguard Worker // halving. (It turns out that max error improves by exactly 64x on every halving.)
39*c8dee2aaSAndroid Build Coastguard Worker class SampleFitCubicToCircle : public ClickHandlerSlide {
40*c8dee2aaSAndroid Build Coastguard Worker public:
SampleFitCubicToCircle()41*c8dee2aaSAndroid Build Coastguard Worker     SampleFitCubicToCircle() { fName = "FitCubicToCircle"; }
load(SkScalar w,SkScalar h)42*c8dee2aaSAndroid Build Coastguard Worker     void load(SkScalar w, SkScalar h) override { this->fitCubic(); }
43*c8dee2aaSAndroid Build Coastguard Worker     void draw(SkCanvas*) override;
44*c8dee2aaSAndroid Build Coastguard Worker     bool onChar(SkUnichar) override;
45*c8dee2aaSAndroid Build Coastguard Worker 
46*c8dee2aaSAndroid Build Coastguard Worker protected:
47*c8dee2aaSAndroid Build Coastguard Worker     Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override;
48*c8dee2aaSAndroid Build Coastguard Worker     bool onClick(Click*) override;
49*c8dee2aaSAndroid Build Coastguard Worker 
50*c8dee2aaSAndroid Build Coastguard Worker private:
51*c8dee2aaSAndroid Build Coastguard Worker     void fitCubic();
52*c8dee2aaSAndroid Build Coastguard Worker     // Coordinates of two points on the unit circle. These are the two endpoints of the arc we fit.
53*c8dee2aaSAndroid Build Coastguard Worker     double fEndptsX[2] = {0, 1};
54*c8dee2aaSAndroid Build Coastguard Worker     double fEndptsY[2] = {-1, 0};
55*c8dee2aaSAndroid Build Coastguard Worker 
56*c8dee2aaSAndroid Build Coastguard Worker     // Fitted cubic and info, set by fitCubic().
57*c8dee2aaSAndroid Build Coastguard Worker     double fControlLength;  // Length of (p1 - p0) and/or (p3 - p2) in unit circle space.
58*c8dee2aaSAndroid Build Coastguard Worker     double fMaxErrorT;  // T value where the cubic diverges most from the true arc.
59*c8dee2aaSAndroid Build Coastguard Worker     std::array<double, 4> fCubicX;  // Screen space cubic control points.
60*c8dee2aaSAndroid Build Coastguard Worker     std::array<double, 4> fCubicY;
61*c8dee2aaSAndroid Build Coastguard Worker     double fMaxError;  // Max error (in pixels) between the cubic and the screen-space arc.
62*c8dee2aaSAndroid Build Coastguard Worker     double fTheta;  // Angle of the arc. This is only used for informational purposes.
63*c8dee2aaSAndroid Build Coastguard Worker     TArray<SkString> fInfoStrings;
64*c8dee2aaSAndroid Build Coastguard Worker 
65*c8dee2aaSAndroid Build Coastguard Worker     class Click;
66*c8dee2aaSAndroid Build Coastguard Worker };
67*c8dee2aaSAndroid Build Coastguard Worker 
68*c8dee2aaSAndroid Build Coastguard Worker // Fits a cubic to an arc on the unit circle with endpoints (x0, y0) and (x1, y1). Using the
69*c8dee2aaSAndroid Build Coastguard Worker // following 3 constraints, we arrive at the formula used in the method:
70*c8dee2aaSAndroid Build Coastguard Worker //
71*c8dee2aaSAndroid Build Coastguard Worker //   1) The endpoints and tangent directions at the endpoints must match the arc.
72*c8dee2aaSAndroid Build Coastguard Worker //   2) The cubic must be symmetric (i.e., length(p1 - p0) == length(p3 - p2)).
73*c8dee2aaSAndroid Build Coastguard Worker //   3) The height of the cubic must match the height of the arc.
74*c8dee2aaSAndroid Build Coastguard Worker //
75*c8dee2aaSAndroid Build Coastguard Worker // Returns the "control length", or length of (p1 - p0) and/or (p3 - p2).
fit_cubic_to_unit_circle(double x0,double y0,double x1,double y1,std::array<double,4> * X,std::array<double,4> * Y)76*c8dee2aaSAndroid Build Coastguard Worker static float fit_cubic_to_unit_circle(double x0, double y0, double x1, double y1,
77*c8dee2aaSAndroid Build Coastguard Worker                                       std::array<double, 4>* X, std::array<double, 4>* Y) {
78*c8dee2aaSAndroid Build Coastguard Worker     constexpr static double kM = -4.0/3;
79*c8dee2aaSAndroid Build Coastguard Worker     constexpr static double kA = 4*M_SQRT2/3;
80*c8dee2aaSAndroid Build Coastguard Worker     double d = x0*x1 + y0*y1;
81*c8dee2aaSAndroid Build Coastguard Worker     double c = (std::sqrt(1 + d) * kM + kA) / std::sqrt(1 - d);
82*c8dee2aaSAndroid Build Coastguard Worker     *X = {x0, x0 - y0*c, x1 + y1*c, x1};
83*c8dee2aaSAndroid Build Coastguard Worker     *Y = {y0, y0 + x0*c, y1 - x1*c, y1};
84*c8dee2aaSAndroid Build Coastguard Worker     return c;
85*c8dee2aaSAndroid Build Coastguard Worker }
86*c8dee2aaSAndroid Build Coastguard Worker 
lerp(double x,double y,double T)87*c8dee2aaSAndroid Build Coastguard Worker static double lerp(double x, double y, double T) {
88*c8dee2aaSAndroid Build Coastguard Worker     return x + T*(y - x);
89*c8dee2aaSAndroid Build Coastguard Worker }
90*c8dee2aaSAndroid Build Coastguard Worker 
91*c8dee2aaSAndroid Build Coastguard Worker // Evaluates the cubic and 1st and 2nd derivatives at T.
eval_cubic(double x[],double T)92*c8dee2aaSAndroid Build Coastguard Worker static std::tuple<double, double, double> eval_cubic(double x[], double T) {
93*c8dee2aaSAndroid Build Coastguard Worker     // Use De Casteljau's algorithm for better accuracy and stability.
94*c8dee2aaSAndroid Build Coastguard Worker     double ab = lerp(x[0], x[1], T);
95*c8dee2aaSAndroid Build Coastguard Worker     double bc = lerp(x[1], x[2], T);
96*c8dee2aaSAndroid Build Coastguard Worker     double cd = lerp(x[2], x[3], T);
97*c8dee2aaSAndroid Build Coastguard Worker     double abc = lerp(ab, bc, T);
98*c8dee2aaSAndroid Build Coastguard Worker     double bcd = lerp(bc, cd, T);
99*c8dee2aaSAndroid Build Coastguard Worker     double abcd = lerp(abc, bcd, T);
100*c8dee2aaSAndroid Build Coastguard Worker     return {abcd, 3 * (bcd - abc) /*1st derivative.*/, 6 * (cd - 2*bc + ab) /*2nd derivative.*/};
101*c8dee2aaSAndroid Build Coastguard Worker }
102*c8dee2aaSAndroid Build Coastguard Worker 
103*c8dee2aaSAndroid Build Coastguard Worker // Uses newton-raphson convergence to find the point where the provided cubic diverges most from the
104*c8dee2aaSAndroid Build Coastguard Worker // unit circle. i.e., the point where the derivative of error == 0. For error we use:
105*c8dee2aaSAndroid Build Coastguard Worker //
106*c8dee2aaSAndroid Build Coastguard Worker //     error = x^2 + y^2 - 1
107*c8dee2aaSAndroid Build Coastguard Worker //     error' = 2xx' + 2yy'
108*c8dee2aaSAndroid Build Coastguard Worker //     error'' = 2xx'' + 2yy'' + 2x'^2 + 2y'^2
109*c8dee2aaSAndroid Build Coastguard Worker //
find_max_error_T(double cubicX[4],double cubicY[4])110*c8dee2aaSAndroid Build Coastguard Worker double find_max_error_T(double cubicX[4], double cubicY[4]) {
111*c8dee2aaSAndroid Build Coastguard Worker     constexpr static double kInitialT = .25;
112*c8dee2aaSAndroid Build Coastguard Worker     double T = kInitialT;
113*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < 64; ++i) {
114*c8dee2aaSAndroid Build Coastguard Worker         auto [x, dx, ddx] = eval_cubic(cubicX, T);
115*c8dee2aaSAndroid Build Coastguard Worker         auto [y, dy, ddy] = eval_cubic(cubicY, T);
116*c8dee2aaSAndroid Build Coastguard Worker         double dError = 2*(x*dx + y*dy);
117*c8dee2aaSAndroid Build Coastguard Worker         double ddError = 2*(x*ddx + y*ddy + dx*dx + dy*dy);
118*c8dee2aaSAndroid Build Coastguard Worker         T -= dError / ddError;
119*c8dee2aaSAndroid Build Coastguard Worker     }
120*c8dee2aaSAndroid Build Coastguard Worker     return T;
121*c8dee2aaSAndroid Build Coastguard Worker }
122*c8dee2aaSAndroid Build Coastguard Worker 
fitCubic()123*c8dee2aaSAndroid Build Coastguard Worker void SampleFitCubicToCircle::fitCubic() {
124*c8dee2aaSAndroid Build Coastguard Worker     fInfoStrings.clear();
125*c8dee2aaSAndroid Build Coastguard Worker 
126*c8dee2aaSAndroid Build Coastguard Worker     std::array<double, 4> X, Y;
127*c8dee2aaSAndroid Build Coastguard Worker     // "Control length" is the length of (p1 - p0) and/or (p3 - p2) in unit circle space.
128*c8dee2aaSAndroid Build Coastguard Worker     fControlLength = fit_cubic_to_unit_circle(fEndptsX[0], fEndptsY[0], fEndptsX[1], fEndptsY[1],
129*c8dee2aaSAndroid Build Coastguard Worker                                               &X, &Y);
130*c8dee2aaSAndroid Build Coastguard Worker     fInfoStrings.push_back().printf("control length=%0.14f", fControlLength);
131*c8dee2aaSAndroid Build Coastguard Worker 
132*c8dee2aaSAndroid Build Coastguard Worker     fMaxErrorT = find_max_error_T(X.data(), Y.data());
133*c8dee2aaSAndroid Build Coastguard Worker     fInfoStrings.push_back().printf("max error T=%0.14f", fMaxErrorT);
134*c8dee2aaSAndroid Build Coastguard Worker 
135*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < 4; ++i) {
136*c8dee2aaSAndroid Build Coastguard Worker         fCubicX[i] = X[i] * kRadius + kCenterX;
137*c8dee2aaSAndroid Build Coastguard Worker         fCubicY[i] = Y[i] * kRadius + kCenterY;
138*c8dee2aaSAndroid Build Coastguard Worker     }
139*c8dee2aaSAndroid Build Coastguard Worker     double errX = std::get<0>(eval_cubic(fCubicX.data(), fMaxErrorT)) - kCenterX;
140*c8dee2aaSAndroid Build Coastguard Worker     double errY = std::get<0>(eval_cubic(fCubicY.data(), fMaxErrorT)) - kCenterY;
141*c8dee2aaSAndroid Build Coastguard Worker     fMaxError = std::sqrt(errX*errX + errY*errY) - kRadius;
142*c8dee2aaSAndroid Build Coastguard Worker     fInfoStrings.push_back().printf("max error=%.5gpx", fMaxError);
143*c8dee2aaSAndroid Build Coastguard Worker 
144*c8dee2aaSAndroid Build Coastguard Worker     fTheta = std::atan2(fEndptsY[1], fEndptsX[1]) - std::atan2(fEndptsY[0], fEndptsX[0]);
145*c8dee2aaSAndroid Build Coastguard Worker     fTheta = std::abs(fTheta * 180/M_PI);
146*c8dee2aaSAndroid Build Coastguard Worker     if (fTheta > 180) {
147*c8dee2aaSAndroid Build Coastguard Worker         fTheta = 360 - fTheta;
148*c8dee2aaSAndroid Build Coastguard Worker     }
149*c8dee2aaSAndroid Build Coastguard Worker     fInfoStrings.push_back().printf("(theta=%.2f)", fTheta);
150*c8dee2aaSAndroid Build Coastguard Worker 
151*c8dee2aaSAndroid Build Coastguard Worker     SkDebugf("\n");
152*c8dee2aaSAndroid Build Coastguard Worker     for (const SkString& infoString : fInfoStrings) {
153*c8dee2aaSAndroid Build Coastguard Worker         SkDebugf("%s\n", infoString.c_str());
154*c8dee2aaSAndroid Build Coastguard Worker     }
155*c8dee2aaSAndroid Build Coastguard Worker }
156*c8dee2aaSAndroid Build Coastguard Worker 
draw(SkCanvas * canvas)157*c8dee2aaSAndroid Build Coastguard Worker void SampleFitCubicToCircle::draw(SkCanvas* canvas) {
158*c8dee2aaSAndroid Build Coastguard Worker     canvas->clear(SK_ColorBLACK);
159*c8dee2aaSAndroid Build Coastguard Worker 
160*c8dee2aaSAndroid Build Coastguard Worker     SkPaint circlePaint;
161*c8dee2aaSAndroid Build Coastguard Worker     circlePaint.setColor(0x80ffffff);
162*c8dee2aaSAndroid Build Coastguard Worker     circlePaint.setStyle(SkPaint::kStroke_Style);
163*c8dee2aaSAndroid Build Coastguard Worker     circlePaint.setStrokeWidth(0);
164*c8dee2aaSAndroid Build Coastguard Worker     circlePaint.setAntiAlias(true);
165*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawArc(SkRect::MakeXYWH(kCenterX - kRadius, kCenterY - kRadius, kRadius * 2,
166*c8dee2aaSAndroid Build Coastguard Worker                                      kRadius * 2), 0, 360, false, circlePaint);
167*c8dee2aaSAndroid Build Coastguard Worker 
168*c8dee2aaSAndroid Build Coastguard Worker     SkPaint cubicPaint;
169*c8dee2aaSAndroid Build Coastguard Worker     cubicPaint.setColor(SK_ColorGREEN);
170*c8dee2aaSAndroid Build Coastguard Worker     cubicPaint.setStyle(SkPaint::kStroke_Style);
171*c8dee2aaSAndroid Build Coastguard Worker     cubicPaint.setStrokeWidth(10);
172*c8dee2aaSAndroid Build Coastguard Worker     cubicPaint.setAntiAlias(true);
173*c8dee2aaSAndroid Build Coastguard Worker     SkPath cubicPath;
174*c8dee2aaSAndroid Build Coastguard Worker     cubicPath.moveTo(fCubicX[0], fCubicY[0]);
175*c8dee2aaSAndroid Build Coastguard Worker     cubicPath.cubicTo(fCubicX[1], fCubicY[1], fCubicX[2], fCubicY[2], fCubicX[3], fCubicY[3]);
176*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawPath(cubicPath, cubicPaint);
177*c8dee2aaSAndroid Build Coastguard Worker 
178*c8dee2aaSAndroid Build Coastguard Worker     SkPaint endpointsPaint;
179*c8dee2aaSAndroid Build Coastguard Worker     endpointsPaint.setColor(SK_ColorBLUE);
180*c8dee2aaSAndroid Build Coastguard Worker     endpointsPaint.setStrokeWidth(8);
181*c8dee2aaSAndroid Build Coastguard Worker     endpointsPaint.setAntiAlias(true);
182*c8dee2aaSAndroid Build Coastguard Worker     SkPoint points[2] = {{(float)fCubicX[0], (float)fCubicY[0]},
183*c8dee2aaSAndroid Build Coastguard Worker                          {(float)fCubicX[3], (float)fCubicY[3]}};
184*c8dee2aaSAndroid Build Coastguard Worker     canvas->drawPoints(SkCanvas::kPoints_PointMode, 2, points, endpointsPaint);
185*c8dee2aaSAndroid Build Coastguard Worker 
186*c8dee2aaSAndroid Build Coastguard Worker     SkPaint textPaint;
187*c8dee2aaSAndroid Build Coastguard Worker     textPaint.setColor(SK_ColorWHITE);
188*c8dee2aaSAndroid Build Coastguard Worker     constexpr static float kInfoTextSize = 16;
189*c8dee2aaSAndroid Build Coastguard Worker     SkFont font(ToolUtils::DefaultTypeface(), kInfoTextSize);
190*c8dee2aaSAndroid Build Coastguard Worker     int infoY = 10 + kInfoTextSize;
191*c8dee2aaSAndroid Build Coastguard Worker     for (const SkString& infoString : fInfoStrings) {
192*c8dee2aaSAndroid Build Coastguard Worker         canvas->drawString(infoString.c_str(), 10, infoY, font, textPaint);
193*c8dee2aaSAndroid Build Coastguard Worker         infoY += kInfoTextSize * 3/2;
194*c8dee2aaSAndroid Build Coastguard Worker     }
195*c8dee2aaSAndroid Build Coastguard Worker }
196*c8dee2aaSAndroid Build Coastguard Worker 
197*c8dee2aaSAndroid Build Coastguard Worker class SampleFitCubicToCircle::Click : public ClickHandlerSlide::Click {
198*c8dee2aaSAndroid Build Coastguard Worker public:
Click(int ptIdx)199*c8dee2aaSAndroid Build Coastguard Worker     Click(int ptIdx) : fPtIdx(ptIdx) {}
200*c8dee2aaSAndroid Build Coastguard Worker 
doClick(SampleFitCubicToCircle * that)201*c8dee2aaSAndroid Build Coastguard Worker     void doClick(SampleFitCubicToCircle* that) {
202*c8dee2aaSAndroid Build Coastguard Worker         double dx = fCurr.fX - kCenterX;
203*c8dee2aaSAndroid Build Coastguard Worker         double dy = fCurr.fY - kCenterY;
204*c8dee2aaSAndroid Build Coastguard Worker         double l = std::sqrt(dx*dx + dy*dy);
205*c8dee2aaSAndroid Build Coastguard Worker         that->fEndptsX[fPtIdx] = dx/l;
206*c8dee2aaSAndroid Build Coastguard Worker         that->fEndptsY[fPtIdx] = dy/l;
207*c8dee2aaSAndroid Build Coastguard Worker         if (that->fEndptsX[0] * that->fEndptsY[1] - that->fEndptsY[0] * that->fEndptsX[1] < 0) {
208*c8dee2aaSAndroid Build Coastguard Worker             std::swap(that->fEndptsX[0], that->fEndptsX[1]);
209*c8dee2aaSAndroid Build Coastguard Worker             std::swap(that->fEndptsY[0], that->fEndptsY[1]);
210*c8dee2aaSAndroid Build Coastguard Worker             fPtIdx = 1 - fPtIdx;
211*c8dee2aaSAndroid Build Coastguard Worker         }
212*c8dee2aaSAndroid Build Coastguard Worker         that->fitCubic();
213*c8dee2aaSAndroid Build Coastguard Worker     }
214*c8dee2aaSAndroid Build Coastguard Worker 
215*c8dee2aaSAndroid Build Coastguard Worker private:
216*c8dee2aaSAndroid Build Coastguard Worker     int fPtIdx;
217*c8dee2aaSAndroid Build Coastguard Worker };
218*c8dee2aaSAndroid Build Coastguard Worker 
onFindClickHandler(SkScalar x,SkScalar y,skui::ModifierKey)219*c8dee2aaSAndroid Build Coastguard Worker ClickHandlerSlide::Click* SampleFitCubicToCircle::onFindClickHandler(SkScalar x, SkScalar y,
220*c8dee2aaSAndroid Build Coastguard Worker                                                                      skui::ModifierKey) {
221*c8dee2aaSAndroid Build Coastguard Worker     double dx0 = x - fCubicX[0];
222*c8dee2aaSAndroid Build Coastguard Worker     double dy0 = y - fCubicY[0];
223*c8dee2aaSAndroid Build Coastguard Worker     double dx3 = x - fCubicX[3];
224*c8dee2aaSAndroid Build Coastguard Worker     double dy3 = y - fCubicY[3];
225*c8dee2aaSAndroid Build Coastguard Worker     if (dx0*dx0 + dy0*dy0 < dx3*dx3 + dy3*dy3) {
226*c8dee2aaSAndroid Build Coastguard Worker         return new Click(0);
227*c8dee2aaSAndroid Build Coastguard Worker     } else {
228*c8dee2aaSAndroid Build Coastguard Worker         return new Click(1);
229*c8dee2aaSAndroid Build Coastguard Worker     }
230*c8dee2aaSAndroid Build Coastguard Worker }
231*c8dee2aaSAndroid Build Coastguard Worker 
onClick(ClickHandlerSlide::Click * click)232*c8dee2aaSAndroid Build Coastguard Worker bool SampleFitCubicToCircle::onClick(ClickHandlerSlide::Click* click) {
233*c8dee2aaSAndroid Build Coastguard Worker     Click* myClick = (Click*)click;
234*c8dee2aaSAndroid Build Coastguard Worker     myClick->doClick(this);
235*c8dee2aaSAndroid Build Coastguard Worker     return true;
236*c8dee2aaSAndroid Build Coastguard Worker }
237*c8dee2aaSAndroid Build Coastguard Worker 
onChar(SkUnichar unichar)238*c8dee2aaSAndroid Build Coastguard Worker bool SampleFitCubicToCircle::onChar(SkUnichar unichar) {
239*c8dee2aaSAndroid Build Coastguard Worker     if (unichar == 'E') {
240*c8dee2aaSAndroid Build Coastguard Worker         constexpr static double kMaxErrorT = 0.21132486540519;  // Always the same.
241*c8dee2aaSAndroid Build Coastguard Worker         // Split the arc in half until error =~0, and report the improvement after each halving.
242*c8dee2aaSAndroid Build Coastguard Worker         double lastError = -1;
243*c8dee2aaSAndroid Build Coastguard Worker         for (double theta = fTheta; lastError != 0; theta /= 2) {
244*c8dee2aaSAndroid Build Coastguard Worker             double rads = theta * M_PI/180;
245*c8dee2aaSAndroid Build Coastguard Worker             std::array<double, 4> X, Y;
246*c8dee2aaSAndroid Build Coastguard Worker             fit_cubic_to_unit_circle(1, 0, std::cos(rads), std::sin(rads), &X, &Y);
247*c8dee2aaSAndroid Build Coastguard Worker             auto [x, dx, ddx] = eval_cubic(X.data(), kMaxErrorT);
248*c8dee2aaSAndroid Build Coastguard Worker             auto [y, dy, ddy] = eval_cubic(Y.data(), kMaxErrorT);
249*c8dee2aaSAndroid Build Coastguard Worker             double error = std::sqrt(x*x + y*y) * kRadius - kRadius;
250*c8dee2aaSAndroid Build Coastguard Worker             if ((float)error <= 0) {
251*c8dee2aaSAndroid Build Coastguard Worker                 error = 0;
252*c8dee2aaSAndroid Build Coastguard Worker             }
253*c8dee2aaSAndroid Build Coastguard Worker             SkDebugf("%6.2f degrees:   error= %10.5gpx", theta, error);
254*c8dee2aaSAndroid Build Coastguard Worker             if (lastError > 0) {
255*c8dee2aaSAndroid Build Coastguard Worker                 SkDebugf(" (%17.14fx improvement)", lastError / error);
256*c8dee2aaSAndroid Build Coastguard Worker             }
257*c8dee2aaSAndroid Build Coastguard Worker             SkDebugf("\n");
258*c8dee2aaSAndroid Build Coastguard Worker             lastError = error;
259*c8dee2aaSAndroid Build Coastguard Worker         }
260*c8dee2aaSAndroid Build Coastguard Worker         return true;
261*c8dee2aaSAndroid Build Coastguard Worker     }
262*c8dee2aaSAndroid Build Coastguard Worker     return false;
263*c8dee2aaSAndroid Build Coastguard Worker }
264*c8dee2aaSAndroid Build Coastguard Worker 
265*c8dee2aaSAndroid Build Coastguard Worker DEF_SLIDE(return new SampleFitCubicToCircle;)
266