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
diff --git a/gyp/common.gypi b/gyp/common.gypi
index ce3fc26..9e48c17 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 4fd6e74..a3863a8 100644
--- a/samplecode/SampleQuadStroker.cpp
+++ b/samplecode/SampleQuadStroker.cpp
@@ -85,6 +85,10 @@
     bool fEnabled;
 };
 
+struct CircleTypeButton : public StrokeTypeButton {
+    bool fFill;
+};
+
 class QuadStrokerView : public SampleView {
     enum {
         SKELETON_COLOR = 0xFF0000FF,
@@ -92,9 +96,10 @@
     };
 
     enum {
-        kCount = 10
+        kCount = 15
     };
     SkPoint fPts[kCount];
+    SkRect fWeightControl;
     SkRect fErrorControl;
     SkRect fWidthControl;
     SkRect fBounds;
@@ -103,11 +108,14 @@
     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 @@
     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[9].set(250, 800);
+        fPts[10].set(200, 200); // rrect
+        fPts[11].set(400, 400);
+
+        fPts[12].set(250, 250);  // oval
+        fPts[13].set(450, 450);
+
         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 @@
     }
 
     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 @@
         }
     }
 
-    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 @@
         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 @@
 
         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 @@
         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 @@
     }
 
     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 @@
             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 @@
             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 @@
             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 @@
             }
         }
         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 @@
                 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 @@
             }
         }
         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 @@
             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 @@
             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 db76caf..1a44a3a 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 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-#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 @@
 
 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 @@
     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 @@
     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 @@
     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 @@
     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 @@
                        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 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-#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 @@
     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 @@
     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 @@
 }
 #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::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 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 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 @@
     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 @@
     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 @@
 }
 #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 @@
     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 @@
     }
 }
 
+// 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 @@
         // 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 @@
             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 @@
 // 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 @@
     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 @@
                     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 @@
     }
     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 @@
         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 @@
         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 @@
                 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 @@
     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 @@
 
 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 @@
     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 @@
     }
 
     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 @@
                 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 @@
                     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 a5446a4..0574f38 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 @@
     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 @@
     ////////////////////////////////////////////////////////////////
 
 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 c25a652..2ca7efd 100644
--- a/src/core/SkStrokeRec.cpp
+++ b/src/core/SkStrokeRec.cpp
@@ -100,6 +100,12 @@
 
 #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 @@
     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;