diff --git a/gm/strokes.cpp b/gm/strokes.cpp index a258dfe005..9de20911ce 100644 --- a/gm/strokes.cpp +++ b/gm/strokes.cpp @@ -241,12 +241,42 @@ private: typedef skiagm::GM INHERITED; }; +class Strokes4GM : public skiagm::GM { +public: + Strokes4GM() {} + +protected: + + SkString onShortName() override { + return SkString("strokes_zoomed"); + } + + SkISize onISize() override { + return SkISize::Make(W, H*2); + } + + void onDraw(SkCanvas* canvas) override { + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(0.055f); + + canvas->scale(1000, 1000); + canvas->drawCircle(0, 2, 1.97f, paint); + } + +private: + typedef skiagm::GM INHERITED; +}; + + ////////////////////////////////////////////////////////////////////////////// static skiagm::GM* F0(void*) { return new StrokesGM; } static skiagm::GM* F1(void*) { return new Strokes2GM; } static skiagm::GM* F2(void*) { return new Strokes3GM; } +static skiagm::GM* F3(void*) { return new Strokes4GM; } static skiagm::GMRegistry R0(F0); static skiagm::GMRegistry R1(F1); static skiagm::GMRegistry R2(F2); +static skiagm::GMRegistry R3(F3); diff --git a/samplecode/SampleQuadStroker.cpp b/samplecode/SampleQuadStroker.cpp index 73373a2c97..05b6204026 100644 --- a/samplecode/SampleQuadStroker.cpp +++ b/samplecode/SampleQuadStroker.cpp @@ -304,10 +304,10 @@ protected: void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, SkScalar scale, bool drawText) { - SkRect bounds = path.getBounds(); - if (bounds.isEmpty()) { + if (path.isEmpty()) { return; } + SkRect bounds = path.getBounds(); this->setWHZ(SkScalarCeilToInt(bounds.right()), drawText ? SkScalarRoundToInt(scale * 3 / 2) : SkScalarRoundToInt(scale), SkScalarRoundToInt(950.0f / scale)); diff --git a/src/core/SkGeometry.cpp b/src/core/SkGeometry.cpp index b4489d28e3..2cda344a5d 100644 --- a/src/core/SkGeometry.cpp +++ b/src/core/SkGeometry.cpp @@ -1474,10 +1474,12 @@ void SkConic::computeFastBounds(SkRect* bounds) const { bounds->set(fPts, 3); } +#if 0 // unimplemented bool SkConic::findMaxCurvature(SkScalar* t) const { // TODO: Implement me return false; } +#endif SkScalar SkConic::TransformW(const SkPoint pts[], SkScalar w, const SkMatrix& matrix) { diff --git a/src/core/SkGeometry.h b/src/core/SkGeometry.h index a0c13d6046..bd3abe1acf 100644 --- a/src/core/SkGeometry.h +++ b/src/core/SkGeometry.h @@ -276,7 +276,7 @@ struct SkConic { * * @return true if max curvature found inside 0..1 range, false otherwise */ - bool findMaxCurvature(SkScalar* t) const; +// bool findMaxCurvature(SkScalar* t) const; // unimplemented static SkScalar TransformW(const SkPoint[3], SkScalar w, const SkMatrix&); diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp index 6454f1694d..b4af918593 100644 --- a/src/core/SkStroke.cpp +++ b/src/core/SkStroke.cpp @@ -231,16 +231,16 @@ private: bool fFoundTangents; // do less work until tangents meet (cubic) void addDegenerateLine(const SkQuadConstruct* ); - ReductionType CheckConicLinear(const SkConic& , SkPoint* reduction); - ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3], + static ReductionType CheckConicLinear(const SkConic& , SkPoint* reduction); + static ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3], const SkPoint** tanPtPtr); - ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction); - ResultType compareQuadConic(const SkConic& , SkQuadConstruct* ); + static ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction); + ResultType compareQuadConic(const SkConic& , SkQuadConstruct* ) const; ResultType compareQuadCubic(const SkPoint cubic[4], SkQuadConstruct* ); ResultType compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* ); - bool conicPerpRay(const SkConic& , SkScalar t, SkPoint* tPt, SkPoint* onPt, + void conicPerpRay(const SkConic& , SkScalar t, SkPoint* tPt, SkPoint* onPt, SkPoint* tangent) const; - bool conicQuadEnds(const SkConic& , SkQuadConstruct* ); + void conicQuadEnds(const SkConic& , SkQuadConstruct* ) const; bool conicStroke(const SkConic& , SkQuadConstruct* ); bool cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* ) const; bool cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt, @@ -661,10 +661,20 @@ SkPathStroker::ReductionType SkPathStroker::CheckConicLinear(const SkConic& coni if (!conic_in_line(conic)) { return kQuad_ReductionType; } +#if 0 // once findMaxCurvature is implemented, this will be a better solution SkScalar t; if (!conic.findMaxCurvature(&t) || 0 == t) { return kLine_ReductionType; } +#else // but for now, use extrema instead + SkScalar xT = 0, yT = 0; + (void) conic.findXExtrema(&xT); + (void) conic.findYExtrema(&yT); + SkScalar t = SkTMax(xT, yT); + if (0 == t) { + return kLine_ReductionType; + } +#endif conic.evalAt(t, reduction, NULL); return kDegenerate_ReductionType; } @@ -923,36 +933,30 @@ void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt, // Given a conic 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::conicPerpRay(const SkConic& conic, SkScalar t, SkPoint* tPt, SkPoint* onPt, +void SkPathStroker::conicPerpRay(const SkConic& conic, SkScalar t, SkPoint* tPt, SkPoint* onPt, SkPoint* tangent) const { SkVector dxy; conic.evalAt(t, tPt, &dxy); if (dxy.fX == 0 && dxy.fY == 0) { dxy = conic.fPts[2] - conic.fPts[0]; } - setRayPts(*tPt, &dxy, onPt, tangent); - return true; + this->setRayPts(*tPt, &dxy, onPt, tangent); } // Given a conic and a t range, find the start and end if they haven't been found already. -bool SkPathStroker::conicQuadEnds(const SkConic& conic, SkQuadConstruct* quadPts) { +void SkPathStroker::conicQuadEnds(const SkConic& conic, SkQuadConstruct* quadPts) const { if (!quadPts->fStartSet) { SkPoint conicStartPt; - if (!this->conicPerpRay(conic, quadPts->fStartT, &conicStartPt, &quadPts->fQuad[0], - &quadPts->fTangentStart)) { - return false; - } + this->conicPerpRay(conic, quadPts->fStartT, &conicStartPt, &quadPts->fQuad[0], + &quadPts->fTangentStart); quadPts->fStartSet = true; } if (!quadPts->fEndSet) { SkPoint conicEndPt; - if (!this->conicPerpRay(conic, quadPts->fEndT, &conicEndPt, &quadPts->fQuad[2], - &quadPts->fTangentEnd)) { - return false; - } + this->conicPerpRay(conic, quadPts->fEndT, &conicEndPt, &quadPts->fQuad[2], + &quadPts->fTangentEnd); quadPts->fEndSet = true; } - return true; } @@ -1025,35 +1029,49 @@ SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts, const SkPoint& end = quadPts->fQuad[2]; SkVector aLen = quadPts->fTangentStart - start; SkVector bLen = quadPts->fTangentEnd - end; + /* Slopes match when denom goes to zero: + axLen / ayLen == bxLen / byLen + (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen + byLen * axLen == ayLen * bxLen + byLen * axLen - ayLen * bxLen ( == denom ) + */ SkScalar denom = aLen.cross(bLen); + if (denom == 0 || !SkScalarIsFinite(denom)) { + return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, "denom == 0"); + } SkVector ab0 = start - end; SkScalar numerA = bLen.cross(ab0); SkScalar numerB = aLen.cross(ab0); - if (!SkScalarNearlyZero(denom)) { + if ((numerA >= 0) == (numerB >= 0)) { // if the control point is outside the quad ends // 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 ((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); - } if (SkTMax(dist1, dist2) <= fInvResScaleSquared) { return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, "SkTMax(dist1=%g, dist2=%g) <= fInvResScaleSquared", dist1, dist2); } 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); } + // check to see if the denomerator is teeny relative to the numerator + bool validDivide = SkScalarAbs(numerA) * SK_ScalarNearlyZero < SkScalarAbs(denom); + SkASSERT(!SkScalarNearlyZero(denom / numerA) == validDivide); + if (validDivide) { + if (kCtrlPt_RayType == intersectRayType) { + numerA /= denom; + SkPoint* ctrlPt = &quadPts->fQuad[1]; + // the intersection of the tangents need not be on the tangent segment + // so 0 <= numerA <= 1 is not necessarily true + 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); + } + // 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. @@ -1062,7 +1080,7 @@ SkPathStroker::ResultType SkPathStroker::tangentsMeet(const SkPoint cubic[4], if (!this->cubicQuadEnds(cubic, quadPts)) { return kNormalError_ResultType; } - return intersectRay(quadPts, kResultType_RayType STROKER_DEBUG_PARAMS(fRecursionDepth)); + return this->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. @@ -1178,7 +1196,7 @@ SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4] if (!this->cubicQuadEnds(cubic, quadPts)) { return kNormalError_ResultType; } - ResultType resultType = intersectRay(quadPts, kCtrlPt_RayType + ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType STROKER_DEBUG_PARAMS(fRecursionDepth) ); if (resultType != kQuad_ResultType) { return resultType; @@ -1188,26 +1206,24 @@ SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4] if (!this->cubicPerpRay(cubic, quadPts->fMidT, &ray[1], &ray[0], NULL)) { return kNormalError_ResultType; } - return strokeCloseEnough(quadPts->fQuad, ray, quadPts STROKER_DEBUG_PARAMS(fRecursionDepth)); + return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts + STROKER_DEBUG_PARAMS(fRecursionDepth)); } SkPathStroker::ResultType SkPathStroker::compareQuadConic(const SkConic& conic, - SkQuadConstruct* quadPts) { + SkQuadConstruct* quadPts) const { // get the quadratic approximation of the stroke - if (!this->conicQuadEnds(conic, quadPts)) { - return kNormalError_ResultType; - } - ResultType resultType = intersectRay(quadPts, kCtrlPt_RayType + this->conicQuadEnds(conic, quadPts); + ResultType resultType = this->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]; // points near midpoint on quad, midpoint on conic - if (!this->conicPerpRay(conic, quadPts->fMidT, &ray[1], &ray[0], NULL)) { - return kNormalError_ResultType; - } - return strokeCloseEnough(quadPts->fQuad, ray, quadPts STROKER_DEBUG_PARAMS(fRecursionDepth)); + this->conicPerpRay(conic, quadPts->fMidT, &ray[1], &ray[0], NULL); + return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts + STROKER_DEBUG_PARAMS(fRecursionDepth)); } SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3], @@ -1225,7 +1241,7 @@ SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3], &quadPts->fTangentEnd); quadPts->fEndSet = true; } - ResultType resultType = intersectRay(quadPts, kCtrlPt_RayType + ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType STROKER_DEBUG_PARAMS(fRecursionDepth)); if (resultType != kQuad_ResultType) { return resultType; @@ -1233,7 +1249,8 @@ SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3], // 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)); + return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts + STROKER_DEBUG_PARAMS(fRecursionDepth)); } void SkPathStroker::addDegenerateLine(const SkQuadConstruct* quadPts) { @@ -1577,8 +1594,8 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { } } - SkAutoConicToQuads converter; #ifdef SK_LEGACY_STROKE_CURVES + SkAutoConicToQuads converter; const SkScalar conicTol = SK_Scalar1 / 4 / fResScale; #endif SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(), fResScale); diff --git a/tools/pathops_sorter.htm b/tools/pathops_sorter.htm index 3411505eb7..4fb18fad64 100644 --- a/tools/pathops_sorter.htm +++ b/tools/pathops_sorter.htm @@ -7,9 +7,10 @@
-{{{6, -3}, {0, 1}}} id=3 -{{{1.6714313f, -1.08141601f}, {2.24979973f, -1.14467525f}, {2.27122664f, -0.514151096f}, {0, 1}}} id=5 -{{{0.001119050197303295135, 0.9992539882659912109}, {4.001119136810302734, 6.999254226684570312}}}, +{{{fX=1.97000003 fY=2.00000000 }, {fX=1.97000003 fY=3.97000003 }, {fX=0.000000000 fY=3.97000003 }} fW: 0.707106769 +{{{fX=1.99750006 fY=2.00000000 } {fX=1.99750006 fY=2.02749991 } +{{{fX=1.96415257 fY=2.36347127 } {fX=1.95914865 fY=2.39051223 } +