Path.cpp 17 KB

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