/src/mozilla-central/gfx/2d/BezierUtils.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #include "BezierUtils.h" |
8 | | |
9 | | #include "PathHelpers.h" |
10 | | |
11 | | namespace mozilla { |
12 | | namespace gfx { |
13 | | |
14 | | Point |
15 | | GetBezierPoint(const Bezier& aBezier, Float t) |
16 | 0 | { |
17 | 0 | Float s = 1.0f - t; |
18 | 0 |
|
19 | 0 | return Point( |
20 | 0 | aBezier.mPoints[0].x * s * s * s + |
21 | 0 | 3.0f * aBezier.mPoints[1].x * t * s * s + |
22 | 0 | 3.0f * aBezier.mPoints[2].x * t * t * s + |
23 | 0 | aBezier.mPoints[3].x * t * t * t, |
24 | 0 | aBezier.mPoints[0].y * s * s * s + |
25 | 0 | 3.0f * aBezier.mPoints[1].y * t * s * s + |
26 | 0 | 3.0f * aBezier.mPoints[2].y * t * t * s + |
27 | 0 | aBezier.mPoints[3].y * t * t * t |
28 | 0 | ); |
29 | 0 | } |
30 | | |
31 | | Point |
32 | | GetBezierDifferential(const Bezier& aBezier, Float t) |
33 | 0 | { |
34 | 0 | // Return P'(t). |
35 | 0 |
|
36 | 0 | Float s = 1.0f - t; |
37 | 0 |
|
38 | 0 | return Point( |
39 | 0 | -3.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s * s + |
40 | 0 | 2.0f * (aBezier.mPoints[1].x - aBezier.mPoints[2].x) * t * s + |
41 | 0 | (aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t * t), |
42 | 0 | -3.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s * s + |
43 | 0 | 2.0f * (aBezier.mPoints[1].y - aBezier.mPoints[2].y) * t * s+ |
44 | 0 | (aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t * t) |
45 | 0 | ); |
46 | 0 | } |
47 | | |
48 | | Point |
49 | | GetBezierDifferential2(const Bezier& aBezier, Float t) |
50 | 0 | { |
51 | 0 | // Return P''(t). |
52 | 0 |
|
53 | 0 | Float s = 1.0f - t; |
54 | 0 |
|
55 | 0 | return Point( |
56 | 0 | 6.0f * ((aBezier.mPoints[0].x - aBezier.mPoints[1].x) * s - |
57 | 0 | (aBezier.mPoints[1].x - aBezier.mPoints[2].x) * (s - t) - |
58 | 0 | (aBezier.mPoints[2].x - aBezier.mPoints[3].x) * t), |
59 | 0 | 6.0f * ((aBezier.mPoints[0].y - aBezier.mPoints[1].y) * s - |
60 | 0 | (aBezier.mPoints[1].y - aBezier.mPoints[2].y) * (s - t) - |
61 | 0 | (aBezier.mPoints[2].y - aBezier.mPoints[3].y) * t) |
62 | 0 | ); |
63 | 0 | } |
64 | | |
65 | | Float |
66 | | GetBezierLength(const Bezier& aBezier, Float a, Float b) |
67 | 0 | { |
68 | 0 | if (a < 0.5f && b > 0.5f) { |
69 | 0 | // To increase the accuracy, split into two parts. |
70 | 0 | return GetBezierLength(aBezier, a, 0.5f) + |
71 | 0 | GetBezierLength(aBezier, 0.5f, b); |
72 | 0 | } |
73 | 0 | |
74 | 0 | // Calculate length of simple bezier curve with Simpson's rule. |
75 | 0 | // _ |
76 | 0 | // / b |
77 | 0 | // length = | |P'(x)| dx |
78 | 0 | // _/ a |
79 | 0 | // |
80 | 0 | // b - a a + b |
81 | 0 | // = ----- [ |P'(a)| + 4 |P'(-----)| + |P'(b)| ] |
82 | 0 | // 6 2 |
83 | 0 | |
84 | 0 | Float fa = GetBezierDifferential(aBezier, a).Length(); |
85 | 0 | Float fab = GetBezierDifferential(aBezier, (a + b) / 2.0f).Length(); |
86 | 0 | Float fb = GetBezierDifferential(aBezier, b).Length(); |
87 | 0 |
|
88 | 0 | return (b - a) / 6.0f * (fa + 4.0f * fab + fb); |
89 | 0 | } |
90 | | |
91 | | static void |
92 | | SplitBezierA(Bezier* aSubBezier, const Bezier& aBezier, Float t) |
93 | 0 | { |
94 | 0 | // Split bezier curve into [0,t] and [t,1] parts, and return [0,t] part. |
95 | 0 |
|
96 | 0 | Float s = 1.0f - t; |
97 | 0 |
|
98 | 0 | Point tmp1; |
99 | 0 | Point tmp2; |
100 | 0 |
|
101 | 0 | aSubBezier->mPoints[0] = aBezier.mPoints[0]; |
102 | 0 |
|
103 | 0 | aSubBezier->mPoints[1] = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t; |
104 | 0 | tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t; |
105 | 0 | tmp2 = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t; |
106 | 0 |
|
107 | 0 | aSubBezier->mPoints[2] = aSubBezier->mPoints[1] * s + tmp1 * t; |
108 | 0 | tmp1 = tmp1 * s + tmp2 * t; |
109 | 0 |
|
110 | 0 | aSubBezier->mPoints[3] = aSubBezier->mPoints[2] * s + tmp1 * t; |
111 | 0 | } |
112 | | |
113 | | static void |
114 | | SplitBezierB(Bezier* aSubBezier, const Bezier& aBezier, Float t) |
115 | 0 | { |
116 | 0 | // Split bezier curve into [0,t] and [t,1] parts, and return [t,1] part. |
117 | 0 |
|
118 | 0 | Float s = 1.0f - t; |
119 | 0 |
|
120 | 0 | Point tmp1; |
121 | 0 | Point tmp2; |
122 | 0 |
|
123 | 0 | aSubBezier->mPoints[3] = aBezier.mPoints[3]; |
124 | 0 |
|
125 | 0 | aSubBezier->mPoints[2] = aBezier.mPoints[2] * s + aBezier.mPoints[3] * t; |
126 | 0 | tmp1 = aBezier.mPoints[1] * s + aBezier.mPoints[2] * t; |
127 | 0 | tmp2 = aBezier.mPoints[0] * s + aBezier.mPoints[1] * t; |
128 | 0 |
|
129 | 0 | aSubBezier->mPoints[1] = tmp1 * s + aSubBezier->mPoints[2] * t; |
130 | 0 | tmp1 = tmp2 * s + tmp1 * t; |
131 | 0 |
|
132 | 0 | aSubBezier->mPoints[0] = tmp1 * s + aSubBezier->mPoints[1] * t; |
133 | 0 | } |
134 | | |
135 | | void |
136 | | GetSubBezier(Bezier* aSubBezier, const Bezier& aBezier, Float t1, Float t2) |
137 | 0 | { |
138 | 0 | Bezier tmp; |
139 | 0 | SplitBezierB(&tmp, aBezier, t1); |
140 | 0 |
|
141 | 0 | Float range = 1.0f - t1; |
142 | 0 | if (range == 0.0f) { |
143 | 0 | *aSubBezier = tmp; |
144 | 0 | } else { |
145 | 0 | SplitBezierA(aSubBezier, tmp, (t2 - t1) / range); |
146 | 0 | } |
147 | 0 | } |
148 | | |
149 | | static Point |
150 | | BisectBezierNearestPoint(const Bezier& aBezier, const Point& aTarget, |
151 | | Float* aT) |
152 | 0 | { |
153 | 0 | // Find a nearest point on bezier curve with Binary search. |
154 | 0 | // Called from FindBezierNearestPoint. |
155 | 0 |
|
156 | 0 | Float lower = 0.0f; |
157 | 0 | Float upper = 1.0f; |
158 | 0 | Float t; |
159 | 0 |
|
160 | 0 | Point P, lastP; |
161 | 0 | const size_t MAX_LOOP = 32; |
162 | 0 | const Float DIST_MARGIN = 0.1f; |
163 | 0 | const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN; |
164 | 0 | const Float DIFF = 0.0001f; |
165 | 0 | for (size_t i = 0; i < MAX_LOOP; i++) { |
166 | 0 | t = (upper + lower) / 2.0f; |
167 | 0 | P = GetBezierPoint(aBezier, t); |
168 | 0 |
|
169 | 0 | // Check if it converged. |
170 | 0 | if (i > 0 && (lastP - P).LengthSquare() < DIST_MARGIN_SQUARE) { |
171 | 0 | break; |
172 | 0 | } |
173 | 0 | |
174 | 0 | Float distSquare = (P - aTarget).LengthSquare(); |
175 | 0 | if ((GetBezierPoint(aBezier, t + DIFF) - aTarget).LengthSquare() < |
176 | 0 | distSquare) { |
177 | 0 | lower = t; |
178 | 0 | } else if ((GetBezierPoint(aBezier, t - DIFF) - aTarget).LengthSquare() < |
179 | 0 | distSquare) { |
180 | 0 | upper = t; |
181 | 0 | } else { |
182 | 0 | break; |
183 | 0 | } |
184 | 0 | |
185 | 0 | lastP = P; |
186 | 0 | } |
187 | 0 |
|
188 | 0 | if (aT) { |
189 | 0 | *aT = t; |
190 | 0 | } |
191 | 0 |
|
192 | 0 | return P; |
193 | 0 | } |
194 | | |
195 | | Point |
196 | | FindBezierNearestPoint(const Bezier& aBezier, const Point& aTarget, |
197 | | Float aInitialT, Float* aT) |
198 | 0 | { |
199 | 0 | // Find a nearest point on bezier curve with Newton's method. |
200 | 0 | // It converges within 4 iterations in most cases. |
201 | 0 | // |
202 | 0 | // f(t_n) |
203 | 0 | // t_{n+1} = t_n - --------- |
204 | 0 | // f'(t_n) |
205 | 0 | // |
206 | 0 | // d 2 |
207 | 0 | // f(t) = ---- | P(t) - aTarget | |
208 | 0 | // dt |
209 | 0 |
|
210 | 0 | Float t = aInitialT; |
211 | 0 | Point P; |
212 | 0 | Point lastP = GetBezierPoint(aBezier, t); |
213 | 0 |
|
214 | 0 | const size_t MAX_LOOP = 4; |
215 | 0 | const Float DIST_MARGIN = 0.1f; |
216 | 0 | const Float DIST_MARGIN_SQUARE = DIST_MARGIN * DIST_MARGIN; |
217 | 0 | for (size_t i = 0; i <= MAX_LOOP; i++) { |
218 | 0 | Point dP = GetBezierDifferential(aBezier, t); |
219 | 0 | Point ddP = GetBezierDifferential2(aBezier, t); |
220 | 0 | Float f = 2.0f * (lastP.DotProduct(dP) - aTarget.DotProduct(dP)); |
221 | 0 | Float df = 2.0f * (dP.DotProduct(dP) + lastP.DotProduct(ddP) - |
222 | 0 | aTarget.DotProduct(ddP)); |
223 | 0 | t = t - f / df; |
224 | 0 | P = GetBezierPoint(aBezier, t); |
225 | 0 | if ((P - lastP).LengthSquare() < DIST_MARGIN_SQUARE) { |
226 | 0 | break; |
227 | 0 | } |
228 | 0 | lastP = P; |
229 | 0 |
|
230 | 0 | if (i == MAX_LOOP) { |
231 | 0 | // If aInitialT is too bad, it won't converge in a few iterations, |
232 | 0 | // fallback to binary search. |
233 | 0 | return BisectBezierNearestPoint(aBezier, aTarget, aT); |
234 | 0 | } |
235 | 0 | } |
236 | 0 |
|
237 | 0 | if (aT) { |
238 | 0 | *aT = t; |
239 | 0 | } |
240 | 0 |
|
241 | 0 | return P; |
242 | 0 | } |
243 | | |
244 | | void |
245 | | GetBezierPointsForCorner(Bezier* aBezier, Corner aCorner, |
246 | | const Point& aCornerPoint, const Size& aCornerSize) |
247 | 0 | { |
248 | 0 | // Calculate bezier control points for elliptic arc. |
249 | 0 |
|
250 | 0 | const Float signsList[4][2] = { |
251 | 0 | { +1.0f, +1.0f }, |
252 | 0 | { -1.0f, +1.0f }, |
253 | 0 | { -1.0f, -1.0f }, |
254 | 0 | { +1.0f, -1.0f } |
255 | 0 | }; |
256 | 0 | const Float (& signs)[2] = signsList[aCorner]; |
257 | 0 |
|
258 | 0 | aBezier->mPoints[0] = aCornerPoint; |
259 | 0 | aBezier->mPoints[0].x += signs[0] * aCornerSize.width; |
260 | 0 |
|
261 | 0 | aBezier->mPoints[1] = aBezier->mPoints[0]; |
262 | 0 | aBezier->mPoints[1].x -= signs[0] * aCornerSize.width * kKappaFactor; |
263 | 0 |
|
264 | 0 | aBezier->mPoints[3] = aCornerPoint; |
265 | 0 | aBezier->mPoints[3].y += signs[1] * aCornerSize.height; |
266 | 0 |
|
267 | 0 | aBezier->mPoints[2] = aBezier->mPoints[3]; |
268 | 0 | aBezier->mPoints[2].y -= signs[1] * aCornerSize.height * kKappaFactor; |
269 | 0 | } |
270 | | |
271 | | Float |
272 | | GetQuarterEllipticArcLength(Float a, Float b) |
273 | 0 | { |
274 | 0 | // Calculate the approximate length of a quarter elliptic arc formed by radii |
275 | 0 | // (a, b), by Ramanujan's approximation of the perimeter p of an ellipse. |
276 | 0 | // _ _ |
277 | 0 | // | 2 | |
278 | 0 | // | 3 * (a - b) | |
279 | 0 | // p = PI | (a + b) + ------------------------------------------- | |
280 | 0 | // | 2 2 | |
281 | 0 | // |_ 10 * (a + b) + sqrt(a + 14 * a * b + b ) _| |
282 | 0 | // |
283 | 0 | // _ _ |
284 | 0 | // | 2 | |
285 | 0 | // | 3 * (a - b) | |
286 | 0 | // = PI | (a + b) + -------------------------------------------------- | |
287 | 0 | // | 2 2 | |
288 | 0 | // |_ 10 * (a + b) + sqrt(4 * (a + b) - 3 * (a - b) ) _| |
289 | 0 | // |
290 | 0 | // _ _ |
291 | 0 | // | 2 | |
292 | 0 | // | 3 * S | |
293 | 0 | // = PI | A + -------------------------------------- | |
294 | 0 | // | 2 2 | |
295 | 0 | // |_ 10 * A + sqrt(4 * A - 3 * S ) _| |
296 | 0 | // |
297 | 0 | // where A = a + b, S = a - b |
298 | 0 |
|
299 | 0 | Float A = a + b, S = a - b; |
300 | 0 | Float A2 = A * A, S2 = S * S; |
301 | 0 | Float p = M_PI * (A + 3.0f * S2 / (10.0f * A + sqrt(4.0f * A2 - 3.0f * S2))); |
302 | 0 | return p / 4.0f; |
303 | 0 | } |
304 | | |
305 | | Float |
306 | | CalculateDistanceToEllipticArc(const Point& P, const Point& normal, |
307 | | const Point& origin, Float width, Float height) |
308 | 0 | { |
309 | 0 | // Solve following equations with n and return smaller n. |
310 | 0 | // |
311 | 0 | // / (x, y) = P + n * normal |
312 | 0 | // | |
313 | 0 | // < _ _ 2 _ _ 2 |
314 | 0 | // | | x - origin.x | | y - origin.y | |
315 | 0 | // | | ------------ | + | ------------ | = 1 |
316 | 0 | // \ |_ width _| |_ height _| |
317 | 0 |
|
318 | 0 | Float a = (P.x - origin.x) / width; |
319 | 0 | Float b = normal.x / width; |
320 | 0 | Float c = (P.y - origin.y) / height; |
321 | 0 | Float d = normal.y / height; |
322 | 0 |
|
323 | 0 | Float A = b * b + d * d; |
324 | 0 | Float B = a * b + c * d; |
325 | 0 | Float C = a * a + c * c - 1; |
326 | 0 |
|
327 | 0 | Float S = sqrt(B * B - A * C); |
328 | 0 |
|
329 | 0 | Float n1 = - B + S; |
330 | 0 | Float n2 = - B - S; |
331 | 0 |
|
332 | | #ifdef DEBUG |
333 | | Float epsilon = (Float) 0.001; |
334 | | MOZ_ASSERT(n1 >= -epsilon); |
335 | | MOZ_ASSERT(n2 >= -epsilon); |
336 | | #endif |
337 | |
|
338 | 0 | return std::max((n1 < n2 ? n1 : n2) / A, (Float) 0.0); |
339 | 0 | } |
340 | | |
341 | | } // namespace gfx |
342 | | } // namespace mozilla |