xref: /aosp_15_r20/external/skia/src/base/SkQuads.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2023 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/SkQuads.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 
13*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
14*c8dee2aaSAndroid Build Coastguard Worker #include <limits>
15*c8dee2aaSAndroid Build Coastguard Worker 
16*c8dee2aaSAndroid Build Coastguard Worker // Solve 0 = M * x + B. If M is 0, there are no solutions, unless B is also 0,
17*c8dee2aaSAndroid Build Coastguard Worker // in which case there are infinite solutions, so we just return 1 of them.
solve_linear(const double M,const double B,double solution[2])18*c8dee2aaSAndroid Build Coastguard Worker static int solve_linear(const double M, const double B, double solution[2]) {
19*c8dee2aaSAndroid Build Coastguard Worker     if (sk_double_nearly_zero(M)) {
20*c8dee2aaSAndroid Build Coastguard Worker         solution[0] = 0;
21*c8dee2aaSAndroid Build Coastguard Worker         if (sk_double_nearly_zero(B)) {
22*c8dee2aaSAndroid Build Coastguard Worker             return 1;
23*c8dee2aaSAndroid Build Coastguard Worker         }
24*c8dee2aaSAndroid Build Coastguard Worker         return 0;
25*c8dee2aaSAndroid Build Coastguard Worker     }
26*c8dee2aaSAndroid Build Coastguard Worker     solution[0] = -B / M;
27*c8dee2aaSAndroid Build Coastguard Worker     if (!std::isfinite(solution[0])) {
28*c8dee2aaSAndroid Build Coastguard Worker         return 0;
29*c8dee2aaSAndroid Build Coastguard Worker     }
30*c8dee2aaSAndroid Build Coastguard Worker     return 1;
31*c8dee2aaSAndroid Build Coastguard Worker }
32*c8dee2aaSAndroid Build Coastguard Worker 
33*c8dee2aaSAndroid Build Coastguard Worker // When B >> A, then the x^2 component doesn't contribute much to the output, so the second root
34*c8dee2aaSAndroid Build Coastguard Worker // will be very large, but have massive round off error. Because of the round off error, the
35*c8dee2aaSAndroid Build Coastguard Worker // second root will not evaluate to zero when substituted back into the quadratic equation. In
36*c8dee2aaSAndroid Build Coastguard Worker // the situation when B >> A, then just treat the quadratic as a linear equation.
close_to_linear(double A,double B)37*c8dee2aaSAndroid Build Coastguard Worker static bool close_to_linear(double A, double B) {
38*c8dee2aaSAndroid Build Coastguard Worker     if (A != 0) {
39*c8dee2aaSAndroid Build Coastguard Worker         // Return if B is much bigger than A.
40*c8dee2aaSAndroid Build Coastguard Worker         return std::abs(B / A) >= 1.0e+16;
41*c8dee2aaSAndroid Build Coastguard Worker     }
42*c8dee2aaSAndroid Build Coastguard Worker 
43*c8dee2aaSAndroid Build Coastguard Worker     // Otherwise A is zero, and the quadratic is linear.
44*c8dee2aaSAndroid Build Coastguard Worker     return true;
45*c8dee2aaSAndroid Build Coastguard Worker }
46*c8dee2aaSAndroid Build Coastguard Worker 
Discriminant(const double a,const double b,const double c)47*c8dee2aaSAndroid Build Coastguard Worker double SkQuads::Discriminant(const double a, const double b, const double c) {
48*c8dee2aaSAndroid Build Coastguard Worker     const double b2 = b * b;
49*c8dee2aaSAndroid Build Coastguard Worker     const double ac = a * c;
50*c8dee2aaSAndroid Build Coastguard Worker 
51*c8dee2aaSAndroid Build Coastguard Worker     // Calculate the rough discriminate which may suffer from a loss in precision due to b2 and
52*c8dee2aaSAndroid Build Coastguard Worker     // ac being too close.
53*c8dee2aaSAndroid Build Coastguard Worker     const double roughDiscriminant = b2 - ac;
54*c8dee2aaSAndroid Build Coastguard Worker 
55*c8dee2aaSAndroid Build Coastguard Worker     // We would like the calculated discriminant to have a relative error of 2-bits or less. For
56*c8dee2aaSAndroid Build Coastguard Worker     // doubles, this means the relative error is <= E = 3*2^-53. This gives a relative error
57*c8dee2aaSAndroid Build Coastguard Worker     // bounds of:
58*c8dee2aaSAndroid Build Coastguard Worker     //
59*c8dee2aaSAndroid Build Coastguard Worker     //     |D - D~| / |D| <= E,
60*c8dee2aaSAndroid Build Coastguard Worker     //
61*c8dee2aaSAndroid Build Coastguard Worker     // where D = B*B - AC, and D~ is the floating point approximation of D.
62*c8dee2aaSAndroid Build Coastguard Worker     // Define the following equations
63*c8dee2aaSAndroid Build Coastguard Worker     //     B2 = B*B,
64*c8dee2aaSAndroid Build Coastguard Worker     //     B2~ = B2(1 + eB2), where eB2 is the floating point round off,
65*c8dee2aaSAndroid Build Coastguard Worker     //     AC = A*C,
66*c8dee2aaSAndroid Build Coastguard Worker     //     AC~ = AC(1 + eAC), where eAC is the floating point round off, and
67*c8dee2aaSAndroid Build Coastguard Worker     //     D~ = B2~ - AC~.
68*c8dee2aaSAndroid Build Coastguard Worker     //  We can now rewrite the above bounds as
69*c8dee2aaSAndroid Build Coastguard Worker     //
70*c8dee2aaSAndroid Build Coastguard Worker     //     |B2 - AC - (B2~ - AC~)| / |B2 - AC| = |B2 - AC - B2~ + AC~| / |B2 - AC| <= E.
71*c8dee2aaSAndroid Build Coastguard Worker     //
72*c8dee2aaSAndroid Build Coastguard Worker     //  Substituting B2~ and AC~, and canceling terms gives
73*c8dee2aaSAndroid Build Coastguard Worker     //
74*c8dee2aaSAndroid Build Coastguard Worker     //     |eAC * AC - eB2 * B2| / |B2 - AC| <= max(|eAC|, |eBC|) * (|AC| + |B2|) / |B2 - AC|.
75*c8dee2aaSAndroid Build Coastguard Worker     //
76*c8dee2aaSAndroid Build Coastguard Worker     //  We know that B2 is always positive, if AC is negative, then there is no cancellation
77*c8dee2aaSAndroid Build Coastguard Worker     //  problem, and max(|eAC|, |eBC|) <= 2^-53, thus
78*c8dee2aaSAndroid Build Coastguard Worker     //
79*c8dee2aaSAndroid Build Coastguard Worker     //     2^-53 * (AC + B2) / |B2 - AC| <= 3 * 2^-53. Leading to
80*c8dee2aaSAndroid Build Coastguard Worker     //     AC + B2 <= 3 * |B2 - AC|.
81*c8dee2aaSAndroid Build Coastguard Worker     //
82*c8dee2aaSAndroid Build Coastguard Worker     // If 3 * |B2 - AC| >= AC + B2 holds, then the roughDiscriminant has 2-bits of rounding error
83*c8dee2aaSAndroid Build Coastguard Worker     // or less and can be used.
84*c8dee2aaSAndroid Build Coastguard Worker     if (3 * std::abs(roughDiscriminant) >= b2 + ac) {
85*c8dee2aaSAndroid Build Coastguard Worker         return roughDiscriminant;
86*c8dee2aaSAndroid Build Coastguard Worker     }
87*c8dee2aaSAndroid Build Coastguard Worker 
88*c8dee2aaSAndroid Build Coastguard Worker     // Use the extra internal precision afforded by fma to calculate the rounding error for
89*c8dee2aaSAndroid Build Coastguard Worker     // b^2 and ac.
90*c8dee2aaSAndroid Build Coastguard Worker     const double b2RoundingError = std::fma(b, b, -b2);
91*c8dee2aaSAndroid Build Coastguard Worker     const double acRoundingError = std::fma(a, c, -ac);
92*c8dee2aaSAndroid Build Coastguard Worker 
93*c8dee2aaSAndroid Build Coastguard Worker     // Add the total rounding error back into the discriminant guess.
94*c8dee2aaSAndroid Build Coastguard Worker     const double discriminant = (b2 - ac) + (b2RoundingError - acRoundingError);
95*c8dee2aaSAndroid Build Coastguard Worker     return discriminant;
96*c8dee2aaSAndroid Build Coastguard Worker }
97*c8dee2aaSAndroid Build Coastguard Worker 
Roots(double A,double B,double C)98*c8dee2aaSAndroid Build Coastguard Worker SkQuads::RootResult SkQuads::Roots(double A, double B, double C) {
99*c8dee2aaSAndroid Build Coastguard Worker     const double discriminant = Discriminant(A, B, C);
100*c8dee2aaSAndroid Build Coastguard Worker 
101*c8dee2aaSAndroid Build Coastguard Worker     if (A == 0) {
102*c8dee2aaSAndroid Build Coastguard Worker         double root;
103*c8dee2aaSAndroid Build Coastguard Worker         if (B == 0) {
104*c8dee2aaSAndroid Build Coastguard Worker             if (C == 0) {
105*c8dee2aaSAndroid Build Coastguard Worker                 root = std::numeric_limits<double>::infinity();
106*c8dee2aaSAndroid Build Coastguard Worker             } else {
107*c8dee2aaSAndroid Build Coastguard Worker                 root = std::numeric_limits<double>::quiet_NaN();
108*c8dee2aaSAndroid Build Coastguard Worker             }
109*c8dee2aaSAndroid Build Coastguard Worker         } else {
110*c8dee2aaSAndroid Build Coastguard Worker             // Solve -2*B*x + C == 0; x = c/(2*b).
111*c8dee2aaSAndroid Build Coastguard Worker             root = C / (2 * B);
112*c8dee2aaSAndroid Build Coastguard Worker         }
113*c8dee2aaSAndroid Build Coastguard Worker         return {discriminant, root, root};
114*c8dee2aaSAndroid Build Coastguard Worker     }
115*c8dee2aaSAndroid Build Coastguard Worker 
116*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(A != 0);
117*c8dee2aaSAndroid Build Coastguard Worker     if (discriminant == 0) {
118*c8dee2aaSAndroid Build Coastguard Worker         return {discriminant, B / A, B / A};
119*c8dee2aaSAndroid Build Coastguard Worker     }
120*c8dee2aaSAndroid Build Coastguard Worker 
121*c8dee2aaSAndroid Build Coastguard Worker     if (discriminant > 0) {
122*c8dee2aaSAndroid Build Coastguard Worker         const double D = sqrt(discriminant);
123*c8dee2aaSAndroid Build Coastguard Worker         const double R = B > 0 ? B + D : B - D;
124*c8dee2aaSAndroid Build Coastguard Worker         return {discriminant, R / A, C / R};
125*c8dee2aaSAndroid Build Coastguard Worker     }
126*c8dee2aaSAndroid Build Coastguard Worker 
127*c8dee2aaSAndroid Build Coastguard Worker     // The discriminant is negative or is not finite.
128*c8dee2aaSAndroid Build Coastguard Worker     return {discriminant, NAN, NAN};
129*c8dee2aaSAndroid Build Coastguard Worker }
130*c8dee2aaSAndroid Build Coastguard Worker 
zero_if_tiny(double x)131*c8dee2aaSAndroid Build Coastguard Worker static double zero_if_tiny(double x) {
132*c8dee2aaSAndroid Build Coastguard Worker     return sk_double_nearly_zero(x) ? 0 : x;
133*c8dee2aaSAndroid Build Coastguard Worker }
134*c8dee2aaSAndroid Build Coastguard Worker 
RootsReal(const double A,const double B,const double C,double solution[2])135*c8dee2aaSAndroid Build Coastguard Worker int SkQuads::RootsReal(const double A, const double B, const double C, double solution[2]) {
136*c8dee2aaSAndroid Build Coastguard Worker 
137*c8dee2aaSAndroid Build Coastguard Worker     if (close_to_linear(A, B)) {
138*c8dee2aaSAndroid Build Coastguard Worker         return solve_linear(B, C, solution);
139*c8dee2aaSAndroid Build Coastguard Worker     }
140*c8dee2aaSAndroid Build Coastguard Worker 
141*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(A != 0);
142*c8dee2aaSAndroid Build Coastguard Worker     auto [discriminant, root0, root1] = Roots(A, -0.5 * B, C);
143*c8dee2aaSAndroid Build Coastguard Worker 
144*c8dee2aaSAndroid Build Coastguard Worker     // Handle invariants to mesh with existing code from here on.
145*c8dee2aaSAndroid Build Coastguard Worker     if (!std::isfinite(discriminant) || discriminant < 0) {
146*c8dee2aaSAndroid Build Coastguard Worker         return 0;
147*c8dee2aaSAndroid Build Coastguard Worker     }
148*c8dee2aaSAndroid Build Coastguard Worker 
149*c8dee2aaSAndroid Build Coastguard Worker     int roots = 0;
150*c8dee2aaSAndroid Build Coastguard Worker     if (const double r0 = zero_if_tiny(root0); std::isfinite(r0)) {
151*c8dee2aaSAndroid Build Coastguard Worker         solution[roots++] = r0;
152*c8dee2aaSAndroid Build Coastguard Worker     }
153*c8dee2aaSAndroid Build Coastguard Worker     if (const double r1 = zero_if_tiny(root1); std::isfinite(r1)) {
154*c8dee2aaSAndroid Build Coastguard Worker         solution[roots++] = r1;
155*c8dee2aaSAndroid Build Coastguard Worker     }
156*c8dee2aaSAndroid Build Coastguard Worker 
157*c8dee2aaSAndroid Build Coastguard Worker     if (roots == 2 && sk_doubles_nearly_equal_ulps(solution[0], solution[1])) {
158*c8dee2aaSAndroid Build Coastguard Worker         roots = 1;
159*c8dee2aaSAndroid Build Coastguard Worker     }
160*c8dee2aaSAndroid Build Coastguard Worker 
161*c8dee2aaSAndroid Build Coastguard Worker     return roots;
162*c8dee2aaSAndroid Build Coastguard Worker }
163*c8dee2aaSAndroid Build Coastguard Worker 
EvalAt(double A,double B,double C,double t)164*c8dee2aaSAndroid Build Coastguard Worker double SkQuads::EvalAt(double A, double B, double C, double t) {
165*c8dee2aaSAndroid Build Coastguard Worker     // Use fused-multiply-add to reduce the amount of round-off error between terms.
166*c8dee2aaSAndroid Build Coastguard Worker     return std::fma(std::fma(A, t, B), t, C);
167*c8dee2aaSAndroid Build Coastguard Worker }
168