|
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "2D.h" |
|
7 #include "PathAnalysis.h" |
|
8 #include "PathHelpers.h" |
|
9 |
|
10 namespace mozilla { |
|
11 namespace gfx { |
|
12 |
|
13 static float CubicRoot(float aValue) { |
|
14 if (aValue < 0.0) { |
|
15 return -CubicRoot(-aValue); |
|
16 } |
|
17 else { |
|
18 return powf(aValue, 1.0f / 3.0f); |
|
19 } |
|
20 } |
|
21 |
|
22 struct BezierControlPoints |
|
23 { |
|
24 BezierControlPoints() {} |
|
25 BezierControlPoints(const Point &aCP1, const Point &aCP2, |
|
26 const Point &aCP3, const Point &aCP4) |
|
27 : mCP1(aCP1), mCP2(aCP2), mCP3(aCP3), mCP4(aCP4) |
|
28 { |
|
29 } |
|
30 |
|
31 Point mCP1, mCP2, mCP3, mCP4; |
|
32 }; |
|
33 |
|
34 void |
|
35 FlattenBezier(const BezierControlPoints &aPoints, |
|
36 PathSink *aSink, Float aTolerance); |
|
37 |
|
38 |
|
39 Path::Path() |
|
40 { |
|
41 } |
|
42 |
|
43 Path::~Path() |
|
44 { |
|
45 } |
|
46 |
|
47 Float |
|
48 Path::ComputeLength() |
|
49 { |
|
50 EnsureFlattenedPath(); |
|
51 return mFlattenedPath->ComputeLength(); |
|
52 } |
|
53 |
|
54 Point |
|
55 Path::ComputePointAtLength(Float aLength, Point* aTangent) |
|
56 { |
|
57 EnsureFlattenedPath(); |
|
58 return mFlattenedPath->ComputePointAtLength(aLength, aTangent); |
|
59 } |
|
60 |
|
61 void |
|
62 Path::EnsureFlattenedPath() |
|
63 { |
|
64 if (!mFlattenedPath) { |
|
65 mFlattenedPath = new FlattenedPath(); |
|
66 StreamToSink(mFlattenedPath); |
|
67 } |
|
68 } |
|
69 |
|
70 // This is the maximum deviation we allow (with an additional ~20% margin of |
|
71 // error) of the approximation from the actual Bezier curve. |
|
72 const Float kFlatteningTolerance = 0.0001f; |
|
73 |
|
74 void |
|
75 FlattenedPath::MoveTo(const Point &aPoint) |
|
76 { |
|
77 MOZ_ASSERT(!mCalculatedLength); |
|
78 FlatPathOp op; |
|
79 op.mType = FlatPathOp::OP_MOVETO; |
|
80 op.mPoint = aPoint; |
|
81 mPathOps.push_back(op); |
|
82 |
|
83 mLastMove = aPoint; |
|
84 } |
|
85 |
|
86 void |
|
87 FlattenedPath::LineTo(const Point &aPoint) |
|
88 { |
|
89 MOZ_ASSERT(!mCalculatedLength); |
|
90 FlatPathOp op; |
|
91 op.mType = FlatPathOp::OP_LINETO; |
|
92 op.mPoint = aPoint; |
|
93 mPathOps.push_back(op); |
|
94 } |
|
95 |
|
96 void |
|
97 FlattenedPath::BezierTo(const Point &aCP1, |
|
98 const Point &aCP2, |
|
99 const Point &aCP3) |
|
100 { |
|
101 MOZ_ASSERT(!mCalculatedLength); |
|
102 FlattenBezier(BezierControlPoints(CurrentPoint(), aCP1, aCP2, aCP3), this, kFlatteningTolerance); |
|
103 } |
|
104 |
|
105 void |
|
106 FlattenedPath::QuadraticBezierTo(const Point &aCP1, |
|
107 const Point &aCP2) |
|
108 { |
|
109 MOZ_ASSERT(!mCalculatedLength); |
|
110 // We need to elevate the degree of this quadratic Bézier to cubic, so we're |
|
111 // going to add an intermediate control point, and recompute control point 1. |
|
112 // The first and last control points remain the same. |
|
113 // This formula can be found on http://fontforge.sourceforge.net/bezier.html |
|
114 Point CP0 = CurrentPoint(); |
|
115 Point CP1 = (CP0 + aCP1 * 2.0) / 3.0; |
|
116 Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0; |
|
117 Point CP3 = aCP2; |
|
118 |
|
119 BezierTo(CP1, CP2, CP3); |
|
120 } |
|
121 |
|
122 void |
|
123 FlattenedPath::Close() |
|
124 { |
|
125 MOZ_ASSERT(!mCalculatedLength); |
|
126 LineTo(mLastMove); |
|
127 } |
|
128 |
|
129 void |
|
130 FlattenedPath::Arc(const Point &aOrigin, float aRadius, float aStartAngle, |
|
131 float aEndAngle, bool aAntiClockwise) |
|
132 { |
|
133 ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle, aAntiClockwise); |
|
134 } |
|
135 |
|
136 Float |
|
137 FlattenedPath::ComputeLength() |
|
138 { |
|
139 if (!mCalculatedLength) { |
|
140 Point currentPoint; |
|
141 |
|
142 for (uint32_t i = 0; i < mPathOps.size(); i++) { |
|
143 if (mPathOps[i].mType == FlatPathOp::OP_MOVETO) { |
|
144 currentPoint = mPathOps[i].mPoint; |
|
145 } else { |
|
146 mCachedLength += Distance(currentPoint, mPathOps[i].mPoint); |
|
147 currentPoint = mPathOps[i].mPoint; |
|
148 } |
|
149 } |
|
150 |
|
151 mCalculatedLength = true; |
|
152 } |
|
153 |
|
154 return mCachedLength; |
|
155 } |
|
156 |
|
157 Point |
|
158 FlattenedPath::ComputePointAtLength(Float aLength, Point *aTangent) |
|
159 { |
|
160 // We track the last point that -wasn't- in the same place as the current |
|
161 // point so if we pass the edge of the path with a bunch of zero length |
|
162 // paths we still get the correct tangent vector. |
|
163 Point lastPointSinceMove; |
|
164 Point currentPoint; |
|
165 for (uint32_t i = 0; i < mPathOps.size(); i++) { |
|
166 if (mPathOps[i].mType == FlatPathOp::OP_MOVETO) { |
|
167 if (Distance(currentPoint, mPathOps[i].mPoint)) { |
|
168 lastPointSinceMove = currentPoint; |
|
169 } |
|
170 currentPoint = mPathOps[i].mPoint; |
|
171 } else { |
|
172 Float segmentLength = Distance(currentPoint, mPathOps[i].mPoint); |
|
173 |
|
174 if (segmentLength) { |
|
175 lastPointSinceMove = currentPoint; |
|
176 if (segmentLength > aLength) { |
|
177 Point currentVector = mPathOps[i].mPoint - currentPoint; |
|
178 Point tangent = currentVector / segmentLength; |
|
179 if (aTangent) { |
|
180 *aTangent = tangent; |
|
181 } |
|
182 return currentPoint + tangent * aLength; |
|
183 } |
|
184 } |
|
185 |
|
186 aLength -= segmentLength; |
|
187 currentPoint = mPathOps[i].mPoint; |
|
188 } |
|
189 } |
|
190 |
|
191 Point currentVector = currentPoint - lastPointSinceMove; |
|
192 if (aTangent) { |
|
193 if (hypotf(currentVector.x, currentVector.y)) { |
|
194 *aTangent = currentVector / hypotf(currentVector.x, currentVector.y); |
|
195 } else { |
|
196 *aTangent = Point(); |
|
197 } |
|
198 } |
|
199 return currentPoint; |
|
200 } |
|
201 |
|
202 // This function explicitly permits aControlPoints to refer to the same object |
|
203 // as either of the other arguments. |
|
204 static void |
|
205 SplitBezier(const BezierControlPoints &aControlPoints, |
|
206 BezierControlPoints *aFirstSegmentControlPoints, |
|
207 BezierControlPoints *aSecondSegmentControlPoints, |
|
208 Float t) |
|
209 { |
|
210 MOZ_ASSERT(aSecondSegmentControlPoints); |
|
211 |
|
212 *aSecondSegmentControlPoints = aControlPoints; |
|
213 |
|
214 Point cp1a = aControlPoints.mCP1 + (aControlPoints.mCP2 - aControlPoints.mCP1) * t; |
|
215 Point cp2a = aControlPoints.mCP2 + (aControlPoints.mCP3 - aControlPoints.mCP2) * t; |
|
216 Point cp1aa = cp1a + (cp2a - cp1a) * t; |
|
217 Point cp3a = aControlPoints.mCP3 + (aControlPoints.mCP4 - aControlPoints.mCP3) * t; |
|
218 Point cp2aa = cp2a + (cp3a - cp2a) * t; |
|
219 Point cp1aaa = cp1aa + (cp2aa - cp1aa) * t; |
|
220 aSecondSegmentControlPoints->mCP4 = aControlPoints.mCP4; |
|
221 |
|
222 if(aFirstSegmentControlPoints) { |
|
223 aFirstSegmentControlPoints->mCP1 = aControlPoints.mCP1; |
|
224 aFirstSegmentControlPoints->mCP2 = cp1a; |
|
225 aFirstSegmentControlPoints->mCP3 = cp1aa; |
|
226 aFirstSegmentControlPoints->mCP4 = cp1aaa; |
|
227 } |
|
228 aSecondSegmentControlPoints->mCP1 = cp1aaa; |
|
229 aSecondSegmentControlPoints->mCP2 = cp2aa; |
|
230 aSecondSegmentControlPoints->mCP3 = cp3a; |
|
231 } |
|
232 |
|
233 static void |
|
234 FlattenBezierCurveSegment(const BezierControlPoints &aControlPoints, |
|
235 PathSink *aSink, |
|
236 Float aTolerance) |
|
237 { |
|
238 /* The algorithm implemented here is based on: |
|
239 * http://cis.usouthal.edu/~hain/general/Publications/Bezier/Bezier%20Offset%20Curves.pdf |
|
240 * |
|
241 * The basic premise is that for a small t the third order term in the |
|
242 * equation of a cubic bezier curve is insignificantly small. This can |
|
243 * then be approximated by a quadratic equation for which the maximum |
|
244 * difference from a linear approximation can be much more easily determined. |
|
245 */ |
|
246 BezierControlPoints currentCP = aControlPoints; |
|
247 |
|
248 Float t = 0; |
|
249 while (t < 1.0f) { |
|
250 Point cp21 = currentCP.mCP2 - currentCP.mCP3; |
|
251 Point cp31 = currentCP.mCP3 - currentCP.mCP1; |
|
252 |
|
253 Float s3 = (cp31.x * cp21.y - cp31.y * cp21.x) / hypotf(cp21.x, cp21.y); |
|
254 |
|
255 t = 2 * Float(sqrt(aTolerance / (3. * abs(s3)))); |
|
256 |
|
257 if (t >= 1.0f) { |
|
258 aSink->LineTo(aControlPoints.mCP4); |
|
259 break; |
|
260 } |
|
261 |
|
262 Point prevCP2, prevCP3, nextCP1, nextCP2, nextCP3; |
|
263 SplitBezier(currentCP, nullptr, ¤tCP, t); |
|
264 |
|
265 aSink->LineTo(currentCP.mCP1); |
|
266 } |
|
267 } |
|
268 |
|
269 static inline void |
|
270 FindInflectionApproximationRange(BezierControlPoints aControlPoints, |
|
271 Float *aMin, Float *aMax, Float aT, |
|
272 Float aTolerance) |
|
273 { |
|
274 SplitBezier(aControlPoints, nullptr, &aControlPoints, aT); |
|
275 |
|
276 Point cp21 = aControlPoints.mCP2 - aControlPoints.mCP1; |
|
277 Point cp41 = aControlPoints.mCP4 - aControlPoints.mCP1; |
|
278 |
|
279 if (cp21.x == 0 && cp21.y == 0) { |
|
280 // In this case s3 becomes lim[n->0] (cp41.x * n) / n - (cp41.y * n) / n = cp41.x - cp41.y. |
|
281 |
|
282 // Use the absolute value so that Min and Max will correspond with the |
|
283 // minimum and maximum of the range. |
|
284 *aMin = aT - CubicRoot(abs(aTolerance / (cp41.x - cp41.y))); |
|
285 *aMax = aT + CubicRoot(abs(aTolerance / (cp41.x - cp41.y))); |
|
286 return; |
|
287 } |
|
288 |
|
289 Float s3 = (cp41.x * cp21.y - cp41.y * cp21.x) / hypotf(cp21.x, cp21.y); |
|
290 |
|
291 if (s3 == 0) { |
|
292 // This means within the precision we have it can be approximated |
|
293 // infinitely by a linear segment. Deal with this by specifying the |
|
294 // approximation range as extending beyond the entire curve. |
|
295 *aMin = -1.0f; |
|
296 *aMax = 2.0f; |
|
297 return; |
|
298 } |
|
299 |
|
300 Float tf = CubicRoot(abs(aTolerance / s3)); |
|
301 |
|
302 *aMin = aT - tf * (1 - aT); |
|
303 *aMax = aT + tf * (1 - aT); |
|
304 } |
|
305 |
|
306 /* Find the inflection points of a bezier curve. Will return false if the |
|
307 * curve is degenerate in such a way that it is best approximated by a straight |
|
308 * line. |
|
309 * |
|
310 * The below algorithm was written by Jeff Muizelaar <jmuizelaar@mozilla.com>, explanation follows: |
|
311 * |
|
312 * The lower inflection point is returned in aT1, the higher one in aT2. In the |
|
313 * case of a single inflection point this will be in aT1. |
|
314 * |
|
315 * The method is inspired by the algorithm in "analysis of in?ection points for planar cubic bezier curve" |
|
316 * |
|
317 * Here are some differences between this algorithm and versions discussed elsewhere in the literature: |
|
318 * |
|
319 * zhang et. al compute a0, d0 and e0 incrementally using the follow formula: |
|
320 * |
|
321 * Point a0 = CP2 - CP1 |
|
322 * Point a1 = CP3 - CP2 |
|
323 * Point a2 = CP4 - CP1 |
|
324 * |
|
325 * Point d0 = a1 - a0 |
|
326 * Point d1 = a2 - a1 |
|
327 |
|
328 * Point e0 = d1 - d0 |
|
329 * |
|
330 * this avoids any multiplications and may or may not be faster than the approach take below. |
|
331 * |
|
332 * "fast, precise flattening of cubic bezier path and ofset curves" by hain et. al |
|
333 * Point a = CP1 + 3 * CP2 - 3 * CP3 + CP4 |
|
334 * Point b = 3 * CP1 - 6 * CP2 + 3 * CP3 |
|
335 * Point c = -3 * CP1 + 3 * CP2 |
|
336 * Point d = CP1 |
|
337 * the a, b, c, d can be expressed in terms of a0, d0 and e0 defined above as: |
|
338 * c = 3 * a0 |
|
339 * b = 3 * d0 |
|
340 * a = e0 |
|
341 * |
|
342 * |
|
343 * a = 3a = a.y * b.x - a.x * b.y |
|
344 * b = 3b = a.y * c.x - a.x * c.y |
|
345 * c = 9c = b.y * c.x - b.x * c.y |
|
346 * |
|
347 * The additional multiples of 3 cancel each other out as show below: |
|
348 * |
|
349 * x = (-b + sqrt(b * b - 4 * a * c)) / (2 * a) |
|
350 * x = (-3 * b + sqrt(3 * b * 3 * b - 4 * a * 3 * 9 * c / 3)) / (2 * 3 * a) |
|
351 * x = 3 * (-b + sqrt(b * b - 4 * a * c)) / (2 * 3 * a) |
|
352 * x = (-b + sqrt(b * b - 4 * a * c)) / (2 * a) |
|
353 * |
|
354 * I haven't looked into whether the formulation of the quadratic formula in |
|
355 * hain has any numerical advantages over the one used below. |
|
356 */ |
|
357 static inline void |
|
358 FindInflectionPoints(const BezierControlPoints &aControlPoints, |
|
359 Float *aT1, Float *aT2, uint32_t *aCount) |
|
360 { |
|
361 // Find inflection points. |
|
362 // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation |
|
363 // of this approach. |
|
364 Point A = aControlPoints.mCP2 - aControlPoints.mCP1; |
|
365 Point B = aControlPoints.mCP3 - (aControlPoints.mCP2 * 2) + aControlPoints.mCP1; |
|
366 Point C = aControlPoints.mCP4 - (aControlPoints.mCP3 * 3) + (aControlPoints.mCP2 * 3) - aControlPoints.mCP1; |
|
367 |
|
368 Float a = Float(B.x) * C.y - Float(B.y) * C.x; |
|
369 Float b = Float(A.x) * C.y - Float(A.y) * C.x; |
|
370 Float c = Float(A.x) * B.y - Float(A.y) * B.x; |
|
371 |
|
372 if (a == 0) { |
|
373 // Not a quadratic equation. |
|
374 if (b == 0) { |
|
375 // Instead of a linear acceleration change we have a constant |
|
376 // acceleration change. This means the equation has no solution |
|
377 // and there are no inflection points, unless the constant is 0. |
|
378 // In that case the curve is a straight line, but we'll let |
|
379 // FlattenBezierCurveSegment deal with this. |
|
380 *aCount = 0; |
|
381 return; |
|
382 } |
|
383 *aT1 = -c / b; |
|
384 *aCount = 1; |
|
385 return; |
|
386 } else { |
|
387 Float discriminant = b * b - 4 * a * c; |
|
388 |
|
389 if (discriminant < 0) { |
|
390 // No inflection points. |
|
391 *aCount = 0; |
|
392 } else if (discriminant == 0) { |
|
393 *aCount = 1; |
|
394 *aT1 = -b / (2 * a); |
|
395 } else { |
|
396 /* Use the following formula for computing the roots: |
|
397 * |
|
398 * q = -1/2 * (b + sign(b) * sqrt(b^2 - 4ac)) |
|
399 * t1 = q / a |
|
400 * t2 = c / q |
|
401 */ |
|
402 Float q = sqrtf(discriminant); |
|
403 if (b < 0) { |
|
404 q = b - q; |
|
405 } else { |
|
406 q = b + q; |
|
407 } |
|
408 q *= Float(-1./2); |
|
409 |
|
410 *aT1 = q / a; |
|
411 *aT2 = c / q; |
|
412 if (*aT1 > *aT2) { |
|
413 std::swap(*aT1, *aT2); |
|
414 } |
|
415 *aCount = 2; |
|
416 } |
|
417 } |
|
418 |
|
419 return; |
|
420 } |
|
421 |
|
422 void |
|
423 FlattenBezier(const BezierControlPoints &aControlPoints, |
|
424 PathSink *aSink, Float aTolerance) |
|
425 { |
|
426 Float t1; |
|
427 Float t2; |
|
428 uint32_t count; |
|
429 |
|
430 FindInflectionPoints(aControlPoints, &t1, &t2, &count); |
|
431 |
|
432 // Check that at least one of the inflection points is inside [0..1] |
|
433 if (count == 0 || ((t1 < 0 || t1 > 1.0) && ((t2 < 0 || t2 > 1.0) || count == 1)) ) { |
|
434 FlattenBezierCurveSegment(aControlPoints, aSink, aTolerance); |
|
435 return; |
|
436 } |
|
437 |
|
438 Float t1min = t1, t1max = t1, t2min = t2, t2max = t2; |
|
439 |
|
440 BezierControlPoints remainingCP = aControlPoints; |
|
441 |
|
442 // For both inflection points, calulate the range where they can be linearly |
|
443 // approximated if they are positioned within [0,1] |
|
444 if (count > 0 && t1 >= 0 && t1 < 1.0) { |
|
445 FindInflectionApproximationRange(aControlPoints, &t1min, &t1max, t1, aTolerance); |
|
446 } |
|
447 if (count > 1 && t2 >= 0 && t2 < 1.0) { |
|
448 FindInflectionApproximationRange(aControlPoints, &t2min, &t2max, t2, aTolerance); |
|
449 } |
|
450 BezierControlPoints nextCPs = aControlPoints; |
|
451 BezierControlPoints prevCPs; |
|
452 |
|
453 // Process ranges. [t1min, t1max] and [t2min, t2max] are approximated by line |
|
454 // segments. |
|
455 if (t1min > 0) { |
|
456 // Flatten the Bezier up until the first inflection point's approximation |
|
457 // point. |
|
458 SplitBezier(aControlPoints, &prevCPs, |
|
459 &remainingCP, t1min); |
|
460 FlattenBezierCurveSegment(prevCPs, aSink, aTolerance); |
|
461 } |
|
462 if (t1max >= 0 && t1max < 1.0 && (count == 1 || t2min > t1max)) { |
|
463 // The second inflection point's approximation range begins after the end |
|
464 // of the first, approximate the first inflection point by a line and |
|
465 // subsequently flatten up until the end or the next inflection point. |
|
466 SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); |
|
467 |
|
468 aSink->LineTo(nextCPs.mCP1); |
|
469 |
|
470 if (count == 1 || (count > 1 && t2min >= 1.0)) { |
|
471 // No more inflection points to deal with, flatten the rest of the curve. |
|
472 FlattenBezierCurveSegment(nextCPs, aSink, aTolerance); |
|
473 } |
|
474 } else if (count > 1 && t2min > 1.0) { |
|
475 // We've already concluded t2min <= t1max, so if this is true the |
|
476 // approximation range for the first inflection point runs past the |
|
477 // end of the curve, draw a line to the end and we're done. |
|
478 aSink->LineTo(aControlPoints.mCP4); |
|
479 return; |
|
480 } |
|
481 |
|
482 if (count > 1 && t2min < 1.0 && t2max > 0) { |
|
483 if (t2min > 0 && t2min < t1max) { |
|
484 // In this case the t2 approximation range starts inside the t1 |
|
485 // approximation range. |
|
486 SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); |
|
487 aSink->LineTo(nextCPs.mCP1); |
|
488 } else if (t2min > 0 && t1max > 0) { |
|
489 SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); |
|
490 |
|
491 // Find a control points describing the portion of the curve between t1max and t2min. |
|
492 Float t2mina = (t2min - t1max) / (1 - t1max); |
|
493 SplitBezier(nextCPs, &prevCPs, &nextCPs, t2mina); |
|
494 FlattenBezierCurveSegment(prevCPs, aSink, aTolerance); |
|
495 } else if (t2min > 0) { |
|
496 // We have nothing interesting before t2min, find that bit and flatten it. |
|
497 SplitBezier(aControlPoints, &prevCPs, &nextCPs, t2min); |
|
498 FlattenBezierCurveSegment(prevCPs, aSink, aTolerance); |
|
499 } |
|
500 if (t2max < 1.0) { |
|
501 // Flatten the portion of the curve after t2max |
|
502 SplitBezier(aControlPoints, nullptr, &nextCPs, t2max); |
|
503 |
|
504 // Draw a line to the start, this is the approximation between t2min and |
|
505 // t2max. |
|
506 aSink->LineTo(nextCPs.mCP1); |
|
507 FlattenBezierCurveSegment(nextCPs, aSink, aTolerance); |
|
508 } else { |
|
509 // Our approximation range extends beyond the end of the curve. |
|
510 aSink->LineTo(aControlPoints.mCP4); |
|
511 return; |
|
512 } |
|
513 } |
|
514 } |
|
515 |
|
516 } |
|
517 } |