gfx/2d/Path.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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/. */
     6 #include "2D.h"
     7 #include "PathAnalysis.h"
     8 #include "PathHelpers.h"
    10 namespace mozilla {
    11 namespace gfx {
    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 }
    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   }
    31   Point mCP1, mCP2, mCP3, mCP4;
    32 };
    34 void
    35 FlattenBezier(const BezierControlPoints &aPoints,
    36               PathSink *aSink, Float aTolerance);
    39 Path::Path()
    40 {
    41 }
    43 Path::~Path()
    44 {
    45 }
    47 Float
    48 Path::ComputeLength()
    49 {
    50   EnsureFlattenedPath();
    51   return mFlattenedPath->ComputeLength();
    52 }
    54 Point
    55 Path::ComputePointAtLength(Float aLength, Point* aTangent)
    56 {
    57   EnsureFlattenedPath();
    58   return mFlattenedPath->ComputePointAtLength(aLength, aTangent);
    59 }
    61 void
    62 Path::EnsureFlattenedPath()
    63 {
    64   if (!mFlattenedPath) {
    65     mFlattenedPath = new FlattenedPath();
    66     StreamToSink(mFlattenedPath);
    67   }
    68 }
    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;
    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);
    83   mLastMove = aPoint;
    84 }
    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 }
    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 }
   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;
   119   BezierTo(CP1, CP2, CP3);
   120 }
   122 void
   123 FlattenedPath::Close()
   124 {
   125   MOZ_ASSERT(!mCalculatedLength);
   126   LineTo(mLastMove);
   127 }
   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 }
   136 Float
   137 FlattenedPath::ComputeLength()
   138 {
   139   if (!mCalculatedLength) {
   140     Point currentPoint;
   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     }
   151     mCalculatedLength =  true;
   152   }
   154   return mCachedLength;
   155 }
   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);
   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       }
   186       aLength -= segmentLength;
   187       currentPoint = mPathOps[i].mPoint;
   188     }
   189   }
   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 }
   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);
   212   *aSecondSegmentControlPoints = aControlPoints;
   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;
   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 }
   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;
   248   Float t = 0;
   249   while (t < 1.0f) {
   250     Point cp21 = currentCP.mCP2 - currentCP.mCP3;
   251     Point cp31 = currentCP.mCP3 - currentCP.mCP1;
   253     Float s3 = (cp31.x * cp21.y - cp31.y * cp21.x) / hypotf(cp21.x, cp21.y);
   255     t = 2 * Float(sqrt(aTolerance / (3. * abs(s3))));
   257     if (t >= 1.0f) {
   258       aSink->LineTo(aControlPoints.mCP4);
   259       break;
   260     }
   262     Point prevCP2, prevCP3, nextCP1, nextCP2, nextCP3;
   263     SplitBezier(currentCP, nullptr, &currentCP, t);
   265     aSink->LineTo(currentCP.mCP1);
   266   }
   267 }
   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);
   276     Point cp21 = aControlPoints.mCP2 - aControlPoints.mCP1;
   277     Point cp41 = aControlPoints.mCP4 - aControlPoints.mCP1;
   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.
   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     }
   289     Float s3 = (cp41.x * cp21.y - cp41.y * cp21.x) / hypotf(cp21.x, cp21.y);
   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     }
   300     Float tf = CubicRoot(abs(aTolerance / s3));
   302     *aMin = aT - tf * (1 - aT);
   303     *aMax = aT + tf * (1 - aT);
   304 }
   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
   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;
   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;
   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;
   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);
   410       *aT1 = q / a;
   411       *aT2 = c / q;
   412       if (*aT1 > *aT2) {
   413         std::swap(*aT1, *aT2);
   414       }
   415       *aCount = 2;
   416     }
   417   }
   419   return;
   420 }
   422 void
   423 FlattenBezier(const BezierControlPoints &aControlPoints,
   424               PathSink *aSink, Float aTolerance)
   425 {
   426   Float t1;
   427   Float t2;
   428   uint32_t count;
   430   FindInflectionPoints(aControlPoints, &t1, &t2, &count);
   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   }
   438   Float t1min = t1, t1max = t1, t2min = t2, t2max = t2;
   440   BezierControlPoints remainingCP = aControlPoints;
   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;
   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);
   468     aSink->LineTo(nextCPs.mCP1);
   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   }
   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);
   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);
   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 }
   516 }
   517 }

mercurial