Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / gfx / 2d / BezierUtils.cpp
blob8c80d1c43fa969d7b5d044722a561f430a3b6a2c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "BezierUtils.h"
9 #include "PathHelpers.h"
11 namespace mozilla {
12 namespace gfx {
14 Point GetBezierPoint(const Bezier& aBezier, Float t) {
15 Float s = 1.0f - t;
17 return Point(aBezier.mPoints[0].x * s * s * s +
18 3.0f * aBezier.mPoints[1].x * t * s * s +
19 3.0f * aBezier.mPoints[2].x * t * t * s +
20 aBezier.mPoints[3].x * t * t * t,
21 aBezier.mPoints[0].y * s * s * s +
22 3.0f * aBezier.mPoints[1].y * t * s * s +
23 3.0f * aBezier.mPoints[2].y * t * t * s +
24 aBezier.mPoints[3].y * t * t * t);
27 Point GetBezierDifferential(const Bezier& aBezier, Float t) {
28 // Return P'(t).
30 Float s = 1.0f - t;
32 return Point(
33 -3.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s * s +
34 2.0f * (aBezier.mPoints[1].x - aBezier.mPoints[2].x) * t * s +
35 (aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t * t),
36 -3.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s * s +
37 2.0f * (aBezier.mPoints[1].y - aBezier.mPoints[2].y) * t * s +
38 (aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t * t));
41 Point GetBezierDifferential2(const Bezier& aBezier, Float t) {
42 // Return P''(t).
44 Float s = 1.0f - t;
46 return Point(6.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s -
47 (aBezier.mPoints[1].x - aBezier.mPoints[2].x) * (s - t) -
48 (aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t),
49 6.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s -
50 (aBezier.mPoints[1].y - aBezier.mPoints[2].y) * (s - t) -
51 (aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t));
54 Float GetBezierLength(const Bezier& aBezier, Float a, Float b) {
55 if (a < 0.5f && b > 0.5f) {
56 // To increase the accuracy, split into two parts.
57 return GetBezierLength(aBezier, a, 0.5f) +
58 GetBezierLength(aBezier, 0.5f, b);
61 // Calculate length of simple bezier curve with Simpson's rule.
62 // _
63 // / b
64 // length = | |P'(x)| dx
65 // _/ a
67 // b - a a + b
68 // = ----- [ |P'(a)| + 4 |P'(-----)| + |P'(b)| ]
69 // 6 2
71 Float fa = GetBezierDifferential(aBezier, a).Length();
72 Float fab = GetBezierDifferential(aBezier, (a + b) / 2.0f).Length();
73 Float fb = GetBezierDifferential(aBezier, b).Length();
75 return (b - a) / 6.0f * (fa + 4.0f * fab + fb);
78 static void SplitBezierA(Bezier* aSubBezier, const Bezier& aBezier, Float t) {
79 // Split bezier curve into [0,t] and [t,1] parts, and return [0,t] part.
81 Float s = 1.0f - t;
83 Point tmp1;
84 Point tmp2;
86 aSubBezier->mPoints[0] = aBezier.mPoints[0];
88 aSubBezier->mPoints[1] = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t;
89 tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t;
90 tmp2 = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t;
92 aSubBezier->mPoints[2] = aSubBezier->mPoints[1] * s + tmp1 * t;
93 tmp1 = tmp1 * s + tmp2 * t;
95 aSubBezier->mPoints[3] = aSubBezier->mPoints[2] * s + tmp1 * t;
98 static void SplitBezierB(Bezier* aSubBezier, const Bezier& aBezier, Float t) {
99 // Split bezier curve into [0,t] and [t,1] parts, and return [t,1] part.
101 Float s = 1.0f - t;
103 Point tmp1;
104 Point tmp2;
106 aSubBezier->mPoints[3] = aBezier.mPoints[3];
108 aSubBezier->mPoints[2] = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t;
109 tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t;
110 tmp2 = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t;
112 aSubBezier->mPoints[1] = tmp1 * s + aSubBezier->mPoints[2] * t;
113 tmp1 = tmp2 * s + tmp1 * t;
115 aSubBezier->mPoints[0] = tmp1 * s + aSubBezier->mPoints[1] * t;
118 void GetSubBezier(Bezier* aSubBezier, const Bezier& aBezier, Float t1,
119 Float t2) {
120 Bezier tmp;
121 SplitBezierB(&tmp, aBezier, t1);
123 Float range = 1.0f - t1;
124 if (range == 0.0f) {
125 *aSubBezier = tmp;
126 } else {
127 SplitBezierA(aSubBezier, tmp, (t2 - t1) / range);
131 static Point BisectBezierNearestPoint(const Bezier& aBezier,
132 const Point& aTarget, Float* aT) {
133 // Find a nearest point on bezier curve with Binary search.
134 // Called from FindBezierNearestPoint.
136 Float lower = 0.0f;
137 Float upper = 1.0f;
138 Float t;
140 Point P, lastP;
141 const size_t MAX_LOOP = 32;
142 const Float DIST_MARGIN = 0.1f;
143 const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN;
144 const Float DIFF = 0.0001f;
145 for (size_t i = 0; i < MAX_LOOP; i++) {
146 t = (upper + lower) / 2.0f;
147 P = GetBezierPoint(aBezier, t);
149 // Check if it converged.
150 if (i > 0 && (lastP - P).LengthSquare() < DIST_MARGIN_SQUARE) {
151 break;
154 Float distSquare = (P - aTarget).LengthSquare();
155 if ((GetBezierPoint(aBezier, t + DIFF) - aTarget).LengthSquare() <
156 distSquare) {
157 lower = t;
158 } else if ((GetBezierPoint(aBezier, t - DIFF) - aTarget).LengthSquare() <
159 distSquare) {
160 upper = t;
161 } else {
162 break;
165 lastP = P;
168 if (aT) {
169 *aT = t;
172 return P;
175 Point FindBezierNearestPoint(const Bezier& aBezier, const Point& aTarget,
176 Float aInitialT, Float* aT) {
177 // Find a nearest point on bezier curve with Newton's method.
178 // It converges within 4 iterations in most cases.
180 // f(t_n)
181 // t_{n+1} = t_n - ---------
182 // f'(t_n)
184 // d 2
185 // f(t) = ---- | P(t) - aTarget |
186 // dt
188 Float t = aInitialT;
189 Point P;
190 Point lastP = GetBezierPoint(aBezier, t);
192 const size_t MAX_LOOP = 4;
193 const Float DIST_MARGIN = 0.1f;
194 const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN;
195 for (size_t i = 0; i <= MAX_LOOP; i++) {
196 Point dP = GetBezierDifferential(aBezier, t);
197 Point ddP = GetBezierDifferential2(aBezier, t);
198 Float f = 2.0f * (lastP.DotProduct(dP) - aTarget.DotProduct(dP));
199 Float df = 2.0f * (dP.DotProduct(dP) + lastP.DotProduct(ddP) -
200 aTarget.DotProduct(ddP));
201 t = t - f / df;
202 P = GetBezierPoint(aBezier, t);
203 if ((P - lastP).LengthSquare() < DIST_MARGIN_SQUARE) {
204 break;
206 lastP = P;
208 if (i == MAX_LOOP) {
209 // If aInitialT is too bad, it won't converge in a few iterations,
210 // fallback to binary search.
211 return BisectBezierNearestPoint(aBezier, aTarget, aT);
215 if (aT) {
216 *aT = t;
219 return P;
222 void GetBezierPointsForCorner(Bezier* aBezier, Corner aCorner,
223 const Point& aCornerPoint,
224 const Size& aCornerSize) {
225 // Calculate bezier control points for elliptic arc.
227 const Float signsList[4][2] = {
228 {+1.0f, +1.0f}, {-1.0f, +1.0f}, {-1.0f, -1.0f}, {+1.0f, -1.0f}};
229 const Float(&signs)[2] = signsList[aCorner];
231 aBezier->mPoints[0] = aCornerPoint;
232 aBezier->mPoints[0].x += signs[0] * aCornerSize.width;
234 aBezier->mPoints[1] = aBezier->mPoints[0];
235 aBezier->mPoints[1].x -= signs[0] * aCornerSize.width * kKappaFactor;
237 aBezier->mPoints[3] = aCornerPoint;
238 aBezier->mPoints[3].y += signs[1] * aCornerSize.height;
240 aBezier->mPoints[2] = aBezier->mPoints[3];
241 aBezier->mPoints[2].y -= signs[1] * aCornerSize.height * kKappaFactor;
244 Float GetQuarterEllipticArcLength(Float a, Float b) {
245 // Calculate the approximate length of a quarter elliptic arc formed by radii
246 // (a, b), by Ramanujan's approximation of the perimeter p of an ellipse.
247 // _ _
248 // | 2 |
249 // | 3 * (a - b) |
250 // p = PI | (a + b) + ------------------------------------------- |
251 // | 2 2 |
252 // |_ 10 * (a + b) + sqrt(a + 14 * a * b + b ) _|
254 // _ _
255 // | 2 |
256 // | 3 * (a - b) |
257 // = PI | (a + b) + -------------------------------------------------- |
258 // | 2 2 |
259 // |_ 10 * (a + b) + sqrt(4 * (a + b) - 3 * (a - b) ) _|
261 // _ _
262 // | 2 |
263 // | 3 * S |
264 // = PI | A + -------------------------------------- |
265 // | 2 2 |
266 // |_ 10 * A + sqrt(4 * A - 3 * S ) _|
268 // where A = a + b, S = a - b
270 Float A = a + b, S = a - b;
271 Float A2 = A * A, S2 = S * S;
272 Float p = M_PI * (A + 3.0f * S2 / (10.0f * A + sqrt(4.0f * A2 - 3.0f * S2)));
273 return p / 4.0f;
276 Float CalculateDistanceToEllipticArc(const Point& P, const Point& normal,
277 const Point& origin, Float width,
278 Float height) {
279 // Solve following equations with n and return smaller n.
281 // / (x, y) = P + n * normal
282 // |
283 // < _ _ 2 _ _ 2
284 // | | x - origin.x | | y - origin.y |
285 // | | ------------ | + | ------------ | = 1
286 // \ |_ width _| |_ height _|
288 Float a = (P.x - origin.x) / width;
289 Float b = normal.x / width;
290 Float c = (P.y - origin.y) / height;
291 Float d = normal.y / height;
293 Float A = b * b + d * d;
294 // In the quadratic formulat B would be 2*(a*b+c*d), however we factor the 2
295 // out Here which cancels out later.
296 Float B = a * b + c * d;
297 Float C = a * a + c * c - 1.0;
299 Float signB = 1.0;
300 if (B < 0.0) {
301 signB = -1.0;
304 // 2nd degree polynomials are typically computed using the formulae
305 // r1 = -(B - sqrt(delta)) / (2 * A)
306 // r2 = -(B + sqrt(delta)) / (2 * A)
307 // However B - sqrt(delta) can be an inportant source of precision loss for
308 // one of the roots when computing the difference between two similar and
309 // large numbers. To avoid that we pick the root with no precision loss in r1
310 // and compute r2 using the Citardauq formula.
311 // Factoring out 2 from B earlier let
312 Float S = B + signB * sqrt(B * B - A * C);
313 Float r1 = -S / A;
314 Float r2 = -C / S;
316 #ifdef DEBUG
317 Float epsilon = (Float)0.001;
318 MOZ_ASSERT(r1 >= -epsilon);
319 MOZ_ASSERT(r2 >= -epsilon);
320 #endif
322 return std::max((r1 < r2 ? r1 : r2), (Float)0.0);
325 } // namespace gfx
326 } // namespace mozilla