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