diff options
author | caryclark <caryclark@google.com> | 2015-02-20 06:33:57 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-02-20 06:33:57 -0800 |
commit | 04e4d08556750ff6be4576a4cd4925964c63376f (patch) | |
tree | a311cbe718948f35a5408ed175cb93f3be34316b | |
parent | a1f1ee98a1f6d0770f6243270ca2f0e6c92efaba (diff) |
This uses quad approximations of the outer and inner paths describing a stroke. Cubics and conics' thick strokes are approximated with quads as well.
The approximation uses a similar error term as the fill scan converter to determine the number of quads to use.
This also updates SampleApp QuadStroker test with conics, ovals, and stroked text.
Review URL: https://codereview.chromium.org/932113002
-rw-r--r-- | gyp/common.gypi | 1 | ||||
-rw-r--r-- | samplecode/SampleQuadStroker.cpp | 175 | ||||
-rw-r--r-- | src/core/SkStroke.cpp | 291 | ||||
-rw-r--r-- | src/core/SkStroke.h | 14 | ||||
-rw-r--r-- | src/core/SkStrokeRec.cpp | 11 |
5 files changed, 368 insertions, 124 deletions
diff --git a/gyp/common.gypi b/gyp/common.gypi index ce3fc2669d..9e48c17b96 100644 --- a/gyp/common.gypi +++ b/gyp/common.gypi @@ -15,6 +15,7 @@ 'SK_INTERNAL', 'SK_GAMMA_SRGB', 'SK_GAMMA_APPLY_TO_A8', + 'SK_QUAD_STROKE_APPROXIMATION', 'SK_SCALAR_TO_FLOAT_EXCLUDED', # temporary to allow Chrome to call SkFloatToScalar # 'SK_USE_DISCARDABLE_SCALEDIMAGECACHE', # TODO(reed): Re-enable when tests don't crash with this. ], diff --git a/samplecode/SampleQuadStroker.cpp b/samplecode/SampleQuadStroker.cpp index 4fd6e7434b..a3863a8991 100644 --- a/samplecode/SampleQuadStroker.cpp +++ b/samplecode/SampleQuadStroker.cpp @@ -85,6 +85,10 @@ struct StrokeTypeButton { bool fEnabled; }; +struct CircleTypeButton : public StrokeTypeButton { + bool fFill; +}; + class QuadStrokerView : public SampleView { enum { SKELETON_COLOR = 0xFF0000FF, @@ -92,9 +96,10 @@ class QuadStrokerView : public SampleView { }; enum { - kCount = 10 + kCount = 15 }; SkPoint fPts[kCount]; + SkRect fWeightControl; SkRect fErrorControl; SkRect fWidthControl; SkRect fBounds; @@ -103,11 +108,14 @@ class QuadStrokerView : public SampleView { SkAutoTUnref<SkSurface> fMinSurface; SkAutoTUnref<SkSurface> fMaxSurface; StrokeTypeButton fCubicButton; + StrokeTypeButton fConicButton; StrokeTypeButton fQuadButton; StrokeTypeButton fRRectButton; + CircleTypeButton fCircleButton; StrokeTypeButton fTextButton; SkString fText; SkScalar fTextSize; + SkScalar fWeight; SkScalar fWidth, fDWidth; SkScalar fWidthScale; int fW, fH, fZoom; @@ -124,32 +132,44 @@ public: QuadStrokerView() { this->setBGColor(SK_ColorLTGRAY); - fPts[0].set(50, 200); + fPts[0].set(50, 200); // cubic fPts[1].set(50, 100); fPts[2].set(150, 50); fPts[3].set(300, 50); - fPts[4].set(350, 200); + fPts[4].set(350, 200); // conic fPts[5].set(350, 100); fPts[6].set(450, 50); - fPts[7].set(200, 200); - fPts[8].set(400, 400); + fPts[7].set(150, 300); // quad + fPts[8].set(150, 200); + fPts[9].set(250, 150); + + fPts[10].set(200, 200); // rrect + fPts[11].set(400, 400); + + fPts[12].set(250, 250); // oval + fPts[13].set(450, 450); - fPts[9].set(250, 800); fText = "a"; fTextSize = 12; fWidth = 50; fDWidth = 0.25f; + fWeight = 1; fCubicButton.fLabel = 'C'; fCubicButton.fEnabled = false; + fConicButton.fLabel = 'K'; + fConicButton.fEnabled = true; fQuadButton.fLabel = 'Q'; fQuadButton.fEnabled = false; fRRectButton.fLabel = 'R'; fRRectButton.fEnabled = false; + fCircleButton.fLabel = 'O'; + fCircleButton.fEnabled = false; + fCircleButton.fFill = false; fTextButton.fLabel = 'T'; - fTextButton.fEnabled = true; + fTextButton.fEnabled = false; fAnimate = true; setAsNeeded(); } @@ -183,12 +203,21 @@ protected: } void onSizeChange() SK_OVERRIDE { + fWeightControl.setXYWH(this->width() - 150, 30, 30, 400); fErrorControl.setXYWH(this->width() - 100, 30, 30, 400); fWidthControl.setXYWH(this->width() - 50, 30, 30, 400); - fCubicButton.fBounds.setXYWH(this->width() - 50, 450, 30, 30); - fQuadButton.fBounds.setXYWH(this->width() - 50, 500, 30, 30); - fRRectButton.fBounds.setXYWH(this->width() - 50, 550, 30, 30); - fTextButton.fBounds.setXYWH(this->width() - 50, 600, 30, 30); + int buttonOffset = 450; + fCubicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); + buttonOffset += 50; + fConicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); + buttonOffset += 50; + fQuadButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); + buttonOffset += 50; + fRRectButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); + buttonOffset += 50; + fCircleButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); + buttonOffset += 50; + fTextButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30); this->INHERITED::onSizeChange(); } @@ -273,25 +302,26 @@ protected: } } - void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, bool drawText) { + void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, SkScalar scale, + bool drawText) { SkRect bounds = path.getBounds(); if (bounds.isEmpty()) { return; } - this->setWHZ(SkScalarCeilToInt(bounds.right()), SkScalarRoundToInt(fTextSize * 3 / 2), - SkScalarRoundToInt(950.0f / fTextSize)); + this->setWHZ(SkScalarCeilToInt(bounds.right()), drawText + ? SkScalarRoundToInt(scale * 3 / 2) : SkScalarRoundToInt(scale), + SkScalarRoundToInt(950.0f / scale)); erase(fMinSurface); SkPaint paint; paint.setColor(0x1f1f0f0f); - fMinSurface->getCanvas()->drawPath(path, paint); paint.setStyle(SkPaint::kStroke_Style); - paint.setStrokeWidth(width * fTextSize * fTextSize); + paint.setStrokeWidth(width * scale * scale); paint.setColor(0x3f0f1f3f); - fMinSurface->getCanvas()->drawPath(path, paint); - - this->copyMinToMax(); - fMaxSurface->draw(canvas, 0, 0, NULL); - + if (drawText) { + fMinSurface->getCanvas()->drawPath(path, paint); + this->copyMinToMax(); + fMaxSurface->draw(canvas, 0, 0, NULL); + } paint.setAntiAlias(true); paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(1); @@ -300,7 +330,7 @@ protected: SkPath scaled; SkMatrix matrix; matrix.reset(); - matrix.setScale(950 / fTextSize, 950 / fTextSize); + matrix.setScale(950 / scale, 950 / scale); if (drawText) { path.transform(matrix, &scaled); } else { @@ -317,8 +347,11 @@ protected: SkPaint p; p.setStyle(SkPaint::kStroke_Style); - p.setStrokeWidth(width * fTextSize * fTextSize); - + if (drawText) { + p.setStrokeWidth(width * scale * scale); + } else { + p.setStrokeWidth(width); + } p.getFillPath(path, &fill); SkPath scaledFill; if (drawText) { @@ -331,6 +364,33 @@ protected: draw_points(canvas, scaledFill, WIREFRAME_COLOR, false); } + void draw_fill(SkCanvas* canvas, const SkRect& rect, SkScalar width) { + if (rect.isEmpty()) { + return; + } + SkPaint paint; + paint.setColor(0x1f1f0f0f); + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(width); + SkPath path; + SkScalar maxSide = SkTMax(rect.width(), rect.height()) / 2; + SkPoint center = { rect.fLeft + maxSide, rect.fTop + maxSide }; + path.addCircle(center.fX, center.fY, maxSide); + canvas->drawPath(path, paint); + paint.setStyle(SkPaint::kFill_Style); + path.reset(); + path.addCircle(center.fX, center.fY, maxSide - width / 2); + paint.setColor(0x3f0f1f3f); + canvas->drawPath(path, paint); + path.reset(); + path.setFillType(SkPath::kEvenOdd_FillType); + path.addCircle(center.fX, center.fY, maxSide + width / 2); + SkRect outside = SkRect::MakeXYWH(center.fX - maxSide - width, center.fY - maxSide - width, + (maxSide + width) * 2, (maxSide + width) * 2); + path.addRect(outside); + canvas->drawPath(path, paint); + } + void draw_button(SkCanvas* canvas, const StrokeTypeButton& button) { SkPaint paint; paint.setAntiAlias(true); @@ -377,7 +437,8 @@ protected: } void setAsNeeded() { - if (fCubicButton.fEnabled || fQuadButton.fEnabled || fRRectButton.fEnabled) { + if (fConicButton.fEnabled || fCubicButton.fEnabled || fQuadButton.fEnabled + || fRRectButton.fEnabled || fCircleButton.fEnabled) { setForGeometry(); } else { setForText(); @@ -392,27 +453,34 @@ protected: path.moveTo(fPts[0]); path.cubicTo(fPts[1], fPts[2], fPts[3]); setForGeometry(); - draw_stroke(canvas, path, width, false); + draw_stroke(canvas, path, width, 950, false); + } + + if (fConicButton.fEnabled) { + path.moveTo(fPts[4]); + path.conicTo(fPts[5], fPts[6], fWeight); + setForGeometry(); + draw_stroke(canvas, path, width, 950, false); } if (fQuadButton.fEnabled) { path.reset(); - path.moveTo(fPts[4]); - path.quadTo(fPts[5], fPts[6]); + path.moveTo(fPts[7]); + path.quadTo(fPts[8], fPts[9]); setForGeometry(); - draw_stroke(canvas, path, width, false); + draw_stroke(canvas, path, width, 950, false); } if (fRRectButton.fEnabled) { SkScalar rad = 32; SkRect r; - r.set(&fPts[7], 2); + r.set(&fPts[10], 2); path.reset(); SkRRect rr; rr.setRectXY(r, rad, rad); path.addRRect(rr); setForGeometry(); - draw_stroke(canvas, path, width, false); + draw_stroke(canvas, path, width, 950, false); path.reset(); SkRRect rr2; @@ -426,6 +494,19 @@ protected: canvas->drawPath(path, paint); } + if (fCircleButton.fEnabled) { + path.reset(); + SkRect r; + r.set(&fPts[12], 2); + path.addOval(r); + setForGeometry(); + if (fCircleButton.fFill) { + draw_fill(canvas, r, width); + } else { + draw_stroke(canvas, path, width, 950, false); + } + } + if (fTextButton.fEnabled) { path.reset(); SkPaint paint; @@ -433,7 +514,7 @@ protected: paint.setTextSize(fTextSize); paint.getTextPath(fText.c_str(), fText.size(), 0, fTextSize, &path); setForText(); - draw_stroke(canvas, path, width * fWidthScale / fTextSize, true); + draw_stroke(canvas, path, width * fWidthScale / fTextSize, fTextSize, true); } if (fAnimate) { @@ -445,6 +526,9 @@ protected: } } setAsNeeded(); + if (fConicButton.fEnabled) { + draw_control(canvas, fWeightControl, fWeight, 0, 5, "weight"); + } #if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG) draw_control(canvas, fErrorControl, gDebugStrokerError, kStrokerErrorMin, kStrokerErrorMax, "error"); @@ -453,7 +537,9 @@ protected: kWidthMax * fWidthScale, "width"); draw_button(canvas, fQuadButton); draw_button(canvas, fCubicButton); + draw_button(canvas, fConicButton); draw_button(canvas, fRRectButton); + draw_button(canvas, fCircleButton); draw_button(canvas, fTextButton); this->inval(NULL); } @@ -472,9 +558,12 @@ protected: } } const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1); + if (fWeightControl.contains(rectPt)) { + return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 1); + } #if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG) if (fErrorControl.contains(rectPt)) { - return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 1); + return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 2); } #endif if (fWidthControl.contains(rectPt)) { @@ -484,17 +573,27 @@ protected: fCubicButton.fEnabled ^= true; return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 4); } + if (fConicButton.fBounds.contains(rectPt)) { + fConicButton.fEnabled ^= true; + return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 5); + } if (fQuadButton.fBounds.contains(rectPt)) { fQuadButton.fEnabled ^= true; - return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 5); + return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 6); } if (fRRectButton.fBounds.contains(rectPt)) { fRRectButton.fEnabled ^= true; - return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 6); + return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 7); + } + if (fCircleButton.fBounds.contains(rectPt)) { + bool wasEnabled = fCircleButton.fEnabled; + fCircleButton.fEnabled = !fCircleButton.fFill; + fCircleButton.fFill = wasEnabled && !fCircleButton.fFill; + return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 8); } if (fTextButton.fBounds.contains(rectPt)) { fTextButton.fEnabled ^= true; - return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 7); + return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 9); } return this->INHERITED::onFindClickHandler(x, y, modi); } @@ -510,9 +609,11 @@ protected: fPts[index].offset(SkIntToScalar(click->fICurr.fX - click->fIPrev.fX), SkIntToScalar(click->fICurr.fY - click->fIPrev.fY)); this->inval(NULL); + } else if (index == (int) SK_ARRAY_COUNT(fPts) + 1) { + fWeight = MapScreenYtoValue(click->fICurr.fY, fWeightControl, 0, 5); } #if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG) - else if (index == (int) SK_ARRAY_COUNT(fPts) + 1) { + else if (index == (int) SK_ARRAY_COUNT(fPts) + 2) { gDebugStrokerError = SkTMax(FLT_EPSILON, MapScreenYtoValue(click->fICurr.fY, fErrorControl, kStrokerErrorMin, kStrokerErrorMax)); gDebugStrokerErrorSet = true; diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp index db76cafe35..1a44a3a19f 100644 --- a/src/core/SkStroke.cpp +++ b/src/core/SkStroke.cpp @@ -9,26 +9,26 @@ #include "SkGeometry.h" #include "SkPath.h" -#if QUAD_STROKE_APPROXIMATION +#ifdef SK_QUAD_STROKE_APPROXIMATION enum { kTangent_RecursiveLimit, kCubic_RecursiveLimit, + kConic_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 + static const int kRecursiveLimits[] = { 5*3, 26*3, 11*3, 11*3 }; // 3x limits seen in practice + SK_COMPILE_ASSERT(0 == kTangent_RecursiveLimit, cubic_stroke_relies_on_tangent_equalling_zero); + SK_COMPILE_ASSERT(1 == kCubic_RecursiveLimit, cubic_stroke_relies_on_cubic_equalling_one); 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; - + #ifdef SK_DEBUG int gMaxRecursion[SK_ARRAY_COUNT(kRecursiveLimits)] = { 0 }; #endif #ifndef DEBUG_QUAD_STROKER @@ -51,14 +51,16 @@ #endif +#ifndef SK_QUAD_STROKE_APPROXIMATION #define kMaxQuadSubdivide 5 #define kMaxCubicSubdivide 7 +#endif static inline bool degenerate_vector(const SkVector& v) { return !SkPoint::CanNormalize(v.fX, v.fY); } -#if !QUAD_STROKE_APPROXIMATION +#ifndef SK_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) @@ -111,7 +113,7 @@ static bool set_normal_unitnormal(const SkVector& vec, } /////////////////////////////////////////////////////////////////////////////// -#if QUAD_STROKE_APPROXIMATION +#ifdef SK_QUAD_STROKE_APPROXIMATION struct SkQuadConstruct { // The state of the quad stroke under construction. SkPoint fQuad[3]; // the stroked quad parallel to the original curve @@ -156,19 +158,16 @@ struct SkQuadConstruct { // The state of the quad stroke under construction. class SkPathStroker { public: -#if QUAD_STROKE_APPROXIMATION - SkPathStroker(const SkPath& src, - SkScalar radius, SkScalar miterLimit, SkScalar error, SkPaint::Cap, - SkPaint::Join, SkScalar resScale); -#else SkPathStroker(const SkPath& src, SkScalar radius, SkScalar miterLimit, SkPaint::Cap, SkPaint::Join, SkScalar resScale); -#endif void moveTo(const SkPoint&); void lineTo(const SkPoint&); void quadTo(const SkPoint&, const SkPoint&); +#ifdef SK_QUAD_STROKE_APPROXIMATION + void conicTo(const SkPoint&, const SkPoint&, SkScalar weight); +#endif void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&); void close(bool isLine) { this->finishContour(true, isLine); } @@ -181,12 +180,13 @@ public: SkScalar getResScale() const { return fResScale; } private: -#if QUAD_STROKE_APPROXIMATION - SkScalar fError; -#endif SkScalar fRadius; SkScalar fInvMiterLimit; SkScalar fResScale; +#ifdef SK_QUAD_STROKE_APPROXIMATION + SkScalar fInvResScale; + SkScalar fInvResScaleSquared; +#endif SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal; SkPoint fFirstPt, fPrevPt; // on original path @@ -200,7 +200,7 @@ private: SkPath fInner, fOuter; // outer is our working answer, inner is temp SkPath fExtra; // added as extra complete contours -#if QUAD_STROKE_APPROXIMATION +#ifdef SK_QUAD_STROKE_APPROXIMATION enum StrokeType { kOuter_StrokeType = 1, // use sign-opposite values later to flip perpendicular axis kInner_StrokeType = -1 @@ -231,11 +231,17 @@ 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], const SkPoint** tanPtPtr); ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction); + ResultType compareQuadConic(const SkConic& , SkQuadConstruct* ); ResultType compareQuadCubic(const SkPoint cubic[4], SkQuadConstruct* ); ResultType compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* ); + bool conicPerpRay(const SkConic& , SkScalar t, SkPoint* tPt, SkPoint* onPt, + SkPoint* tangent) const; + bool conicQuadEnds(const SkConic& , SkQuadConstruct* ); + 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, SkPoint* tangent) const; @@ -248,6 +254,9 @@ private: void quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt, SkPoint* tangent) const; bool quadStroke(const SkPoint quad[3], SkQuadConstruct* ); + void setConicEndNormal(const SkConic& , + const SkVector& normalAB, const SkVector& unitNormalAB, + SkVector* normalBC, SkVector* unitNormalBC); void setCubicEndNormal(const SkPoint cubic[4], const SkVector& normalAB, const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD); @@ -268,7 +277,7 @@ private: const SkVector& unitNormal); void line_to(const SkPoint& currPt, const SkVector& normal); -#if !QUAD_STROKE_APPROXIMATION +#ifndef SK_QUAD_STROKE_APPROXIMATION void quad_to(const SkPoint pts[3], const SkVector& normalAB, const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC, @@ -348,16 +357,11 @@ 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, SkScalar resScale) -#else SkPathStroker::SkPathStroker(const SkPath& src, SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap, SkPaint::Join join, SkScalar resScale) -#endif - : fRadius(radius), fResScale(resScale) { + : fRadius(radius) + , fResScale(resScale) { /* This is only used when join is miter_join, but we initialize it here so that it is always defined, to fis valgrind warnings. @@ -386,15 +390,11 @@ SkPathStroker::SkPathStroker(const SkPath& src, fOuter.setIsVolatile(true); fInner.incReserve(src.countPoints()); fInner.setIsVolatile(true); -#if QUAD_STROKE_APPROXIMATION -#ifdef SK_DEBUG - if (!gDebugStrokerErrorSet) { - gDebugStrokerError = error; - } - fError = gDebugStrokerError; -#else - fError = error; -#endif +#ifdef SK_QUAD_STROKE_APPROXIMATION + // TODO : write a common error function used by stroking and filling + // The '4' below matches the fill scan converter's error term + fInvResScale = SkScalarInvert(resScale * 4); + fInvResScaleSquared = fInvResScale * fInvResScale; fRecursionDepth = 0; #endif } @@ -423,7 +423,7 @@ void SkPathStroker::lineTo(const SkPoint& currPt) { this->postJoinTo(currPt, normal, unitNormal); } -#if !QUAD_STROKE_APPROXIMATION +#ifndef SK_QUAD_STROKE_APPROXIMATION void SkPathStroker::quad_to(const SkPoint pts[3], const SkVector& normalAB, const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC, @@ -461,7 +461,7 @@ void SkPathStroker::quad_to(const SkPoint pts[3], } #endif -#if QUAD_STROKE_APPROXIMATION +#ifdef SK_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)) { @@ -470,6 +470,11 @@ void SkPathStroker::setQuadEndNormal(const SkPoint quad[3], const SkVector& norm } } +void SkPathStroker::setConicEndNormal(const SkConic& conic, const SkVector& normalAB, + const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) { + setQuadEndNormal(conic.fPts, normalAB, unitNormalAB, normalBC, unitNormalBC); +} + void SkPathStroker::setCubicEndNormal(const SkPoint cubic[4], const SkVector& normalAB, const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD) { SkVector ab = cubic[1] - cubic[0]; @@ -547,7 +552,8 @@ static SkScalar pt_to_line(const SkPoint& pt, const SkPoint& lineStart, const Sk */ static bool cubic_in_line(const SkPoint cubic[4]) { SkScalar ptMax = -1; - int outer1, outer2; + int outer1 SK_INIT_TO_AVOID_WARNING; + int outer2 SK_INIT_TO_AVOID_WARNING; for (int index = 0; index < 3; ++index) { for (int inner = index + 1; inner < 4; ++inner) { SkVector testDiff = cubic[inner] - cubic[index]; @@ -583,7 +589,8 @@ static bool cubic_in_line(const SkPoint cubic[4]) { */ static bool quad_in_line(const SkPoint quad[3]) { SkScalar ptMax = -1; - int outer1, outer2; + int outer1 SK_INIT_TO_AVOID_WARNING; + int outer2 SK_INIT_TO_AVOID_WARNING; for (int index = 0; index < 2; ++index) { for (int inner = index + 1; inner < 3; ++inner) { SkVector testDiff = quad[inner] - quad[index]; @@ -603,6 +610,10 @@ static bool quad_in_line(const SkPoint quad[3]) { return pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= lineSlop; } +static bool conic_in_line(const SkConic& conic) { + return quad_in_line(conic.fPts); +} + SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3], const SkPoint** tangentPtPtr) { bool degenerateAB = degenerate_vector(cubic[1] - cubic[0]); @@ -634,6 +645,27 @@ SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic return (ReductionType) (kQuad_ReductionType + count); } +SkPathStroker::ReductionType SkPathStroker::CheckConicLinear(const SkConic& conic, + SkPoint* reduction) { + bool degenerateAB = degenerate_vector(conic.fPts[1] - conic.fPts[0]); + bool degenerateBC = degenerate_vector(conic.fPts[2] - conic.fPts[1]); + if (degenerateAB & degenerateBC) { + return kPoint_ReductionType; + } + if (degenerateAB | degenerateBC) { + return kLine_ReductionType; + } + if (!conic_in_line(conic)) { + return kQuad_ReductionType; + } + SkScalar t; + if (!conic.findMaxCurvature(&t) || 0 == t) { + return kLine_ReductionType; + } + conic.evalAt(t, reduction, NULL); + return kDegenerate_ReductionType; +} + SkPathStroker::ReductionType SkPathStroker::CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction) { bool degenerateAB = degenerate_vector(quad[1] - quad[0]); @@ -742,8 +774,45 @@ DRAW_LINE: } #endif +#ifdef SK_QUAD_STROKE_APPROXIMATION +void SkPathStroker::conicTo(const SkPoint& pt1, const SkPoint& pt2, SkScalar weight) { + const SkConic conic(fPrevPt, pt1, pt2, weight); + SkPoint reduction; + ReductionType reductionType = CheckConicLinear(conic, &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->conicStroke(conic, &quadPts)) { + return; + } + this->init(kInner_StrokeType, &quadPts, 0, 1); + if (!this->conicStroke(conic, &quadPts)) { + return; + } + this->setConicEndNormal(conic, normalAB, unitAB, &normalBC, &unitBC); + this->postJoinTo(pt2, normalBC, unitBC); +} +#endif + void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) { -#if QUAD_STROKE_APPROXIMATION +#ifdef SK_QUAD_STROKE_APPROXIMATION const SkPoint quad[3] = { fPrevPt, pt1, pt2 }; SkPoint reduction; ReductionType reductionType = CheckQuadLinear(quad, &reduction); @@ -831,7 +900,7 @@ void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) { this->postJoinTo(pt2, normalBC, unitBC); } -#if QUAD_STROKE_APPROXIMATION +#ifdef SK_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, @@ -852,6 +921,41 @@ 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, + 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; +} + +// 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) { + if (!quadPts->fStartSet) { + SkPoint conicStartPt; + if (!this->conicPerpRay(conic, quadPts->fStartT, &conicStartPt, &quadPts->fQuad[0], + &quadPts->fTangentStart)) { + return false; + } + quadPts->fStartSet = true; + } + if (!quadPts->fEndSet) { + SkPoint conicEndPt; + if (!this->conicPerpRay(conic, quadPts->fEndT, &conicEndPt, &quadPts->fQuad[2], + &quadPts->fTangentEnd)) { + return false; + } + quadPts->fEndSet = true; + } + return true; +} + + // 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, @@ -930,10 +1034,6 @@ SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts, // 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; @@ -944,6 +1044,10 @@ SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts, 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 @@ -979,19 +1083,19 @@ static int intersect_quad_ray(const SkPoint line[2], const SkPoint quad[3], SkSc // 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) { + if (pt.fX + fInvResScale < xMin) { return false; } SkScalar xMax = SkTMax(SkTMax(quad[0].fX, quad[1].fX), quad[2].fX); - if (pt.fX - fError > xMax) { + if (pt.fX - fInvResScale > xMax) { return false; } SkScalar yMin = SkTMin(SkTMin(quad[0].fY, quad[1].fY), quad[2].fY); - if (pt.fY + fError < yMin) { + if (pt.fY + fInvResScale < yMin) { return false; } SkScalar yMax = SkTMax(SkTMax(quad[0].fY, quad[1].fY), quad[2].fY); - if (pt.fY - fError > yMax) { + if (pt.fY - fInvResScale > yMax) { return false; } return true; @@ -1022,7 +1126,7 @@ SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[ 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 (points_within_dist(ray[0], strokeMid, fInvResScaleSquared)) { // 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", @@ -1031,8 +1135,8 @@ SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[ 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); + "points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fInvResScaleSquared=%g)", + ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY, fInvResScaleSquared); } // measure the distance to quad's bounds (quick reject) // an alternative : look for point in triangle @@ -1051,7 +1155,7 @@ SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[ } SkPoint quadPt; SkEvalQuadAt(stroke, roots[0], &quadPt); - SkScalar error = fError * (SK_Scalar1 - SkScalarAbs(roots[0] - 0.5f) * 2); + SkScalar error = fInvResScale * (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, @@ -1080,14 +1184,32 @@ SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4] return resultType; } // project a ray from the curve to the stroke - SkPoint ray[2]; // point near midpoint on quad, midpoint on cubic + SkPoint ray[2]; // points 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::compareQuadConic(const SkConic& conic, + SkQuadConstruct* quadPts) { + // get the quadratic approximation of the stroke + if (!this->conicQuadEnds(conic, 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]; // 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)); +} + SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* quadPts) { // get the quadratic approximation of the stroke @@ -1126,7 +1248,7 @@ bool SkPathStroker::cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct return false; } SkScalar dist = pt_to_line(strokeMid, quadPts->fQuad[0], quadPts->fQuad[2]); - return dist < fError * fError; + return dist < fInvResScaleSquared; } bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts) { @@ -1137,8 +1259,8 @@ bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts return false; } if ((kDegenerate_ResultType == resultType - || points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2], fError)) - && cubicMidOnLine(cubic, quadPts)) { + || points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2], + fInvResScaleSquared)) && cubicMidOnLine(cubic, quadPts)) { addDegenerateLine(quadPts); return true; } @@ -1189,6 +1311,36 @@ bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts return true; } +bool SkPathStroker::conicStroke(const SkConic& conic, SkQuadConstruct* quadPts) { + ResultType resultType = this->compareQuadConic(conic, 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[kConic_RecursiveLimit] = SkTMax(gMaxRecursion[kConic_RecursiveLimit], + fRecursionDepth + 1)); + if (++fRecursionDepth > kRecursiveLimits[kConic_RecursiveLimit]) { + return false; // just abort if projected quad isn't representable + } + SkQuadConstruct half; + (void) half.initWithStart(quadPts); + if (!this->conicStroke(conic, &half)) { + return false; + } + (void) half.initWithEnd(quadPts); + if (!this->conicStroke(conic, &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) { @@ -1223,7 +1375,7 @@ bool SkPathStroker::quadStroke(const SkPoint quad[3], SkQuadConstruct* quadPts) void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkPoint& pt3) { -#if QUAD_STROKE_APPROXIMATION +#ifdef SK_QUAD_STROKE_APPROXIMATION const SkPoint cubic[4] = { fPrevPt, pt1, pt2, pt3 }; SkPoint reduction[3]; const SkPoint* tangentPt; @@ -1349,13 +1501,6 @@ 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; @@ -1432,14 +1577,10 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { } SkAutoConicToQuads converter; +#ifndef SK_QUAD_STROKE_APPROXIMATION const SkScalar conicTol = SK_Scalar1 / 4 / fResScale; - -#if QUAD_STROKE_APPROXIMATION - SkPathStroker stroker(src, radius, fMiterLimit, fError, this->getCap(), - this->getJoin(), fResScale); -#else - SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(), fResScale); #endif + SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(), fResScale); SkPath::Iter iter(src, false); SkPath::Verb lastSegment = SkPath::kMove_Verb; @@ -1458,6 +1599,11 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { lastSegment = SkPath::kQuad_Verb; break; case SkPath::kConic_Verb: { +#ifdef SK_QUAD_STROKE_APPROXIMATION + stroker.conicTo(pts[1], pts[2], iter.conicWeight()); + lastSegment = SkPath::kConic_Verb; + break; +#else // todo: if we had maxcurvature for conics, perhaps we should // natively extrude the conic instead of converting to quads. const SkPoint* quadPts = @@ -1467,6 +1613,7 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { quadPts += 2; } lastSegment = SkPath::kQuad_Verb; +#endif } break; case SkPath::kCubic_Verb: stroker.cubicTo(pts[1], pts[2], pts[3]); diff --git a/src/core/SkStroke.h b/src/core/SkStroke.h index a5446a4178..0574f386b3 100644 --- a/src/core/SkStroke.h +++ b/src/core/SkStroke.h @@ -13,15 +13,9 @@ #include "SkPaint.h" #include "SkStrokerPriv.h" -// set to 1 to use experimental outset stroking with quads -#ifndef QUAD_STROKE_APPROXIMATION -#define QUAD_STROKE_APPROXIMATION 0 -#endif - -#if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG) +#if defined SK_QUAD_STROKE_APPROXIMATION && defined SK_DEBUG extern bool gDebugStrokerErrorSet; extern SkScalar gDebugStrokerError; - extern int gMaxRecursion[]; #endif @@ -43,9 +37,6 @@ public: SkPaint::Join getJoin() const { return (SkPaint::Join)fJoin; } void setJoin(SkPaint::Join); -#if QUAD_STROKE_APPROXIMATION - void setError(SkScalar); -#endif void setMiterLimit(SkScalar); void setWidth(SkScalar); @@ -76,9 +67,6 @@ public: //////////////////////////////////////////////////////////////// private: -#if QUAD_STROKE_APPROXIMATION - SkScalar fError; -#endif SkScalar fWidth, fMiterLimit; SkScalar fResScale; uint8_t fCap, fJoin; diff --git a/src/core/SkStrokeRec.cpp b/src/core/SkStrokeRec.cpp index c25a652a88..2ca7efd703 100644 --- a/src/core/SkStrokeRec.cpp +++ b/src/core/SkStrokeRec.cpp @@ -100,6 +100,12 @@ void SkStrokeRec::setStrokeStyle(SkScalar width, bool strokeAndFill) { #include "SkStroke.h" +#if defined SK_QUAD_STROKE_APPROXIMATION && defined SK_DEBUG + // enables tweaking these values at runtime from SampleApp + bool gDebugStrokerErrorSet = false; + SkScalar gDebugStrokerError; +#endif + bool SkStrokeRec::applyToPath(SkPath* dst, const SkPath& src) const { if (fWidth <= 0) { // hairline or fill return false; @@ -111,9 +117,10 @@ bool SkStrokeRec::applyToPath(SkPath* dst, const SkPath& src) const { stroker.setMiterLimit(fMiterLimit); stroker.setWidth(fWidth); stroker.setDoFill(fStrokeAndFill); +#if defined SK_QUAD_STROKE_APPROXIMATION && defined SK_DEBUG + stroker.setResScale(gDebugStrokerErrorSet ? gDebugStrokerError : fResScale); +#else stroker.setResScale(fResScale); -#if QUAD_STROKE_APPROXIMATION - stroker.setError(1); #endif stroker.strokePath(src, dst); return true; |