| Index: src/core/SkStroke.cpp
|
| diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp
|
| index 1e4ae79a5e0108725d2492a268bdeac122e053bb..a9cc6cd60ad9ec5d684927ee9c5bbecce96a0842 100644
|
| --- a/src/core/SkStroke.cpp
|
| +++ b/src/core/SkStroke.cpp
|
| @@ -9,6 +9,48 @@
|
| #include "SkGeometry.h"
|
| #include "SkPath.h"
|
|
|
| +#if QUAD_STROKE_APPROXIMATION
|
| +
|
| + enum {
|
| + kTangent_RecursiveLimit,
|
| + kCubic_RecursiveLimit,
|
| + kQuad_RecursiveLimit
|
| + };
|
| +
|
| + // quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure
|
| + // largest seen for normal cubics : 5, 26
|
| + // largest seen for normal quads : 11
|
| + static const int kRecursiveLimits[] = { 5*3, 26*3, 11*3 }; // 3x limits seen in practical tests
|
| +
|
| + SK_COMPILE_ASSERT(SK_ARRAY_COUNT(kRecursiveLimits) == kQuad_RecursiveLimit + 1,
|
| + recursive_limits_mismatch);
|
| +
|
| + #ifdef SK_DEBUG // enables tweaking these values at runtime from SampleApp
|
| + bool gDebugStrokerErrorSet = false;
|
| + SkScalar gDebugStrokerError;
|
| +
|
| + int gMaxRecursion[SK_ARRAY_COUNT(kRecursiveLimits)] = { 0 };
|
| + #endif
|
| + #ifndef DEBUG_QUAD_STROKER
|
| + #define DEBUG_QUAD_STROKER 0
|
| + #endif
|
| +
|
| + #if DEBUG_QUAD_STROKER
|
| + /* Enable to show the decisions made in subdividing the curve -- helpful when the resulting
|
| + stroke has more than the optimal number of quadratics and lines */
|
| + #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
|
| + SkDebugf("[%d] %s " format "\n", depth, __FUNCTION__, __VA_ARGS__), \
|
| + SkDebugf(" " #resultType " t=(%g,%g)\n", quadPts->fStartT, quadPts->fEndT), \
|
| + resultType
|
| + #define STROKER_DEBUG_PARAMS(...) , __VA_ARGS__
|
| + #else
|
| + #define STROKER_RESULT(resultType, depth, quadPts, format, ...) \
|
| + resultType
|
| + #define STROKER_DEBUG_PARAMS(...)
|
| + #endif
|
| +
|
| +#endif
|
| +
|
| #define kMaxQuadSubdivide 5
|
| #define kMaxCubicSubdivide 7
|
|
|
| @@ -16,6 +58,7 @@ static inline bool degenerate_vector(const SkVector& v) {
|
| return !SkPoint::CanNormalize(v.fX, v.fY);
|
| }
|
|
|
| +#if !QUAD_STROKE_APPROXIMATION
|
| static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) {
|
| /* root2/2 is a 45-degree angle
|
| make this constant bigger for more subdivisions (but not >= 1)
|
| @@ -43,6 +86,7 @@ static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) {
|
| SkScalar dot = SkPoint::DotProduct(norm0, norm1);
|
| return dot <= kTooPinchyNormalDotProd;
|
| }
|
| +#endif
|
|
|
| static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after,
|
| SkScalar radius,
|
| @@ -67,12 +111,60 @@ static bool set_normal_unitnormal(const SkVector& vec,
|
| }
|
|
|
| ///////////////////////////////////////////////////////////////////////////////
|
| +#if QUAD_STROKE_APPROXIMATION
|
| +
|
| +struct SkQuadConstruct { // The state of the quad stroke under construction.
|
| + SkPoint fQuad[3]; // the stroked quad parallel to the original curve
|
| + SkPoint fTangentStart; // a point tangent to fQuad[0]
|
| + SkPoint fTangentEnd; // a point tangent to fQuad[2]
|
| + SkScalar fStartT; // a segment of the original curve
|
| + SkScalar fMidT; // "
|
| + SkScalar fEndT; // "
|
| + bool fStartSet; // state to share common points across structs
|
| + bool fEndSet; // "
|
| +
|
| + // return false if start and end are too close to have a unique middle
|
| + bool init(SkScalar start, SkScalar end) {
|
| + fStartT = start;
|
| + fMidT = (start + end) * SK_ScalarHalf;
|
| + fEndT = end;
|
| + fStartSet = fEndSet = false;
|
| + return fStartT < fMidT && fMidT < fEndT;
|
| + }
|
| +
|
| + bool initWithStart(SkQuadConstruct* parent) {
|
| + if (!init(parent->fStartT, parent->fMidT)) {
|
| + return false;
|
| + }
|
| + fQuad[0] = parent->fQuad[0];
|
| + fTangentStart = parent->fTangentStart;
|
| + fStartSet = true;
|
| + return true;
|
| + }
|
| +
|
| + bool initWithEnd(SkQuadConstruct* parent) {
|
| + if (!init(parent->fMidT, parent->fEndT)) {
|
| + return false;
|
| + }
|
| + fQuad[2] = parent->fQuad[2];
|
| + fTangentEnd = parent->fTangentEnd;
|
| + fEndSet = true;
|
| + return true;
|
| + }
|
| +};
|
| +#endif
|
|
|
| class SkPathStroker {
|
| public:
|
| +#if QUAD_STROKE_APPROXIMATION
|
| + SkPathStroker(const SkPath& src,
|
| + SkScalar radius, SkScalar miterLimit, SkScalar error, SkPaint::Cap cap,
|
| + SkPaint::Join join);
|
| +#else
|
| SkPathStroker(const SkPath& src,
|
| SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap,
|
| SkPaint::Join join);
|
| +#endif
|
|
|
| void moveTo(const SkPoint&);
|
| void lineTo(const SkPoint&);
|
| @@ -87,6 +179,9 @@ public:
|
| }
|
|
|
| private:
|
| +#if QUAD_STROKE_APPROXIMATION
|
| + SkScalar fError;
|
| +#endif
|
| SkScalar fRadius;
|
| SkScalar fInvMiterLimit;
|
|
|
| @@ -102,6 +197,67 @@ private:
|
| SkPath fInner, fOuter; // outer is our working answer, inner is temp
|
| SkPath fExtra; // added as extra complete contours
|
|
|
| +#if QUAD_STROKE_APPROXIMATION
|
| + enum StrokeType {
|
| + kOuter_StrokeType = 1, // use sign-opposite values later to flip perpendicular axis
|
| + kInner_StrokeType = -1
|
| + } fStrokeType;
|
| +
|
| + enum ResultType {
|
| + kSplit_ResultType, // the caller should split the quad stroke in two
|
| + kDegenerate_ResultType, // the caller should add a line
|
| + kQuad_ResultType, // the caller should (continue to try to) add a quad stroke
|
| + kNormalError_ResultType, // the cubic's normal couldn't be computed -- abort
|
| + };
|
| +
|
| + enum ReductionType {
|
| + kPoint_ReductionType, // all curve points are practically identical
|
| + kLine_ReductionType, // the control point is on the line between the ends
|
| + kQuad_ReductionType, // the control point is outside the line between the ends
|
| + kDegenerate_ReductionType, // the control point is on the line but outside the ends
|
| + kDegenerate2_ReductionType, // two control points are on the line but outside ends (cubic)
|
| + kDegenerate3_ReductionType, // three areas of max curvature found (for cubic)
|
| + };
|
| +
|
| + enum IntersectRayType {
|
| + kCtrlPt_RayType,
|
| + kResultType_RayType,
|
| + };
|
| +
|
| + int fRecursionDepth; // track stack depth to abort if numerics run amok
|
| + bool fFoundTangents; // do less work until tangents meet (cubic)
|
| +
|
| + void addDegenerateLine(const SkQuadConstruct* );
|
| + ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3],
|
| + const SkPoint** tanPtPtr);
|
| + ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction);
|
| + ResultType compareQuadCubic(const SkPoint cubic[4], SkQuadConstruct* );
|
| + ResultType compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* );
|
| + bool cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* ) const;
|
| + bool cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
|
| + SkPoint* tangent) const;
|
| + bool cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* );
|
| + bool cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* , SkPoint* mid) const;
|
| + bool cubicStroke(const SkPoint cubic[4], SkQuadConstruct* );
|
| + void init(StrokeType strokeType, SkQuadConstruct* , SkScalar tStart, SkScalar tEnd);
|
| + ResultType intersectRay(SkQuadConstruct* , IntersectRayType STROKER_DEBUG_PARAMS(int) ) const;
|
| + bool ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const;
|
| + void quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
|
| + SkPoint* tangent) const;
|
| + bool quadStroke(const SkPoint quad[3], SkQuadConstruct* );
|
| + void setCubicEndNormal(const SkPoint cubic[4],
|
| + const SkVector& normalAB, const SkVector& unitNormalAB,
|
| + SkVector* normalCD, SkVector* unitNormalCD);
|
| + void setQuadEndNormal(const SkPoint quad[3],
|
| + const SkVector& normalAB, const SkVector& unitNormalAB,
|
| + SkVector* normalBC, SkVector* unitNormalBC);
|
| + void setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt, SkPoint* tangent) const;
|
| + static bool SlightAngle(SkQuadConstruct* );
|
| + ResultType strokeCloseEnough(const SkPoint stroke[3], const SkPoint ray[2],
|
| + SkQuadConstruct* STROKER_DEBUG_PARAMS(int depth) ) const;
|
| + ResultType tangentsMeet(const SkPoint cubic[4], SkQuadConstruct* );
|
| +#endif
|
| +
|
| void finishContour(bool close, bool isLine);
|
| void preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
|
| bool isLine);
|
| @@ -109,6 +265,7 @@ private:
|
| const SkVector& unitNormal);
|
|
|
| void line_to(const SkPoint& currPt, const SkVector& normal);
|
| +#if !QUAD_STROKE_APPROXIMATION
|
| void quad_to(const SkPoint pts[3],
|
| const SkVector& normalAB, const SkVector& unitNormalAB,
|
| SkVector* normalBC, SkVector* unitNormalBC,
|
| @@ -117,6 +274,7 @@ private:
|
| const SkVector& normalAB, const SkVector& unitNormalAB,
|
| SkVector* normalCD, SkVector* unitNormalCD,
|
| int subDivide);
|
| +#endif
|
| };
|
|
|
| ///////////////////////////////////////////////////////////////////////////////
|
| @@ -187,9 +345,15 @@ void SkPathStroker::finishContour(bool close, bool currIsLine) {
|
|
|
| ///////////////////////////////////////////////////////////////////////////////
|
|
|
| +#if QUAD_STROKE_APPROXIMATION
|
| +SkPathStroker::SkPathStroker(const SkPath& src,
|
| + SkScalar radius, SkScalar miterLimit, SkScalar error,
|
| + SkPaint::Cap cap, SkPaint::Join join)
|
| +#else
|
| SkPathStroker::SkPathStroker(const SkPath& src,
|
| SkScalar radius, SkScalar miterLimit,
|
| SkPaint::Cap cap, SkPaint::Join join)
|
| +#endif
|
| : fRadius(radius) {
|
|
|
| /* This is only used when join is miter_join, but we initialize it here
|
| @@ -217,6 +381,17 @@ SkPathStroker::SkPathStroker(const SkPath& src,
|
| // 1x for inner == 'wag' (worst contour length would be better guess)
|
| fOuter.incReserve(src.countPoints() * 3);
|
| fInner.incReserve(src.countPoints());
|
| +#if QUAD_STROKE_APPROXIMATION
|
| +#ifdef SK_DEBUG
|
| + if (!gDebugStrokerErrorSet) {
|
| + gDebugStrokerError = error;
|
| + }
|
| + fError = gDebugStrokerError;
|
| +#else
|
| + fError = error;
|
| +#endif
|
| + fRecursionDepth = 0;
|
| +#endif
|
| }
|
|
|
| void SkPathStroker::moveTo(const SkPoint& pt) {
|
| @@ -243,6 +418,7 @@ void SkPathStroker::lineTo(const SkPoint& currPt) {
|
| this->postJoinTo(currPt, normal, unitNormal);
|
| }
|
|
|
| +#if !QUAD_STROKE_APPROXIMATION
|
| void SkPathStroker::quad_to(const SkPoint pts[3],
|
| const SkVector& normalAB, const SkVector& unitNormalAB,
|
| SkVector* normalBC, SkVector* unitNormalBC,
|
| @@ -278,6 +454,203 @@ void SkPathStroker::quad_to(const SkPoint pts[3],
|
| pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY);
|
| }
|
| }
|
| +#endif
|
| +
|
| +#if QUAD_STROKE_APPROXIMATION
|
| +void SkPathStroker::setQuadEndNormal(const SkPoint quad[3], const SkVector& normalAB,
|
| + const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) {
|
| + if (!set_normal_unitnormal(quad[1], quad[2], fRadius, normalBC, unitNormalBC)) {
|
| + *normalBC = normalAB;
|
| + *unitNormalBC = unitNormalAB;
|
| + }
|
| +}
|
| +
|
| +void SkPathStroker::setCubicEndNormal(const SkPoint cubic[4], const SkVector& normalAB,
|
| + const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD) {
|
| + SkVector ab = cubic[1] - cubic[0];
|
| + SkVector cd = cubic[3] - cubic[2];
|
| +
|
| + bool degenerateAB = degenerate_vector(ab);
|
| + bool degenerateCD = degenerate_vector(cd);
|
| +
|
| + if (degenerateAB && degenerateCD) {
|
| + goto DEGENERATE_NORMAL;
|
| + }
|
| +
|
| + if (degenerateAB) {
|
| + ab = cubic[2] - cubic[0];
|
| + degenerateAB = degenerate_vector(ab);
|
| + }
|
| + if (degenerateCD) {
|
| + cd = cubic[3] - cubic[1];
|
| + degenerateCD = degenerate_vector(cd);
|
| + }
|
| + if (degenerateAB || degenerateCD) {
|
| +DEGENERATE_NORMAL:
|
| + *normalCD = normalAB;
|
| + *unitNormalCD = unitNormalAB;
|
| + return;
|
| + }
|
| + SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
|
| +}
|
| +
|
| +void SkPathStroker::init(StrokeType strokeType, SkQuadConstruct* quadPts, SkScalar tStart,
|
| + SkScalar tEnd) {
|
| + fStrokeType = strokeType;
|
| + fFoundTangents = false;
|
| + quadPts->init(tStart, tEnd);
|
| +}
|
| +
|
| +// returns the distance squared from the point to the line
|
| +static SkScalar pt_to_line(const SkPoint& pt, const SkPoint& lineStart, const SkPoint& lineEnd) {
|
| + SkVector dxy = lineEnd - lineStart;
|
| + if (degenerate_vector(dxy)) {
|
| + return pt.distanceToSqd(lineStart);
|
| + }
|
| + SkVector ab0 = pt - lineStart;
|
| + SkScalar numer = dxy.dot(ab0);
|
| + SkScalar denom = dxy.dot(dxy);
|
| + SkScalar t = numer / denom;
|
| + SkPoint hit;
|
| + hit.fX = lineStart.fX * (1 - t) + lineEnd.fX * t;
|
| + hit.fY = lineStart.fY * (1 - t) + lineEnd.fY * t;
|
| + return hit.distanceToSqd(pt);
|
| +}
|
| +
|
| +/* Given a cubic, determine if all four points are in a line.
|
| + Return true if the inner points is close to a line connecting the outermost points.
|
| +
|
| + Find the outermost point by looking for the largest difference in X or Y.
|
| + Given the indices of the outermost points, and that outer_1 is greater than outer_2,
|
| + this table shows the index of the smaller of the remaining points:
|
| +
|
| + outer_2
|
| + 0 1 2 3
|
| + outer_1 ----------------
|
| + 0 | - 2 1 1
|
| + 1 | - - 0 0
|
| + 2 | - - - 0
|
| + 3 | - - - -
|
| +
|
| + If outer_1 == 0 and outer_2 == 1, the smaller of the remaining indices (2 and 3) is 2.
|
| +
|
| + This table can be collapsed to: (1 + (2 >> outer_2)) >> outer_1
|
| +
|
| + Given three indices (outer_1 outer_2 mid_1) from 0..3, the remaining index is:
|
| +
|
| + mid_2 == (outer_1 ^ outer_2 ^ mid_1)
|
| + */
|
| +static bool cubic_in_line(const SkPoint cubic[4]) {
|
| + SkScalar ptMax = -1;
|
| + int outer1, outer2;
|
| + for (int index = 0; index < 3; ++index) {
|
| + for (int inner = index + 1; inner < 4; ++inner) {
|
| + SkVector testDiff = cubic[inner] - cubic[index];
|
| + SkScalar testMax = SkTMax(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY));
|
| + if (ptMax < testMax) {
|
| + outer1 = index;
|
| + outer2 = inner;
|
| + ptMax = testMax;
|
| + }
|
| + }
|
| + }
|
| + SkASSERT(outer1 >= 0 && outer1 <= 2);
|
| + SkASSERT(outer2 >= 1 && outer2 <= 3);
|
| + SkASSERT(outer1 < outer2);
|
| + int mid1 = (1 + (2 >> outer2)) >> outer1;
|
| + SkASSERT(mid1 >= 0 && mid1 <= 2);
|
| + SkASSERT(outer1 != mid1 && outer2 != mid1);
|
| + int mid2 = outer1 ^ outer2 ^ mid1;
|
| + SkASSERT(mid2 >= 1 && mid2 <= 3);
|
| + SkASSERT(mid2 != outer1 && mid2 != outer2 && mid2 != mid1);
|
| + SkASSERT(((1 << outer1) | (1 << outer2) | (1 << mid1) | (1 << mid2)) == 0x0f);
|
| + SkScalar lineSlop = ptMax * ptMax * 0.00001f; // this multiplier is pulled out of the air
|
| + return pt_to_line(cubic[mid1], cubic[outer1], cubic[outer2]) <= lineSlop
|
| + && pt_to_line(cubic[mid2], cubic[outer1], cubic[outer2]) <= lineSlop;
|
| +}
|
| +
|
| +/* Given quad, see if all there points are in a line.
|
| + Return true if the inside point is close to a line connecting the outermost points.
|
| +
|
| + Find the outermost point by looking for the largest difference in X or Y.
|
| + Since the XOR of the indices is 3 (0 ^ 1 ^ 2)
|
| + the missing index equals: outer_1 ^ outer_2 ^ 3
|
| + */
|
| +static bool quad_in_line(const SkPoint quad[3]) {
|
| + SkScalar ptMax = -1;
|
| + int outer1, outer2;
|
| + for (int index = 0; index < 2; ++index) {
|
| + for (int inner = index + 1; inner < 3; ++inner) {
|
| + SkVector testDiff = quad[inner] - quad[index];
|
| + SkScalar testMax = SkTMax(SkScalarAbs(testDiff.fX), SkScalarAbs(testDiff.fY));
|
| + if (ptMax < testMax) {
|
| + outer1 = index;
|
| + outer2 = inner;
|
| + ptMax = testMax;
|
| + }
|
| + }
|
| + }
|
| + SkASSERT(outer1 >= 0 && outer1 <= 1);
|
| + SkASSERT(outer2 >= 1 && outer2 <= 2);
|
| + SkASSERT(outer1 < outer2);
|
| + int mid = outer1 ^ outer2 ^ 3;
|
| + SkScalar lineSlop = ptMax * ptMax * 0.00001f; // this multiplier is pulled out of the air
|
| + return pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= lineSlop;
|
| +}
|
| +
|
| +SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic[4],
|
| + SkPoint reduction[3], const SkPoint** tangentPtPtr) {
|
| + bool degenerateAB = degenerate_vector(cubic[1] - cubic[0]);
|
| + bool degenerateBC = degenerate_vector(cubic[2] - cubic[1]);
|
| + bool degenerateCD = degenerate_vector(cubic[3] - cubic[2]);
|
| + if (degenerateAB & degenerateBC & degenerateCD) {
|
| + return kPoint_ReductionType;
|
| + }
|
| + if (degenerateAB + degenerateBC + degenerateCD == 2) {
|
| + return kLine_ReductionType;
|
| + }
|
| + if (!cubic_in_line(cubic)) {
|
| + *tangentPtPtr = degenerateAB ? &cubic[2] : &cubic[1];
|
| + return kQuad_ReductionType;
|
| + }
|
| + SkScalar tValues[3];
|
| + int count = SkFindCubicMaxCurvature(cubic, tValues);
|
| + if (count == 0) {
|
| + return kLine_ReductionType;
|
| + }
|
| + for (int index = 0; index < count; ++index) {
|
| + SkScalar t = tValues[index];
|
| + SkEvalCubicAt(cubic, t, &reduction[index], NULL, NULL);
|
| + }
|
| + SK_COMPILE_ASSERT(kQuad_ReductionType + 1 == kDegenerate_ReductionType, enum_out_of_whack);
|
| + SK_COMPILE_ASSERT(kQuad_ReductionType + 2 == kDegenerate2_ReductionType, enum_out_of_whack);
|
| + SK_COMPILE_ASSERT(kQuad_ReductionType + 3 == kDegenerate3_ReductionType, enum_out_of_whack);
|
| +
|
| + return (ReductionType) (kQuad_ReductionType + count);
|
| +}
|
| +
|
| +SkPathStroker::ReductionType SkPathStroker::CheckQuadLinear(const SkPoint quad[3],
|
| + SkPoint* reduction) {
|
| + bool degenerateAB = degenerate_vector(quad[1] - quad[0]);
|
| + bool degenerateBC = degenerate_vector(quad[2] - quad[1]);
|
| + if (degenerateAB & degenerateBC) {
|
| + return kPoint_ReductionType;
|
| + }
|
| + if (degenerateAB | degenerateBC) {
|
| + return kLine_ReductionType;
|
| + }
|
| + if (!quad_in_line(quad)) {
|
| + return kQuad_ReductionType;
|
| + }
|
| + SkScalar t = SkFindQuadMaxCurvature(quad);
|
| + if (0 == t) {
|
| + return kLine_ReductionType;
|
| + }
|
| + SkEvalQuadAt(quad, t, reduction, NULL);
|
| + return kDegenerate_ReductionType;
|
| +}
|
| +
|
| +#else
|
|
|
| void SkPathStroker::cubic_to(const SkPoint pts[4],
|
| const SkVector& normalAB, const SkVector& unitNormalAB,
|
| @@ -362,8 +735,42 @@ DRAW_LINE:
|
| pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
|
| }
|
| }
|
| +#endif
|
|
|
| void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
|
| +#if QUAD_STROKE_APPROXIMATION
|
| + const SkPoint quad[3] = { fPrevPt, pt1, pt2 };
|
| + SkPoint reduction;
|
| + ReductionType reductionType = CheckQuadLinear(quad, &reduction);
|
| + if (kPoint_ReductionType == reductionType) {
|
| + return;
|
| + }
|
| + if (kLine_ReductionType == reductionType) {
|
| + this->lineTo(pt2);
|
| + return;
|
| + }
|
| + if (kDegenerate_ReductionType == reductionType) {
|
| + this->lineTo(reduction);
|
| + SkStrokerPriv::JoinProc saveJoiner = fJoiner;
|
| + fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
|
| + this->lineTo(pt2);
|
| + fJoiner = saveJoiner;
|
| + return;
|
| + }
|
| + SkASSERT(kQuad_ReductionType == reductionType);
|
| + SkVector normalAB, unitAB, normalBC, unitBC;
|
| + this->preJoinTo(pt1, &normalAB, &unitAB, false);
|
| + SkQuadConstruct quadPts;
|
| + this->init(kOuter_StrokeType, &quadPts, 0, 1);
|
| + if (!this->quadStroke(quad, &quadPts)) {
|
| + return;
|
| + }
|
| + this->init(kInner_StrokeType, &quadPts, 0, 1);
|
| + if (!this->quadStroke(quad, &quadPts)) {
|
| + return;
|
| + }
|
| + this->setQuadEndNormal(quad, normalAB, unitAB, &normalBC, &unitBC);
|
| +#else
|
| bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
|
| bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
|
|
|
| @@ -414,12 +821,450 @@ void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
|
| kMaxQuadSubdivide);
|
| }
|
| }
|
| +#endif
|
|
|
| this->postJoinTo(pt2, normalBC, unitBC);
|
| }
|
|
|
| +#if QUAD_STROKE_APPROXIMATION
|
| +// Given a point on the curve and its derivative, scale the derivative by the radius, and
|
| +// compute the perpendicular point and its tangent.
|
| +void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt,
|
| + SkPoint* tangent) const {
|
| + if (!dxy->setLength(fRadius)) { // consider moving double logic into SkPoint::setLength
|
| + double xx = dxy->fX;
|
| + double yy = dxy->fY;
|
| + double dscale = fRadius / sqrt(xx * xx + yy * yy);
|
| + dxy->fX = SkDoubleToScalar(xx * dscale);
|
| + dxy->fY = SkDoubleToScalar(yy * dscale);
|
| + }
|
| + SkScalar axisFlip = SkIntToScalar(fStrokeType); // go opposite ways for outer, inner
|
| + onPt->fX = tPt.fX + axisFlip * dxy->fY;
|
| + onPt->fY = tPt.fY - axisFlip * dxy->fX;
|
| + if (tangent) {
|
| + tangent->fX = onPt->fX + dxy->fX;
|
| + tangent->fY = onPt->fY + dxy->fY;
|
| + }
|
| +}
|
| +
|
| +// Given a cubic and t, return the point on curve, its perpendicular, and the perpendicular tangent.
|
| +// Returns false if the perpendicular could not be computed (because the derivative collapsed to 0)
|
| +bool SkPathStroker::cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
|
| + SkPoint* tangent) const {
|
| + SkVector dxy;
|
| + SkEvalCubicAt(cubic, t, tPt, &dxy, NULL);
|
| + if (dxy.fX == 0 && dxy.fY == 0) {
|
| + if (SkScalarNearlyZero(t)) {
|
| + dxy = cubic[2] - cubic[0];
|
| + } else if (SkScalarNearlyZero(1 - t)) {
|
| + dxy = cubic[3] - cubic[1];
|
| + } else {
|
| + return false;
|
| + }
|
| + if (dxy.fX == 0 && dxy.fY == 0) {
|
| + dxy = cubic[3] - cubic[0];
|
| + }
|
| + }
|
| + setRayPts(*tPt, &dxy, onPt, tangent);
|
| + return true;
|
| +}
|
| +
|
| +// Given a cubic and a t range, find the start and end if they haven't been found already.
|
| +bool SkPathStroker::cubicQuadEnds(const SkPoint cubic[4], SkQuadConstruct* quadPts) {
|
| + if (!quadPts->fStartSet) {
|
| + SkPoint cubicStartPt;
|
| + if (!this->cubicPerpRay(cubic, quadPts->fStartT, &cubicStartPt, &quadPts->fQuad[0],
|
| + &quadPts->fTangentStart)) {
|
| + return false;
|
| + }
|
| + quadPts->fStartSet = true;
|
| + }
|
| + if (!quadPts->fEndSet) {
|
| + SkPoint cubicEndPt;
|
| + if (!this->cubicPerpRay(cubic, quadPts->fEndT, &cubicEndPt, &quadPts->fQuad[2],
|
| + &quadPts->fTangentEnd)) {
|
| + return false;
|
| + }
|
| + quadPts->fEndSet = true;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +bool SkPathStroker::cubicQuadMid(const SkPoint cubic[4], const SkQuadConstruct* quadPts,
|
| + SkPoint* mid) const {
|
| + SkPoint cubicMidPt;
|
| + return this->cubicPerpRay(cubic, quadPts->fMidT, &cubicMidPt, mid, NULL);
|
| +}
|
| +
|
| +// Given a quad and t, return the point on curve, its perpendicular, and the perpendicular tangent.
|
| +void SkPathStroker::quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
|
| + SkPoint* tangent) const {
|
| + SkVector dxy;
|
| + SkEvalQuadAt(quad, t, tPt, &dxy);
|
| + if (dxy.fX == 0 && dxy.fY == 0) {
|
| + dxy = quad[2] - quad[0];
|
| + }
|
| + setRayPts(*tPt, &dxy, onPt, tangent);
|
| +}
|
| +
|
| +// Find the intersection of the stroke tangents to construct a stroke quad.
|
| +// Return whether the stroke is a degenerate (a line), a quad, or must be split.
|
| +// Optionally compute the quad's control point.
|
| +SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts,
|
| + IntersectRayType intersectRayType STROKER_DEBUG_PARAMS(int depth)) const {
|
| + const SkPoint& start = quadPts->fQuad[0];
|
| + const SkPoint& end = quadPts->fQuad[2];
|
| + SkVector aLen = quadPts->fTangentStart - start;
|
| + SkVector bLen = quadPts->fTangentEnd - end;
|
| + SkScalar denom = aLen.cross(bLen);
|
| + SkVector ab0 = start - end;
|
| + SkScalar numerA = bLen.cross(ab0);
|
| + SkScalar numerB = aLen.cross(ab0);
|
| + if (!SkScalarNearlyZero(denom)) {
|
| + // if the perpendicular distances from the quad points to the opposite tangent line
|
| + // are small, a straight line is good enough
|
| + SkScalar dist1 = pt_to_line(start, end, quadPts->fTangentEnd);
|
| + SkScalar dist2 = pt_to_line(end, start, quadPts->fTangentStart);
|
| + if (SkTMax(dist1, dist2) <= fError * fError) {
|
| + return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
|
| + "SkTMax(dist1=%g, dist2=%g) <= fError * fError", dist1, dist2);
|
| + }
|
| + if ((numerA >= 0) != (numerB >= 0)) {
|
| + if (kCtrlPt_RayType == intersectRayType) {
|
| + numerA /= denom;
|
| + SkPoint* ctrlPt = &quadPts->fQuad[1];
|
| + ctrlPt->fX = start.fX * (1 - numerA) + quadPts->fTangentStart.fX * numerA;
|
| + ctrlPt->fY = start.fY * (1 - numerA) + quadPts->fTangentStart.fY * numerA;
|
| + }
|
| + return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
|
| + "(numerA=%g >= 0) != (numerB=%g >= 0)", numerA, numerB);
|
| + }
|
| + return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
|
| + "(numerA=%g >= 0) == (numerB=%g >= 0)", numerA, numerB);
|
| + } else { // if the lines are parallel, straight line is good enough
|
| + return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
|
| + "SkScalarNearlyZero(denom=%g)", denom);
|
| + }
|
| +}
|
| +
|
| +// Given a cubic and a t-range, determine if the stroke can be described by a quadratic.
|
| +SkPathStroker::ResultType SkPathStroker::tangentsMeet(const SkPoint cubic[4],
|
| + SkQuadConstruct* quadPts) {
|
| + if (!this->cubicQuadEnds(cubic, quadPts)) {
|
| + return kNormalError_ResultType;
|
| + }
|
| + return intersectRay(quadPts, kResultType_RayType STROKER_DEBUG_PARAMS(fRecursionDepth));
|
| +}
|
| +
|
| +// Intersect the line with the quad and return the t values on the quad where the line crosses.
|
| +static int intersect_quad_ray(const SkPoint line[2], const SkPoint quad[3], SkScalar roots[2]) {
|
| + SkVector vec = line[1] - line[0];
|
| + SkScalar r[3];
|
| + for (int n = 0; n < 3; ++n) {
|
| + r[n] = (quad[n].fY - line[0].fY) * vec.fX - (quad[n].fX - line[0].fX) * vec.fY;
|
| + }
|
| + SkScalar A = r[2];
|
| + SkScalar B = r[1];
|
| + SkScalar C = r[0];
|
| + A += C - 2 * B; // A = a - 2*b + c
|
| + B -= C; // B = -(b - c)
|
| + return SkFindUnitQuadRoots(A, 2 * B, C, roots);
|
| +}
|
| +
|
| +// Return true if the point is close to the bounds of the quad. This is used as a quick reject.
|
| +bool SkPathStroker::ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const {
|
| + SkScalar xMin = SkTMin(SkTMin(quad[0].fX, quad[1].fX), quad[2].fX);
|
| + if (pt.fX + fError < xMin) {
|
| + return false;
|
| + }
|
| + SkScalar xMax = SkTMax(SkTMax(quad[0].fX, quad[1].fX), quad[2].fX);
|
| + if (pt.fX - fError > xMax) {
|
| + return false;
|
| + }
|
| + SkScalar yMin = SkTMin(SkTMin(quad[0].fY, quad[1].fY), quad[2].fY);
|
| + if (pt.fY + fError < yMin) {
|
| + return false;
|
| + }
|
| + SkScalar yMax = SkTMax(SkTMax(quad[0].fY, quad[1].fY), quad[2].fY);
|
| + if (pt.fY - fError > yMax) {
|
| + return false;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +static bool points_within_dist(const SkPoint& nearPt, const SkPoint& farPt, SkScalar limit) {
|
| + return nearPt.distanceToSqd(farPt) <= limit * limit;
|
| +}
|
| +
|
| +static bool sharp_angle(const SkPoint quad[3]) {
|
| + SkVector smaller = quad[1] - quad[0];
|
| + SkVector larger = quad[1] - quad[2];
|
| + SkScalar smallerLen = smaller.lengthSqd();
|
| + SkScalar largerLen = larger.lengthSqd();
|
| + if (smallerLen > largerLen) {
|
| + SkTSwap(smaller, larger);
|
| + largerLen = smallerLen;
|
| + }
|
| + if (!smaller.setLength(largerLen)) {
|
| + return false;
|
| + }
|
| + SkScalar dot = smaller.dot(larger);
|
| + return dot > 0;
|
| +}
|
| +
|
| +SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[3],
|
| + const SkPoint ray[2], SkQuadConstruct* quadPts STROKER_DEBUG_PARAMS(int depth)) const {
|
| + SkPoint strokeMid;
|
| + SkEvalQuadAt(stroke, SK_ScalarHalf, &strokeMid);
|
| + // measure the distance from the curve to the quad-stroke midpoint, compare to radius
|
| + if (points_within_dist(ray[0], strokeMid, fError)) { // if the difference is small
|
| + if (sharp_angle(quadPts->fQuad)) {
|
| + return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
|
| + "sharp_angle (1) =%g,%g, %g,%g, %g,%g",
|
| + quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
|
| + quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
|
| + quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
|
| + }
|
| + return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
|
| + "points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fError)",
|
| + ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY);
|
| + }
|
| + // measure the distance to quad's bounds (quick reject)
|
| + // an alternative : look for point in triangle
|
| + if (!ptInQuadBounds(stroke, ray[0])) { // if far, subdivide
|
| + return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
|
| + "!pt_in_quad_bounds(stroke=(%g,%g %g,%g %g,%g), ray[0]=%g,%g)",
|
| + stroke[0].fX, stroke[0].fY, stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY,
|
| + ray[0].fX, ray[0].fY);
|
| + }
|
| + // measure the curve ray distance to the quad-stroke
|
| + SkScalar roots[2];
|
| + int rootCount = intersect_quad_ray(ray, stroke, roots);
|
| + if (rootCount != 1) {
|
| + return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
|
| + "rootCount=%d != 1", rootCount);
|
| + }
|
| + SkPoint quadPt;
|
| + SkEvalQuadAt(stroke, roots[0], &quadPt);
|
| + SkScalar error = fError * (SK_Scalar1 - SkScalarAbs(roots[0] - 0.5f) * 2);
|
| + if (points_within_dist(ray[0], quadPt, error)) { // if the difference is small, we're done
|
| + if (sharp_angle(quadPts->fQuad)) {
|
| + return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
|
| + "sharp_angle (2) =%g,%g, %g,%g, %g,%g",
|
| + quadPts->fQuad[0].fX, quadPts->fQuad[0].fY,
|
| + quadPts->fQuad[1].fX, quadPts->fQuad[1].fY,
|
| + quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
|
| + }
|
| + return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
|
| + "points_within_dist(ray[0]=%g,%g, quadPt=%g,%g, error=%g)",
|
| + ray[0].fX, ray[0].fY, quadPt.fX, quadPt.fY, error);
|
| + }
|
| + // otherwise, subdivide
|
| + return STROKER_RESULT(kSplit_ResultType, depth, quadPts, "%s", "fall through");
|
| +}
|
| +
|
| +SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4],
|
| + SkQuadConstruct* quadPts) {
|
| + // get the quadratic approximation of the stroke
|
| + if (!this->cubicQuadEnds(cubic, quadPts)) {
|
| + return kNormalError_ResultType;
|
| + }
|
| + ResultType resultType = intersectRay(quadPts, kCtrlPt_RayType
|
| + STROKER_DEBUG_PARAMS(fRecursionDepth) );
|
| + if (resultType != kQuad_ResultType) {
|
| + return resultType;
|
| + }
|
| + // project a ray from the curve to the stroke
|
| + SkPoint ray[2]; // point near midpoint on quad, midpoint on cubic
|
| + if (!this->cubicPerpRay(cubic, quadPts->fMidT, &ray[1], &ray[0], NULL)) {
|
| + return kNormalError_ResultType;
|
| + }
|
| + return strokeCloseEnough(quadPts->fQuad, ray, quadPts STROKER_DEBUG_PARAMS(fRecursionDepth));
|
| +}
|
| +
|
| +// if false is returned, caller splits quadratic approximation
|
| +SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3],
|
| + SkQuadConstruct* quadPts) {
|
| + // get the quadratic approximation of the stroke
|
| + if (!quadPts->fStartSet) {
|
| + SkPoint quadStartPt;
|
| + this->quadPerpRay(quad, quadPts->fStartT, &quadStartPt, &quadPts->fQuad[0],
|
| + &quadPts->fTangentStart);
|
| + quadPts->fStartSet = true;
|
| + }
|
| + if (!quadPts->fEndSet) {
|
| + SkPoint quadEndPt;
|
| + this->quadPerpRay(quad, quadPts->fEndT, &quadEndPt, &quadPts->fQuad[2],
|
| + &quadPts->fTangentEnd);
|
| + quadPts->fEndSet = true;
|
| + }
|
| + ResultType resultType = intersectRay(quadPts, kCtrlPt_RayType
|
| + STROKER_DEBUG_PARAMS(fRecursionDepth));
|
| + if (resultType != kQuad_ResultType) {
|
| + return resultType;
|
| + }
|
| + // project a ray from the curve to the stroke
|
| + SkPoint ray[2];
|
| + this->quadPerpRay(quad, quadPts->fMidT, &ray[1], &ray[0], NULL);
|
| + return strokeCloseEnough(quadPts->fQuad, ray, quadPts STROKER_DEBUG_PARAMS(fRecursionDepth));
|
| +}
|
| +
|
| +void SkPathStroker::addDegenerateLine(const SkQuadConstruct* quadPts) {
|
| + const SkPoint* quad = quadPts->fQuad;
|
| + SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
|
| + path->lineTo(quad[2].fX, quad[2].fY);
|
| +}
|
| +
|
| +bool SkPathStroker::cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* quadPts) const {
|
| + SkPoint strokeMid;
|
| + if (!cubicQuadMid(cubic, quadPts, &strokeMid)) {
|
| + return false;
|
| + }
|
| + SkScalar dist = pt_to_line(strokeMid, quadPts->fQuad[0], quadPts->fQuad[2]);
|
| + return dist < fError * fError;
|
| +}
|
| +
|
| +bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts) {
|
| + if (!fFoundTangents) {
|
| + ResultType resultType = this->tangentsMeet(cubic, quadPts);
|
| + if (kQuad_ResultType != resultType) {
|
| + if (kNormalError_ResultType == resultType) {
|
| + return false;
|
| + }
|
| + if ((kDegenerate_ResultType == resultType
|
| + || points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2], fError))
|
| + && cubicMidOnLine(cubic, quadPts)) {
|
| + addDegenerateLine(quadPts);
|
| + return true;
|
| + }
|
| + } else {
|
| + fFoundTangents = true;
|
| + }
|
| + }
|
| + if (fFoundTangents) {
|
| + ResultType resultType = this->compareQuadCubic(cubic, quadPts);
|
| + if (kQuad_ResultType == resultType) {
|
| + SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
|
| + const SkPoint* stroke = quadPts->fQuad;
|
| + path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY);
|
| + return true;
|
| + }
|
| + if (kDegenerate_ResultType == resultType) {
|
| + addDegenerateLine(quadPts);
|
| + return true;
|
| + }
|
| + if (kNormalError_ResultType == resultType) {
|
| + return false;
|
| + }
|
| + }
|
| + if (!SkScalarIsFinite(quadPts->fQuad[2].fX) || !SkScalarIsFinite(quadPts->fQuad[2].fY)) {
|
| + return false; // just abort if projected quad isn't representable
|
| + }
|
| + SkDEBUGCODE(gMaxRecursion[fFoundTangents] = SkTMax(gMaxRecursion[fFoundTangents],
|
| + fRecursionDepth + 1));
|
| + if (++fRecursionDepth > kRecursiveLimits[fFoundTangents]) {
|
| + return false; // just abort if projected quad isn't representable
|
| + }
|
| + SkQuadConstruct half;
|
| + if (!half.initWithStart(quadPts)) {
|
| + addDegenerateLine(quadPts);
|
| + return true;
|
| + }
|
| + if (!this->cubicStroke(cubic, &half)) {
|
| + return false;
|
| + }
|
| + if (!half.initWithEnd(quadPts)) {
|
| + addDegenerateLine(quadPts);
|
| + return true;
|
| + }
|
| + if (!this->cubicStroke(cubic, &half)) {
|
| + return false;
|
| + }
|
| + --fRecursionDepth;
|
| + return true;
|
| +}
|
| +
|
| +bool SkPathStroker::quadStroke(const SkPoint quad[3], SkQuadConstruct* quadPts) {
|
| + ResultType resultType = this->compareQuadQuad(quad, quadPts);
|
| + if (kQuad_ResultType == resultType) {
|
| + const SkPoint* stroke = quadPts->fQuad;
|
| + SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
|
| + path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY);
|
| + return true;
|
| + }
|
| + if (kDegenerate_ResultType == resultType) {
|
| + addDegenerateLine(quadPts);
|
| + return true;
|
| + }
|
| + SkDEBUGCODE(gMaxRecursion[kQuad_RecursiveLimit] = SkTMax(gMaxRecursion[kQuad_RecursiveLimit],
|
| + fRecursionDepth + 1));
|
| + if (++fRecursionDepth > kRecursiveLimits[kQuad_RecursiveLimit]) {
|
| + return false; // just abort if projected quad isn't representable
|
| + }
|
| + SkQuadConstruct half;
|
| + (void) half.initWithStart(quadPts);
|
| + if (!this->quadStroke(quad, &half)) {
|
| + return false;
|
| + }
|
| + (void) half.initWithEnd(quadPts);
|
| + if (!this->quadStroke(quad, &half)) {
|
| + return false;
|
| + }
|
| + --fRecursionDepth;
|
| + return true;
|
| +}
|
| +
|
| +#endif
|
| +
|
| void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
|
| const SkPoint& pt3) {
|
| +#if QUAD_STROKE_APPROXIMATION
|
| + const SkPoint cubic[4] = { fPrevPt, pt1, pt2, pt3 };
|
| + SkPoint reduction[3];
|
| + const SkPoint* tangentPt;
|
| + ReductionType reductionType = CheckCubicLinear(cubic, reduction, &tangentPt);
|
| + if (kPoint_ReductionType == reductionType) {
|
| + return;
|
| + }
|
| + if (kLine_ReductionType == reductionType) {
|
| + this->lineTo(pt3);
|
| + return;
|
| + }
|
| + if (kDegenerate_ReductionType <= reductionType && kDegenerate3_ReductionType >= reductionType) {
|
| + this->lineTo(reduction[0]);
|
| + SkStrokerPriv::JoinProc saveJoiner = fJoiner;
|
| + fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
|
| + if (kDegenerate2_ReductionType <= reductionType) {
|
| + this->lineTo(reduction[1]);
|
| + }
|
| + if (kDegenerate3_ReductionType == reductionType) {
|
| + this->lineTo(reduction[2]);
|
| + }
|
| + this->lineTo(pt3);
|
| + fJoiner = saveJoiner;
|
| + return;
|
| + }
|
| + SkASSERT(kQuad_ReductionType == reductionType);
|
| + SkVector normalAB, unitAB, normalCD, unitCD;
|
| + this->preJoinTo(*tangentPt, &normalAB, &unitAB, false);
|
| + SkScalar tValues[2];
|
| + int count = SkFindCubicInflections(cubic, tValues);
|
| + SkScalar lastT = 0;
|
| + for (int index = 0; index <= count; ++index) {
|
| + SkScalar nextT = index < count ? tValues[index] : 1;
|
| + SkQuadConstruct quadPts;
|
| + this->init(kOuter_StrokeType, &quadPts, lastT, nextT);
|
| + if (!this->cubicStroke(cubic, &quadPts)) {
|
| + return;
|
| + }
|
| + this->init(kInner_StrokeType, &quadPts, lastT, nextT);
|
| + if (!this->cubicStroke(cubic, &quadPts)) {
|
| + return;
|
| + }
|
| + lastT = nextT;
|
| + }
|
| + this->setCubicEndNormal(cubic, normalAB, unitAB, &normalCD, &unitCD);
|
| +#else
|
| bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
|
| bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
|
| bool degenerateCD = SkPath::IsLineDegenerate(pt2, pt3);
|
| @@ -465,6 +1310,7 @@ void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
|
|
|
| }
|
| }
|
| +#endif
|
|
|
| this->postJoinTo(pt3, normalCD, unitCD);
|
| }
|
| @@ -498,6 +1344,13 @@ SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
|
| fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
|
| }
|
|
|
| +#if QUAD_STROKE_APPROXIMATION
|
| +void SkStroke::setError(SkScalar error) {
|
| + SkASSERT(error > 0);
|
| + fError = error;
|
| +}
|
| +#endif
|
| +
|
| void SkStroke::setWidth(SkScalar width) {
|
| SkASSERT(width >= 0);
|
| fWidth = width;
|
| @@ -575,8 +1428,13 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
|
| SkAutoConicToQuads converter;
|
| const SkScalar conicTol = SK_Scalar1 / 4;
|
|
|
| +#if QUAD_STROKE_APPROXIMATION
|
| + SkPathStroker stroker(src, radius, fMiterLimit, fError, this->getCap(),
|
| + this->getJoin());
|
| +#else
|
| SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(),
|
| this->getJoin());
|
| +#endif
|
| SkPath::Iter iter(src, false);
|
| SkPath::Verb lastSegment = SkPath::kMove_Verb;
|
|
|
|
|