toy to play with antialias raytracing

Here's the ray tracer I wrote.
I've commented out the calls to experimental
stroking -- enough of it remains working
to help visualize what you've been talking
to me about.

R=reed@google.com
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2014993002

Review-Url: https://codereview.chromium.org/2014993002
diff --git a/gyp/SampleApp.gyp b/gyp/SampleApp.gyp
index a9e312b..44e5904 100644
--- a/gyp/SampleApp.gyp
+++ b/gyp/SampleApp.gyp
@@ -16,6 +16,7 @@
         '../src/image',
         '../src/images',
         '../src/lazy',
+        '../src/pathops',
         '../gm',       # needed to pull gm.h
         '../samplecode', # To pull SampleApp.h and SampleCode.h
         '../tools/debugger',
@@ -35,6 +36,7 @@
         '../samplecode/PerlinPatch.cpp',
         '../samplecode/Sample2PtRadial.cpp',
         '../samplecode/SampleAAClip.cpp',
+        '../samplecode/SampleAAGeometry.cpp',
         '../samplecode/SampleAARects.cpp',
         '../samplecode/SampleAARectModes.cpp',
         '../samplecode/SampleAll.cpp',
diff --git a/samplecode/SampleAAGeometry.cpp b/samplecode/SampleAAGeometry.cpp
new file mode 100644
index 0000000..7d87303
--- /dev/null
+++ b/samplecode/SampleAAGeometry.cpp
@@ -0,0 +1,1869 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SampleCode.h"
+#include "SkCanvas.h"
+#include "SkGeometry.h"
+#include "SkIntersections.h"
+#include "SkOpEdgeBuilder.h"
+// #include "SkPathOpsSimplifyAA.h"
+// #include "SkPathStroker.h"
+#include "SkView.h"
+
+#if 0
+void SkStrokeSegment::dump() const {
+    SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
+    if (SkPath::kQuad_Verb == fVerb) {
+        SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
+    }
+    SkDebugf("}}");
+#ifdef SK_DEBUG
+    SkDebugf(" id=%d", fDebugID);
+#endif
+    SkDebugf("\n");
+}
+
+void SkStrokeSegment::dumpAll() const {
+    const SkStrokeSegment* segment = this;
+    while (segment) {
+        segment->dump();
+        segment = segment->fNext;
+    }
+}
+
+void SkStrokeTriple::dump() const {
+    SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}", fPts[0].fX, fPts[0].fY, fPts[1].fX, fPts[1].fY);
+    if (SkPath::kQuad_Verb <= fVerb) {
+        SkDebugf(", {%1.9g,%1.9g}", fPts[2].fX, fPts[2].fY);
+    }
+    if (SkPath::kCubic_Verb == fVerb) {
+        SkDebugf(", {%1.9g,%1.9g}", fPts[3].fX, fPts[3].fY);
+    } else if (SkPath::kConic_Verb == fVerb) {
+        SkDebugf(", %1.9g", weight());
+    }
+    SkDebugf("}}");
+#ifdef SK_DEBUG
+    SkDebugf(" triple id=%d", fDebugID);
+#endif
+    SkDebugf("\ninner:\n");
+    fInner->dumpAll();
+    SkDebugf("outer:\n");
+    fOuter->dumpAll();
+    SkDebugf("join:\n");
+    fJoin->dumpAll();
+}
+
+void SkStrokeTriple::dumpAll() const {
+    const SkStrokeTriple* triple = this;
+    while (triple) {
+        triple->dump();
+        triple = triple->fNext;
+    }
+}
+
+void SkStrokeContour::dump() const {
+#ifdef SK_DEBUG
+    SkDebugf("id=%d ", fDebugID);
+#endif
+    SkDebugf("head:\n");
+    fHead->dumpAll();
+    SkDebugf("head cap:\n");
+    fHeadCap->dumpAll();
+    SkDebugf("tail cap:\n");
+    fTailCap->dumpAll();
+}
+
+void SkStrokeContour::dumpAll() const {
+    const SkStrokeContour* contour = this;
+    while (contour) {
+        contour->dump();
+        contour = contour->fNext;
+    }
+}
+#endif
+
+SkScalar gCurveDistance = 10;
+
+#if 0  // unused
+static SkPath::Verb get_path_verb(int index, const SkPath& path) {
+    if (index < 0) {
+        return SkPath::kMove_Verb;
+    }
+    SkPoint pts[4];
+    SkPath::Verb verb;
+    SkPath::Iter iter(path, true);
+    int counter = -1;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        if (++counter < index) {
+            continue;
+        }
+        return verb;
+    }
+    SkASSERT(0);
+    return SkPath::kMove_Verb;
+}
+#endif
+
+static SkScalar get_path_weight(int index, const SkPath& path) {
+    SkPoint pts[4];
+    SkPath::Verb verb;
+    SkPath::Iter iter(path, true);
+    int counter = -1;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        if (++counter < index) {
+            continue;
+        }
+        return verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
+    }
+    SkASSERT(0);
+    return 0;
+}
+
+static void set_path_pt(int index, const SkPoint& pt, SkPath* path) {
+    SkPath result;
+    SkPoint pts[4];
+    SkPath::Verb verb;
+    SkPath::RawIter iter(*path);
+    int startIndex = 0;
+    int endIndex = 0;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                endIndex += 1;
+                break;
+            case SkPath::kLine_Verb:
+                endIndex += 1;
+                break;
+            case SkPath::kQuad_Verb:
+            case SkPath::kConic_Verb:
+                endIndex += 2;
+                break;
+            case SkPath::kCubic_Verb:
+                endIndex += 3;
+                break;
+            case SkPath::kClose_Verb:
+                break;
+            case SkPath::kDone_Verb:
+                break;
+            default:
+                SkASSERT(0);
+        }
+        if (startIndex <= index && index < endIndex) {
+            pts[index - startIndex] = pt;
+            index = -1;
+        }
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                result.moveTo(pts[0]);
+                break;
+            case SkPath::kLine_Verb:
+                result.lineTo(pts[1]);
+                startIndex += 1;
+                break;
+            case SkPath::kQuad_Verb:
+                result.quadTo(pts[1], pts[2]);
+                startIndex += 2;
+                break;
+            case SkPath::kConic_Verb:
+                result.conicTo(pts[1], pts[2], iter.conicWeight());
+                startIndex += 2;
+                break;
+            case SkPath::kCubic_Verb:
+                result.cubicTo(pts[1], pts[2], pts[3]);
+                startIndex += 3;
+                break;
+            case SkPath::kClose_Verb:
+                result.close();
+                startIndex += 1;
+                break;
+            case SkPath::kDone_Verb:
+                break;
+            default:
+                SkASSERT(0);
+        }
+    }
+#if 0
+    SkDebugf("\n\noriginal\n");
+    path->dump();
+    SkDebugf("\nedited\n");
+    result.dump();
+#endif
+    *path = result;
+}
+
+static void add_path_segment(int index, SkPath* path) {
+    SkPath result;
+    SkPoint pts[4];
+    SkPoint firstPt = { 0, 0 };  // init to avoid warning
+    SkPoint lastPt = { 0, 0 };  // init to avoid warning
+    SkPath::Verb verb;
+    SkPath::RawIter iter(*path);
+    int counter = -1;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        SkScalar weight  SK_INIT_TO_AVOID_WARNING;
+        if (++counter == index) {
+            switch (verb) {
+                case SkPath::kLine_Verb:
+                    result.lineTo((pts[0].fX + pts[1].fX) / 2, (pts[0].fY + pts[1].fY) / 2);
+                    break;
+                case SkPath::kQuad_Verb: {
+                    SkPoint chop[5];
+                    SkChopQuadAtHalf(pts, chop);
+                    result.quadTo(chop[1], chop[2]);
+                    pts[1] = chop[3];
+                    } break;
+                case SkPath::kConic_Verb: {
+                    SkConic chop[2];
+                    SkConic conic;
+                    conic.set(pts, iter.conicWeight());
+                    conic.chopAt(0.5f, chop);
+                    result.conicTo(chop[0].fPts[1], chop[0].fPts[2], chop[0].fW);
+                    pts[1] = chop[1].fPts[1];
+                    weight = chop[1].fW;
+                    } break;
+                case SkPath::kCubic_Verb: {
+                    SkPoint chop[7];
+                    SkChopCubicAtHalf(pts, chop);
+                    result.cubicTo(chop[1], chop[2], chop[3]);
+                    pts[1] = chop[4];
+                    pts[2] = chop[5];
+                    } break;
+                case SkPath::kClose_Verb: {
+                    result.lineTo((lastPt.fX + firstPt.fX) / 2, (lastPt.fY + firstPt.fY) / 2);
+                    } break;
+                default:
+                    SkASSERT(0);
+            }
+        } else if (verb == SkPath::kConic_Verb) {
+            weight = iter.conicWeight();
+        }
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                result.moveTo(firstPt = pts[0]);
+                break;
+            case SkPath::kLine_Verb:
+                result.lineTo(lastPt = pts[1]);
+                break;
+            case SkPath::kQuad_Verb:
+                result.quadTo(pts[1], lastPt = pts[2]);
+                break;
+            case SkPath::kConic_Verb:
+                result.conicTo(pts[1], lastPt = pts[2], weight);
+                break;
+            case SkPath::kCubic_Verb:
+                result.cubicTo(pts[1], pts[2], lastPt = pts[3]);
+                break;
+            case SkPath::kClose_Verb:
+                result.close();
+                break;
+            case SkPath::kDone_Verb:
+                break;
+            default:
+                SkASSERT(0);
+        }
+    }
+    *path = result;
+}
+
+static void delete_path_segment(int index, SkPath* path) {
+    SkPath result;
+    SkPoint pts[4];
+    SkPath::Verb verb;
+    SkPath::RawIter iter(*path);
+    int counter = -1;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        if (++counter == index) {
+            continue;
+        }
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                result.moveTo(pts[0]);
+                break;
+            case SkPath::kLine_Verb:
+                result.lineTo(pts[1]);
+                break;
+            case SkPath::kQuad_Verb:
+                result.quadTo(pts[1], pts[2]);
+                break;
+            case SkPath::kConic_Verb:
+                result.conicTo(pts[1], pts[2], iter.conicWeight());
+                break;
+            case SkPath::kCubic_Verb:
+                result.cubicTo(pts[1], pts[2], pts[3]);
+                break;
+            case SkPath::kClose_Verb:
+                result.close();
+                break;
+            case SkPath::kDone_Verb:
+                break;
+            default:
+                SkASSERT(0);
+        }
+    }
+    *path = result;
+}
+
+static void set_path_weight(int index, SkScalar w, SkPath* path) {
+    SkPath result;
+    SkPoint pts[4];
+    SkPath::Verb verb;
+    SkPath::Iter iter(*path, true);
+    int counter = -1;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        ++counter;
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                result.moveTo(pts[0]);
+                break;
+            case SkPath::kLine_Verb:
+                result.lineTo(pts[1]);
+                break;
+            case SkPath::kQuad_Verb:
+                result.quadTo(pts[1], pts[2]);
+                break;
+            case SkPath::kConic_Verb:
+                result.conicTo(pts[1], pts[2], counter == index ? w : iter.conicWeight());
+                break;
+            case SkPath::kCubic_Verb:
+                result.cubicTo(pts[1], pts[2], pts[3]);
+                break;
+            case SkPath::kClose_Verb:
+                result.close();
+                break;
+            case SkPath::kDone_Verb:
+                break;
+            default:
+                SkASSERT(0);
+        }
+    }
+    *path = result;
+}
+
+static void set_path_verb(int index, SkPath::Verb v, SkPath* path, SkScalar w) {
+    SkASSERT(SkPath::kLine_Verb <= v && v <= SkPath::kCubic_Verb);
+    SkPath result;
+    SkPoint pts[4];
+    SkPath::Verb verb;
+    SkPath::Iter iter(*path, true);
+    int counter = -1;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        SkScalar weight = verb == SkPath::kConic_Verb ? iter.conicWeight() : 1;
+        if (++counter == index && v != verb) {
+            SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb);
+            switch (verb) {
+                case SkPath::kLine_Verb:
+                    switch (v) {
+                        case SkPath::kConic_Verb:
+                            weight = w;
+                        case SkPath::kQuad_Verb:
+                            pts[2] = pts[1];
+                            pts[1].fX = (pts[0].fX + pts[2].fX) / 2;
+                            pts[1].fY = (pts[0].fY + pts[2].fY) / 2;
+                            break;
+                        case SkPath::kCubic_Verb:
+                            pts[3] = pts[1];
+                            pts[1].fX = (pts[0].fX * 2 + pts[3].fX) / 3;
+                            pts[1].fY = (pts[0].fY * 2 + pts[3].fY) / 3;
+                            pts[2].fX = (pts[0].fX + pts[3].fX * 2) / 3;
+                            pts[2].fY = (pts[0].fY + pts[3].fY * 2) / 3;
+                            break;
+                         default:
+                            SkASSERT(0);
+                            break;
+                    }
+                    break;
+                case SkPath::kQuad_Verb:
+                case SkPath::kConic_Verb:
+                    switch (v) {
+                        case SkPath::kLine_Verb:
+                            pts[1] = pts[2];
+                            break;
+                        case SkPath::kConic_Verb:
+                            weight = w;
+                        case SkPath::kQuad_Verb:
+                            break;
+                        case SkPath::kCubic_Verb: {
+                            SkDQuad dQuad;
+                            dQuad.set(pts);
+                            SkDCubic dCubic = dQuad.debugToCubic();
+                            pts[3] = pts[2];
+                            pts[1] = dCubic[1].asSkPoint();
+                            pts[2] = dCubic[2].asSkPoint();
+                            } break;
+                         default:
+                            SkASSERT(0);
+                            break;
+                    }
+                    break;
+                case SkPath::kCubic_Verb:
+                    switch (v) {
+                        case SkPath::kLine_Verb:
+                            pts[1] = pts[3];
+                            break;
+                        case SkPath::kConic_Verb:
+                            weight = w;
+                        case SkPath::kQuad_Verb: {
+                            SkDCubic dCubic;
+                            dCubic.set(pts);
+                            SkDQuad dQuad = dCubic.toQuad();
+                            pts[1] = dQuad[1].asSkPoint();
+                            pts[2] = pts[3];
+                            } break;
+                        default:
+                            SkASSERT(0);
+                            break;
+                    }
+                    break;
+                default:
+                    SkASSERT(0);
+                    break;
+            }
+            verb = v;
+        }
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                result.moveTo(pts[0]);
+                break;
+            case SkPath::kLine_Verb:
+                result.lineTo(pts[1]);
+                break;
+            case SkPath::kQuad_Verb:
+                result.quadTo(pts[1], pts[2]);
+                break;
+            case SkPath::kConic_Verb:
+                result.conicTo(pts[1], pts[2], weight);
+                break;
+            case SkPath::kCubic_Verb:
+                result.cubicTo(pts[1], pts[2], pts[3]);
+                break;
+            case SkPath::kClose_Verb:
+                result.close();
+                break;
+            default:
+                SkASSERT(0);
+                break;
+        }
+    }
+    *path = result;
+}
+
+static void add_to_map(SkScalar coverage, int x, int y, uint8_t* distanceMap, int w, int h) {
+    int byteCoverage = (int) (coverage * 256);
+    if (byteCoverage < 0) {
+        byteCoverage = 0;
+    } else if (byteCoverage > 255) {
+        byteCoverage = 255;
+    }
+    SkASSERT(x < w);
+    SkASSERT(y < h);
+    distanceMap[y * w + x] = SkTMax(distanceMap[y * w + x], (uint8_t) byteCoverage);
+}
+
+static void filter_coverage(const uint8_t* map, int len, uint8_t min, uint8_t max,
+        uint8_t* filter) {
+    for (int index = 0; index < len; ++index) {
+        uint8_t in = map[index];
+        filter[index] = in < min ? 0 : max < in ? 0 : in;
+    }
+}
+
+static void construct_path(SkPath& path) {
+    path.reset();
+    path.moveTo(442, 101.5f);
+    path.quadTo(413.5f, 691, 772, 514);
+    path.lineTo(346, 721.5f);
+    path.lineTo(154, 209);
+    path.lineTo(442, 101.5f);
+    path.close();
+}
+
+struct ButtonPaints {
+    static const int kMaxStateCount = 3;
+    SkPaint fDisabled;
+    SkPaint fStates[kMaxStateCount];
+    SkPaint fLabel;
+
+    ButtonPaints() {
+        fStates[0].setAntiAlias(true);
+        fStates[0].setStyle(SkPaint::kStroke_Style);
+        fStates[0].setColor(0xFF3F0000);
+        fStates[1] = fStates[0];
+        fStates[1].setStrokeWidth(3);
+        fStates[2] = fStates[1];
+        fStates[2].setColor(0xFFcf0000);
+        fLabel.setAntiAlias(true);
+        fLabel.setTextSize(25.0f);
+        fLabel.setTextAlign(SkPaint::kCenter_Align);
+        fLabel.setStyle(SkPaint::kFill_Style);
+    }
+};
+
+struct Button {
+    SkRect fBounds;
+    int fStateCount;
+    int fState;
+    char fLabel;
+    bool fVisible;
+
+    Button(char label) {
+        fStateCount = 2;
+        fState = 0;
+        fLabel = label;
+        fVisible = false;
+    }
+
+    Button(char label, int stateCount) {
+        SkASSERT(stateCount <= ButtonPaints::kMaxStateCount);
+        fStateCount = stateCount;
+        fState = 0;
+        fLabel = label;
+        fVisible = false;
+    }
+
+    bool contains(const SkRect& rect) {
+        return fVisible && fBounds.contains(rect);
+    }
+
+    bool enabled() {
+        return SkToBool(fState);
+    }
+
+    void draw(SkCanvas* canvas, const ButtonPaints& paints) {
+        if (!fVisible) {
+            return;
+        }
+        canvas->drawRect(fBounds, paints.fStates[fState]);
+        canvas->drawText(&fLabel, 1, fBounds.centerX(), fBounds.fBottom - 5, paints.fLabel);
+    }
+
+    void toggle() {
+        if (++fState == fStateCount) {
+            fState = 0;
+        }
+    }
+
+    void setEnabled(bool enabled) {
+        fState = (int) enabled;
+    }
+};
+
+struct ControlPaints {
+    SkPaint fOutline;
+    SkPaint fIndicator;
+    SkPaint fFill;
+    SkPaint fLabel;
+    SkPaint fValue;
+
+    ControlPaints() {
+        fOutline.setAntiAlias(true);
+        fOutline.setStyle(SkPaint::kStroke_Style);
+        fIndicator = fOutline;
+        fIndicator.setColor(SK_ColorRED);
+        fFill.setAntiAlias(true);
+        fFill.setColor(0x7fff0000);
+        fLabel.setAntiAlias(true);
+        fLabel.setTextSize(13.0f);
+        fValue.setAntiAlias(true);
+        fValue.setTextSize(11.0f);
+    }
+};
+
+struct UniControl {
+    SkString fName;
+    SkRect fBounds;
+    SkScalar fMin;
+    SkScalar fMax;
+    SkScalar fValLo;
+    SkScalar fYLo;
+    bool fVisible;
+
+    UniControl(const char* name, SkScalar min, SkScalar max) {
+        fName = name;
+        fValLo =  fMin = min;
+        fMax = max;
+        fVisible = false;
+
+    }
+
+    virtual ~UniControl() {}
+
+    bool contains(const SkRect& rect) {
+        return fVisible && fBounds.contains(rect);
+    }
+
+    virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
+        if (!fVisible) {
+            return;
+        }
+        canvas->drawRect(fBounds, paints.fOutline);
+        fYLo = fBounds.fTop + (fValLo - fMin) * fBounds.height() / (fMax - fMin);
+        canvas->drawLine(fBounds.fLeft - 5, fYLo, fBounds.fRight + 5, fYLo, paints.fIndicator);
+        SkString label;
+        label.printf("%0.3g", fValLo);
+        canvas->drawText(label.c_str(), label.size(), fBounds.fLeft + 5, fYLo - 5, paints.fValue);
+        canvas->drawText(fName.c_str(), fName.size(), fBounds.fLeft, fBounds.bottom() + 11,
+                paints.fLabel);
+    }
+};
+
+struct BiControl : public UniControl {
+    SkScalar fValHi;
+
+    BiControl(const char* name, SkScalar min, SkScalar max) 
+        : UniControl(name, min, max)
+        ,  fValHi(fMax) {
+    }
+    
+    virtual ~BiControl() {}
+
+    virtual void draw(SkCanvas* canvas, const ControlPaints& paints) {
+        UniControl::draw(canvas, paints);
+        if (!fVisible || fValHi == fValLo) {
+            return;
+        }
+        SkScalar yPos = fBounds.fTop + (fValHi - fMin) * fBounds.height() / (fMax - fMin);
+        canvas->drawLine(fBounds.fLeft - 5, yPos, fBounds.fRight + 5, yPos, paints.fIndicator);
+        SkString label;
+        label.printf("%0.3g", fValHi);
+        if (yPos < fYLo + 10) {
+            yPos = fYLo + 10;
+        }
+        canvas->drawText(label.c_str(), label.size(), fBounds.fLeft + 5, yPos - 5, paints.fValue);
+        SkRect fill = { fBounds.fLeft, fYLo, fBounds.fRight, yPos };
+        canvas->drawRect(fill, paints.fFill);
+    }
+};
+
+
+class MyClick : public SampleView::Click {
+public:
+    enum ClickType {
+        kInvalidType = -1,
+        kPtType,
+        kVerbType,
+        kControlType,
+        kPathType,
+    } fType;
+
+    enum ControlType {
+        kInvalidControl = -1,
+        kFirstControl,
+        kFilterControl = kFirstControl,
+        kResControl,
+        kWeightControl,
+        kWidthControl,
+        kLastControl = kWidthControl,
+        kFirstButton,
+        kCubicButton = kFirstButton,
+        kConicButton,
+        kQuadButton,
+        kLineButton,
+        kLastVerbButton = kLineButton,
+        kAddButton,
+        kDeleteButton,
+        kInOutButton,
+        kFillButton,
+        kSkeletonButton,
+        kFilterButton,
+        kBisectButton,
+        kJoinButton,
+        kLastButton = kJoinButton,
+        kPathMove,
+    } fControl;
+
+    SkPath::Verb fVerb;
+    SkScalar fWeight;
+
+    MyClick(SkView* target, ClickType type, ControlType control)
+        : Click(target)
+        , fType(type) 
+        , fControl(control)
+        , fVerb((SkPath::Verb) -1)
+        , fWeight(1) {
+    }
+
+    MyClick(SkView* target, ClickType type, int index)
+        : Click(target)
+        , fType(type) 
+        , fControl((ControlType) index)
+        , fVerb((SkPath::Verb) -1)
+        , fWeight(1) {
+    }
+
+    MyClick(SkView* target, ClickType type, int index, SkPath::Verb verb, SkScalar weight)
+        : Click(target)
+        , fType(type) 
+        , fControl((ControlType) index)
+        , fVerb(verb)
+        , fWeight(weight) {
+    }
+
+    bool isButton() {
+        return kFirstButton <= fControl && fControl <= kLastButton;
+    }
+
+    int ptHit() const {
+        SkASSERT(fType == kPtType);
+        return (int) fControl;
+    }
+
+    int verbHit() const {
+        SkASSERT(fType == kVerbType);
+        return (int) fControl;
+    }
+};
+
+enum {
+    kControlCount = MyClick::kLastControl - MyClick::kFirstControl + 1,
+};
+
+static struct ControlPair {
+    UniControl* fControl;
+    MyClick::ControlType fControlType;
+} kControlList[kControlCount];
+
+enum {
+    kButtonCount = MyClick::kLastButton - MyClick::kFirstButton + 1,
+    kVerbCount = MyClick::kLastVerbButton - MyClick::kFirstButton + 1,
+};
+
+static struct ButtonPair {
+    Button* fButton;
+    MyClick::ControlType fButtonType;
+} kButtonList[kButtonCount];
+
+static void enable_verb_button(MyClick::ControlType type) {
+    for (int index = 0; index < kButtonCount; ++index) {
+        MyClick::ControlType testType = kButtonList[index].fButtonType;
+        if (MyClick::kFirstButton <= testType && testType <= MyClick::kLastVerbButton) {
+            Button* button = kButtonList[index].fButton;
+            button->setEnabled(testType == type);
+        }
+    }
+}
+
+struct Stroke;
+
+struct Active {
+    Active* fNext;
+    Stroke* fParent;
+    SkScalar fStart;
+    SkScalar fEnd;
+
+    void reset() {
+        fNext = NULL;
+        fStart = 0;
+        fEnd = 1;
+    }
+};
+
+struct Stroke {
+    SkPath fPath;
+    Active fActive;
+    bool fInner;
+
+    void reset() {
+        fPath.reset();
+        fActive.reset();
+    }
+};
+
+struct PathUndo {
+    SkPath fPath;
+    PathUndo* fNext;
+};
+
+class AAGeometryView : public SampleView {
+    SkPaint fActivePaint;
+    SkPaint fComplexPaint;
+    SkPaint fCoveragePaint;
+    SkPaint fLegendLeftPaint;
+    SkPaint fLegendRightPaint;
+    SkPaint fPointPaint;
+    SkPaint fSkeletonPaint;
+    SkPaint fLightSkeletonPaint;
+    SkPath fPath;
+    ControlPaints fControlPaints;
+    UniControl fResControl;
+    UniControl fWeightControl;
+    UniControl fWidthControl;
+    BiControl fFilterControl;
+    ButtonPaints fButtonPaints;
+    Button fCubicButton;
+    Button fConicButton;
+    Button fQuadButton;
+    Button fLineButton;
+    Button fAddButton;
+    Button fDeleteButton;
+    Button fFillButton;
+    Button fSkeletonButton;
+    Button fFilterButton;
+    Button fBisectButton;
+    Button fJoinButton;
+    Button fInOutButton;
+    SkTArray<Stroke> fStrokes;
+    PathUndo* fUndo;
+    int fActivePt;
+    int fActiveVerb;
+    bool fHandlePathMove;
+    bool fShowLegend;
+    bool fHideAll;
+    const int kHitToleranace = 5;
+
+public:
+
+    AAGeometryView() 
+        : fResControl("error", 0, 10)
+        , fWeightControl("weight", 0, 5)
+        , fWidthControl("width", FLT_EPSILON, 100)
+        , fFilterControl("filter", 0, 255)
+        , fCubicButton('C')
+        , fConicButton('K')
+        , fQuadButton('Q')
+        , fLineButton('L')
+        , fAddButton('+')
+        , fDeleteButton('x')
+        , fFillButton('p')
+        , fSkeletonButton('s')
+        , fFilterButton('f', 3)
+        , fBisectButton('b')
+        , fJoinButton('j')
+        , fInOutButton('|')
+        , fUndo(NULL)
+        , fActivePt(-1)
+        , fActiveVerb(-1)
+        , fHandlePathMove(true)
+        , fShowLegend(false)
+        , fHideAll(false)
+    {
+        fCoveragePaint.setAntiAlias(true);
+        fCoveragePaint.setColor(SK_ColorBLUE);
+        SkPaint strokePaint;
+        strokePaint.setAntiAlias(true);
+        strokePaint.setStyle(SkPaint::kStroke_Style);
+        fPointPaint = strokePaint;
+        fPointPaint.setColor(0x99ee3300);
+        fSkeletonPaint = strokePaint;
+        fSkeletonPaint.setColor(SK_ColorRED);
+        fLightSkeletonPaint = fSkeletonPaint;
+        fLightSkeletonPaint.setColor(0xFFFF7f7f);
+        fActivePaint = strokePaint;
+        fActivePaint.setColor(0x99ee3300);
+        fActivePaint.setStrokeWidth(5);
+        fComplexPaint = fActivePaint;
+        fComplexPaint.setColor(SK_ColorBLUE);
+        fLegendLeftPaint.setAntiAlias(true);
+        fLegendLeftPaint.setTextSize(13);
+        fLegendRightPaint = fLegendLeftPaint;
+        fLegendRightPaint.setTextAlign(SkPaint::kRight_Align);
+        construct_path(fPath);
+        fFillButton.fVisible = fSkeletonButton.fVisible = fFilterButton.fVisible
+                = fBisectButton.fVisible = fJoinButton.fVisible = fInOutButton.fVisible = true;
+        fSkeletonButton.setEnabled(true);
+        fInOutButton.setEnabled(true);
+        fJoinButton.setEnabled(true);
+        fFilterControl.fValLo = 120;
+        fFilterControl.fValHi = 141;
+        fFilterControl.fVisible = fFilterButton.fState == 2;
+        fResControl.fValLo = 5;
+        fResControl.fVisible = true;
+        fWidthControl.fValLo = 50;
+        fWidthControl.fVisible = true;
+        init_controlList();
+        init_buttonList();
+    }
+
+    bool constructPath() {
+        construct_path(fPath);
+        this->inval(NULL);
+        return true;
+    }
+
+    void savePath(Click::State state) {
+        if (state != Click::kDown_State) {
+            return;
+        }
+        if (fUndo && fUndo->fPath == fPath) {
+            return;
+        }
+        PathUndo* undo = new PathUndo;
+        undo->fPath = fPath;
+        undo->fNext = fUndo;
+        fUndo = undo;
+    }
+
+    bool undo() {
+        if (!fUndo) {
+            return false;
+        }
+        fPath = fUndo->fPath;
+        validatePath();
+        PathUndo* next = fUndo->fNext;
+        delete fUndo;
+        fUndo = next;
+        this->inval(NULL);
+        return true;
+    }
+
+    void validatePath() {
+        PathUndo* undo = fUndo;
+        int match = 0;
+        while (undo) {
+            match += fPath == undo->fPath;
+            undo = undo->fNext;
+        }
+    }
+
+    void set_controlList(int index, UniControl* control, MyClick::ControlType type) {
+        kControlList[index].fControl = control;
+        kControlList[index].fControlType = type;
+    }
+
+    #define SET_CONTROL(Name) set_controlList(index++, &f##Name##Control, \
+        MyClick::k##Name##Control)
+
+    bool hideAll() {
+        fHideAll ^= true;
+        this->inval(NULL);
+        return true;
+    }
+
+    void init_controlList() {
+        int index = 0;
+        SET_CONTROL(Width);
+        SET_CONTROL(Res);
+        SET_CONTROL(Filter);
+        SET_CONTROL(Weight);
+    };
+    
+    #undef SET_CONTROL
+
+    void set_buttonList(int index, Button* button, MyClick::ControlType type) {
+        kButtonList[index].fButton = button;
+        kButtonList[index].fButtonType = type;
+    }
+
+    #define SET_BUTTON(Name) set_buttonList(index++, &f##Name##Button, \
+            MyClick::k##Name##Button)
+
+    void init_buttonList() {
+        int index = 0;
+        SET_BUTTON(Fill);
+        SET_BUTTON(Skeleton);
+        SET_BUTTON(Filter);
+        SET_BUTTON(Bisect);
+        SET_BUTTON(Join);
+        SET_BUTTON(InOut);
+        SET_BUTTON(Cubic);
+        SET_BUTTON(Conic);
+        SET_BUTTON(Quad);
+        SET_BUTTON(Line);
+        SET_BUTTON(Add);
+        SET_BUTTON(Delete);
+    }
+
+    #undef SET_BUTTON
+
+    // overrides from SkEventSink
+    bool onQuery(SkEvent* evt) override;
+    
+    void onSizeChange() override {
+        setControlButtonsPos();
+        this->INHERITED::onSizeChange();
+    }
+
+    bool pathDump() {
+        fPath.dump();
+        return true;
+    }
+
+    bool scaleDown() {
+        SkMatrix matrix;
+        SkRect bounds = fPath.getBounds();
+        matrix.setScale(1.f / 1.5f, 1.f / 1.5f, bounds.centerX(), bounds.centerY());
+        fPath.transform(matrix);
+        validatePath();
+        this->inval(NULL);
+        return true;
+    }
+
+    bool scaleToFit() {
+        SkMatrix matrix;
+        SkRect bounds = fPath.getBounds();
+        SkScalar scale = SkTMin(this->width() / bounds.width(), this->height() / bounds.height())
+                * 0.8f;
+        matrix.setScale(scale, scale, bounds.centerX(), bounds.centerY());
+        fPath.transform(matrix);
+        bounds = fPath.getBounds();
+        SkScalar offsetX = (this->width() - bounds.width()) / 2 - bounds.fLeft;
+        SkScalar offsetY = (this->height() - bounds.height()) / 2 - bounds.fTop;
+        fPath.offset(offsetX, offsetY);
+        validatePath();
+        this->inval(NULL);
+        return true;
+    }
+
+    bool scaleUp() {
+        SkMatrix matrix;
+        SkRect bounds = fPath.getBounds();
+        matrix.setScale(1.5f, 1.5f, bounds.centerX(), bounds.centerY());
+        fPath.transform(matrix);
+        validatePath();
+        this->inval(NULL);
+        return true;
+    }
+
+    void setControlButtonsPos() {
+        SkScalar widthOffset = this->width() - 100;
+        for (int index = 0; index < kControlCount; ++index) {
+            if (kControlList[index].fControl->fVisible) {
+                kControlList[index].fControl->fBounds.setXYWH(widthOffset, 30, 30, 400);
+                widthOffset -= 50;
+            }
+        }
+        SkScalar buttonOffset = 0;
+        for (int index = 0; index < kButtonCount; ++index) {
+            kButtonList[index].fButton->fBounds.setXYWH(this->width() - 50,
+                    buttonOffset += 50, 30, 30);
+        }
+    }
+
+    bool showLegend() {
+        fShowLegend ^= true;
+        this->inval(NULL);
+        return true;
+    }
+
+    void draw_bisect(SkCanvas* canvas, const SkVector& lastVector, const SkVector& vector,
+                const SkPoint& pt) {
+        SkVector lastV = lastVector;
+        SkScalar lastLen = lastVector.length();
+        SkVector nextV = vector;
+        SkScalar nextLen = vector.length();
+        if (lastLen < nextLen) {
+            lastV.setLength(nextLen);
+        } else {
+            nextV.setLength(lastLen);
+        }
+
+        SkVector bisect = { (lastV.fX + nextV.fX) / 2, (lastV.fY + nextV.fY) / 2 };
+        bisect.setLength(fWidthControl.fValLo * 2);
+        if (fBisectButton.enabled()) {
+            canvas->drawLine(pt.fX, pt.fY, pt.fX + bisect.fX, pt.fY + bisect.fY, fSkeletonPaint);
+        }
+        lastV.setLength(fWidthControl.fValLo);
+        if (fBisectButton.enabled()) {
+            canvas->drawLine(pt.fX, pt.fY, pt.fX - lastV.fY, pt.fY + lastV.fX, fSkeletonPaint);
+        }
+        nextV.setLength(fWidthControl.fValLo);
+        if (fBisectButton.enabled()) {
+            canvas->drawLine(pt.fX, pt.fY, pt.fX + nextV.fY, pt.fY - nextV.fX, fSkeletonPaint);
+        }
+        if (fJoinButton.enabled()) {
+            SkScalar r = fWidthControl.fValLo;
+            SkRect oval = { pt.fX - r, pt.fY - r, pt.fX + r, pt.fY + r};
+            SkScalar startAngle = SkScalarATan2(lastV.fX, -lastV.fY) * 180.f / SK_ScalarPI;
+            SkScalar endAngle = SkScalarATan2(-nextV.fX, nextV.fY) * 180.f / SK_ScalarPI;
+            if (endAngle > startAngle) {
+                canvas->drawArc(oval, startAngle, endAngle - startAngle, false, fSkeletonPaint);
+            } else {
+                canvas->drawArc(oval, startAngle, 360 - (startAngle - endAngle), false,
+                        fSkeletonPaint);
+            }
+        }
+    }
+
+    void draw_bisects(SkCanvas* canvas, bool activeOnly) {
+        SkVector firstVector, lastVector, nextLast, vector;
+        SkPoint pts[4];
+        SkPoint firstPt = { 0, 0 };  // init to avoid warning;
+        SkPath::Verb verb;
+        SkPath::Iter iter(fPath, true);
+        bool foundFirst = false;
+        int counter = -1;
+        while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+            ++counter;
+            if (activeOnly && counter != fActiveVerb && counter - 1 != fActiveVerb
+                    && counter + 1 != fActiveVerb
+                    && (fActiveVerb != 1 || counter != fPath.countVerbs())) {
+                continue;
+            }
+            switch (verb) {
+                case SkPath::kLine_Verb:
+                    nextLast = pts[0] - pts[1];
+                    vector = pts[1] - pts[0];
+                    break;
+                case SkPath::kQuad_Verb: {
+                    nextLast = pts[1] - pts[2];
+                    if (SkScalarNearlyZero(nextLast.length())) {
+                        nextLast = pts[0] - pts[2];
+                    }
+                    vector = pts[1] - pts[0];
+                    if (SkScalarNearlyZero(vector.length())) {
+                        vector = pts[2] - pts[0];
+                    }
+                    if (!fBisectButton.enabled()) {
+                        break;
+                    }
+                    SkScalar t = SkFindQuadMaxCurvature(pts);
+                    if (0 < t && t < 1) {
+                        SkPoint maxPt = SkEvalQuadAt(pts, t);
+                        SkVector tangent = SkEvalQuadTangentAt(pts, t);
+                        tangent.setLength(fWidthControl.fValLo * 2);
+                        canvas->drawLine(maxPt.fX, maxPt.fY,
+                                maxPt.fX + tangent.fY, maxPt.fY - tangent.fX, fSkeletonPaint);
+                    }
+                    } break;
+                case SkPath::kConic_Verb:
+                    nextLast = pts[1] - pts[2];
+                    if (SkScalarNearlyZero(nextLast.length())) {
+                        nextLast = pts[0] - pts[2];
+                    }
+                    vector = pts[1] - pts[0];
+                    if (SkScalarNearlyZero(vector.length())) {
+                        vector = pts[2] - pts[0];
+                    }
+                    if (!fBisectButton.enabled()) {
+                        break;
+                    }
+                    // FIXME : need max curvature or equivalent here
+                    break;
+                case SkPath::kCubic_Verb: {
+                    nextLast = pts[2] - pts[3];
+                    if (SkScalarNearlyZero(nextLast.length())) {
+                        nextLast = pts[1] - pts[3];
+                        if (SkScalarNearlyZero(nextLast.length())) {
+                            nextLast = pts[0] - pts[3];
+                        }
+                    }
+                    vector = pts[0] - pts[1];
+                    if (SkScalarNearlyZero(vector.length())) {
+                        vector = pts[0] - pts[2];
+                        if (SkScalarNearlyZero(vector.length())) {
+                            vector = pts[0] - pts[3];
+                        }
+                    }
+                    if (!fBisectButton.enabled()) {
+                        break;
+                    }
+                    SkScalar tMax[2];
+                    int tMaxCount = SkFindCubicMaxCurvature(pts, tMax);
+                    for (int tIndex = 0; tIndex < tMaxCount; ++tIndex) {
+                        if (0 >= tMax[tIndex] || tMax[tIndex] >= 1) {
+                            continue;
+                        }
+                        SkPoint maxPt;
+                        SkVector tangent;
+                        SkEvalCubicAt(pts, tMax[tIndex], &maxPt, &tangent, NULL);
+                        tangent.setLength(fWidthControl.fValLo * 2);
+                        canvas->drawLine(maxPt.fX, maxPt.fY,
+                                maxPt.fX + tangent.fY, maxPt.fY - tangent.fX, fSkeletonPaint);
+                    }
+                    } break;
+                case SkPath::kClose_Verb:
+                    if (foundFirst) {
+                        draw_bisect(canvas, lastVector, firstVector, firstPt);
+                        foundFirst = false;
+                    }
+                    break;
+                default: 
+                    break;
+            }
+            if (SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb) {
+                if (!foundFirst) {
+                    firstPt = pts[0];
+                    firstVector = vector;
+                    foundFirst = true;
+                } else {
+                    draw_bisect(canvas, lastVector, vector, pts[0]);
+                }
+                lastVector = nextLast;
+            }
+        }
+    }
+
+    void draw_legend(SkCanvas* canvas);
+
+    void draw_segment(SkCanvas* canvas) {
+        SkPoint pts[4];
+        SkPath::Verb verb;
+        SkPath::Iter iter(fPath, true);
+        int counter = -1;
+        while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+            if (++counter < fActiveVerb) {
+                continue;
+            }
+            switch (verb) {
+                case SkPath::kLine_Verb:
+                    canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, fActivePaint);
+                    draw_points(canvas, pts, 2);
+                    break;
+                case SkPath::kQuad_Verb: {
+                    SkPath qPath;
+                    qPath.moveTo(pts[0]);
+                    qPath.quadTo(pts[1], pts[2]);
+                    canvas->drawPath(qPath, fActivePaint);
+                    draw_points(canvas, pts, 3);
+                    } break;
+                case SkPath::kConic_Verb: {
+                    SkPath conicPath;
+                    conicPath.moveTo(pts[0]);
+                    conicPath.conicTo(pts[1], pts[2], iter.conicWeight());
+                    canvas->drawPath(conicPath, fActivePaint);
+                    draw_points(canvas, pts, 3);
+                    } break;
+                case SkPath::kCubic_Verb: {
+                    SkScalar loopT;
+                    bool complex = SkDCubic::ComplexBreak(pts, &loopT);
+                    SkPath cPath;
+                    cPath.moveTo(pts[0]);
+                    cPath.cubicTo(pts[1], pts[2], pts[3]);
+                    canvas->drawPath(cPath, complex ? fComplexPaint : fActivePaint);
+                    draw_points(canvas, pts, 4);
+                    } break;
+                default: 
+                    break;
+            }
+            return;
+        }
+    }
+
+    void draw_points(SkCanvas* canvas, SkPoint* points, int count) {
+        for (int index = 0; index < count; ++index) {
+            canvas->drawCircle(points[index].fX, points[index].fY, 10, fPointPaint);
+        }
+    }
+
+    int hittest_verb(SkPoint pt, SkPath::Verb* verbPtr, SkScalar* weight) {
+        SkIntersections i;
+        SkDLine hHit = {{{pt.fX - kHitToleranace, pt.fY }, {pt.fX + kHitToleranace, pt.fY}}};
+        SkDLine vHit = {{{pt.fX, pt.fY - kHitToleranace }, {pt.fX, pt.fY + kHitToleranace}}};
+        SkPoint pts[4];
+        SkPath::Verb verb;
+        SkPath::Iter iter(fPath, true);
+        int counter = -1;
+        while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+            ++counter;
+            switch (verb) {
+                case SkPath::kLine_Verb: {
+                    SkDLine line;
+                    line.set(pts);
+                    if (i.intersect(line, hHit) || i.intersect(line, vHit)) {
+                        *verbPtr = verb;
+                        *weight = 1;
+                        return counter;
+                    }
+                    } break;
+                case SkPath::kQuad_Verb: {
+                    SkDQuad quad;
+                    quad.set(pts);
+                    if (i.intersect(quad, hHit) || i.intersect(quad, vHit)) {
+                        *verbPtr = verb;
+                        *weight = 1;
+                        return counter;
+                    }
+                    } break;
+                case SkPath::kConic_Verb: {
+                    SkDConic conic;
+                    SkScalar w = iter.conicWeight();
+                    conic.set(pts, w);
+                    if (i.intersect(conic, hHit) || i.intersect(conic, vHit)) {
+                        *verbPtr = verb;
+                        *weight = w;
+                        return counter;
+                    }
+                    } break;
+                case SkPath::kCubic_Verb: {
+                    SkDCubic cubic;
+                    cubic.set(pts);
+                    if (i.intersect(cubic, hHit) || i.intersect(cubic, vHit)) {
+                        *verbPtr = verb;
+                        *weight = 1;
+                        return counter;
+                    }
+                    } break;
+                default: 
+                    break;
+            }
+        }
+        return -1;
+    }
+
+    SkScalar pt_to_line(SkPoint s, SkPoint e, int x, int y) {
+        SkScalar radius = fWidthControl.fValLo;
+        SkVector adjOpp = e - s;
+        SkScalar lenSq = adjOpp.lengthSqd();
+        SkPoint rotated = {
+                (y - s.fY) * adjOpp.fY + (x - s.fX) * adjOpp.fX,
+                (y - s.fY) * adjOpp.fX - (x - s.fX) * adjOpp.fY,
+        };
+        if (rotated.fX < 0 || rotated.fX > lenSq) {
+                return -radius;
+        }
+        rotated.fY /= SkScalarSqrt(lenSq);
+        return SkTMax(-radius, SkTMin(radius, rotated.fY));
+    }
+
+    // given a line, compute the interior and exterior gradient coverage
+    bool coverage(SkPoint s, SkPoint e, uint8_t* distanceMap, int w, int h) {
+        SkScalar radius = fWidthControl.fValLo;
+        int minX = SkTMax(0, (int) (SkTMin(s.fX, e.fX) - radius));
+        int minY = SkTMax(0, (int) (SkTMin(s.fY, e.fY) - radius));
+        int maxX = SkTMin(w, (int) (SkTMax(s.fX, e.fX) + radius) + 1);
+        int maxY = SkTMin(h, (int) (SkTMax(s.fY, e.fY) + radius) + 1);
+        for (int y = minY; y < maxY; ++y) {
+            for (int x = minX; x < maxX; ++x) {
+                SkScalar ptToLineDist = pt_to_line(s, e, x, y);
+                if (ptToLineDist > -radius && ptToLineDist < radius) {
+                    SkScalar coverage = ptToLineDist / radius;
+                    add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
+                }
+                SkVector ptToS = { x - s.fX, y - s.fY };
+                SkScalar dist = ptToS.length();
+                if (dist < radius) {
+                    SkScalar coverage = dist / radius;
+                    add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
+                }
+                SkVector ptToE = { x - e.fX, y - e.fY };
+                dist = ptToE.length();
+                if (dist < radius) {
+                    SkScalar coverage = dist / radius;
+                    add_to_map(1 - SkScalarAbs(coverage), x, y, distanceMap, w, h);
+                }
+            }
+        }
+        return true;
+    }
+
+    void quad_coverage(SkPoint pts[3], uint8_t* distanceMap, int w, int h) {
+        SkScalar dist = pts[0].Distance(pts[0], pts[2]);
+        if (dist < gCurveDistance) {
+            (void) coverage(pts[0], pts[2], distanceMap, w, h);
+            return;
+        }
+        SkPoint split[5];
+        SkChopQuadAt(pts, split, 0.5f);
+        quad_coverage(&split[0], distanceMap, w, h);
+        quad_coverage(&split[2], distanceMap, w, h);
+    }
+
+    void conic_coverage(SkPoint pts[3], SkScalar weight, uint8_t* distanceMap, int w, int h) {
+        SkScalar dist = pts[0].Distance(pts[0], pts[2]);
+        if (dist < gCurveDistance) {
+            (void) coverage(pts[0], pts[2], distanceMap, w, h);
+            return;
+        }
+        SkConic split[2];
+        SkConic conic;
+        conic.set(pts, weight);
+        conic.chopAt(0.5f, split);
+        conic_coverage(split[0].fPts, split[0].fW, distanceMap, w, h);
+        conic_coverage(split[1].fPts, split[1].fW, distanceMap, w, h);
+    }
+
+    void cubic_coverage(SkPoint pts[4], uint8_t* distanceMap, int w, int h) {
+        SkScalar dist = pts[0].Distance(pts[0], pts[3]);
+        if (dist < gCurveDistance) {
+            (void) coverage(pts[0], pts[3], distanceMap, w, h);
+            return;
+        }
+        SkPoint split[7];
+        SkChopCubicAt(pts, split, 0.5f);
+        cubic_coverage(&split[0], distanceMap, w, h);
+        cubic_coverage(&split[3], distanceMap, w, h);
+    }
+
+    void path_coverage(const SkPath& path, uint8_t* distanceMap, int w, int h) {
+        memset(distanceMap, 0, sizeof(distanceMap[0]) * w * h);
+        SkPoint pts[4];
+        SkPath::Verb verb;
+        SkPath::Iter iter(path, true);
+        while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+            switch (verb) {
+                case SkPath::kLine_Verb:
+                    (void) coverage(pts[0], pts[1], distanceMap, w, h);
+                    break;
+                case SkPath::kQuad_Verb:
+                    quad_coverage(pts, distanceMap, w, h);
+                    break;
+                case SkPath::kConic_Verb:
+                    conic_coverage(pts, iter.conicWeight(), distanceMap, w, h);
+                    break;
+                case SkPath::kCubic_Verb:
+                    cubic_coverage(pts, distanceMap, w, h);
+                    break;
+                default: 
+                    break;
+            }
+        }
+    }
+
+    static uint8_t* set_up_dist_map(const SkImageInfo& imageInfo, SkBitmap* distMap) {
+        distMap->setInfo(imageInfo);
+        distMap->setIsVolatile(true);
+        SkAssertResult(distMap->tryAllocPixels());
+        SkASSERT((int) distMap->rowBytes() == imageInfo.width());
+        return distMap->getAddr8(0, 0);
+    }
+
+    void path_stroke(int index, SkPath* inner, SkPath* outer) {
+        #if 0
+        SkPathStroker stroker(fPath, fWidthControl.fValLo, 0,
+                SkPaint::kRound_Cap, SkPaint::kRound_Join, fResControl.fValLo);
+        SkPoint pts[4], firstPt, lastPt;
+        SkPath::Verb verb;
+        SkPath::Iter iter(fPath, true);
+        int counter = -1;
+        while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+            ++counter;
+            switch (verb) {
+                case SkPath::kMove_Verb:
+                    firstPt = pts[0];
+                    break;
+                case SkPath::kLine_Verb:
+                    if (counter == index) {
+                        stroker.moveTo(pts[0]);
+                        stroker.lineTo(pts[1]);
+                        goto done;
+                    }
+                    lastPt = pts[1];
+                    break;
+                case SkPath::kQuad_Verb:
+                    if (counter == index) {
+                        stroker.moveTo(pts[0]);
+                        stroker.quadTo(pts[1], pts[2]);
+                        goto done;
+                    }
+                    lastPt = pts[2];
+                    break;
+                case SkPath::kConic_Verb:
+                    if (counter == index) {
+                        stroker.moveTo(pts[0]);
+                        stroker.conicTo(pts[1], pts[2], iter.conicWeight());
+                        goto done;
+                    }
+                    lastPt = pts[2];
+                    break;
+                case SkPath::kCubic_Verb:
+                    if (counter == index) {
+                        stroker.moveTo(pts[0]);
+                        stroker.cubicTo(pts[1], pts[2], pts[3]);
+                        goto done;
+                    }
+                    lastPt = pts[3];
+                    break;
+                case SkPath::kClose_Verb:
+                    if (counter == index) {
+                        stroker.moveTo(lastPt);
+                        stroker.lineTo(firstPt);
+                        goto done;
+                    }
+                    break;
+                case SkPath::kDone_Verb:
+                    break;
+                default:
+                    SkASSERT(0);
+            }
+        }
+    done:
+        *inner = stroker.fInner;
+        *outer = stroker.fOuter;
+#endif
+    }
+
+    void draw_stroke(SkCanvas* canvas, int active) {
+        SkPath inner, outer;
+        path_stroke(active, &inner, &outer);
+        canvas->drawPath(inner, fSkeletonPaint);
+        canvas->drawPath(outer, fSkeletonPaint);
+    }
+
+    void gather_strokes() {
+        fStrokes.reset();
+        for (int index = 0; index < fPath.countVerbs(); ++index) {
+            Stroke& inner = fStrokes.push_back();
+            inner.reset();
+            inner.fInner = true;
+            Stroke& outer = fStrokes.push_back();
+            outer.reset();
+            outer.fInner = false;
+            path_stroke(index, &inner.fPath, &outer.fPath);
+        }
+    }
+
+    void trim_strokes() {
+        // eliminate self-itersecting loops
+        // trim outside edges
+        gather_strokes();
+        for (int index = 0; index < fStrokes.count(); ++index) {
+            SkPath& outPath = fStrokes[index].fPath;
+            for (int inner = 0; inner < fStrokes.count(); ++inner) {
+                if (index == inner) {
+                    continue;
+                }
+                SkPath& inPath = fStrokes[inner].fPath;
+                if (!outPath.getBounds().intersects(inPath.getBounds())) {
+                    continue;
+                }
+                
+            }
+        }
+    }
+
+    void onDrawContent(SkCanvas* canvas) override {
+#if 0
+        SkDEBUGCODE(SkDebugStrokeGlobals debugGlobals);
+        SkOpAA aaResult(fPath, fWidthControl.fValLo, fResControl.fValLo
+                SkDEBUGPARAMS(&debugGlobals));
+#endif
+        SkPath strokePath;
+//        aaResult.simplify(&strokePath);
+        canvas->drawPath(strokePath, fSkeletonPaint);
+        SkRect bounds = fPath.getBounds();
+        SkScalar radius = fWidthControl.fValLo;
+        int w = (int) (bounds.fRight + radius + 1);
+        int h = (int) (bounds.fBottom + radius + 1);
+        SkImageInfo imageInfo = SkImageInfo::MakeA8(w, h);
+        SkBitmap distMap;
+        uint8_t* distanceMap = set_up_dist_map(imageInfo, &distMap);
+        path_coverage(fPath, distanceMap, w, h);
+        if (fFillButton.enabled()) {
+            canvas->drawPath(fPath, fCoveragePaint);
+        }
+        if (fFilterButton.fState == 2
+                && (0 < fFilterControl.fValLo || fFilterControl.fValHi < 255)) {
+            SkBitmap filteredMap;
+            uint8_t* filtered = set_up_dist_map(imageInfo, &filteredMap);
+            filter_coverage(distanceMap, sizeof(uint8_t) * w * h, (uint8_t) fFilterControl.fValLo,
+                    (uint8_t) fFilterControl.fValHi, filtered);
+            canvas->drawBitmap(filteredMap, 0, 0, &fCoveragePaint);
+        } else if (fFilterButton.enabled()) {
+            canvas->drawBitmap(distMap, 0, 0, &fCoveragePaint);
+        }
+        if (fSkeletonButton.enabled()) {
+            canvas->drawPath(fPath, fActiveVerb >= 0 ? fLightSkeletonPaint : fSkeletonPaint);
+        }
+        if (fActiveVerb >= 0) {
+            draw_segment(canvas);
+        }
+        if (fBisectButton.enabled() || fJoinButton.enabled()) {
+            draw_bisects(canvas, fActiveVerb >= 0);
+        }
+        if (fInOutButton.enabled()) {
+            if (fActiveVerb >= 0) {
+                draw_stroke(canvas, fActiveVerb);
+            } else {
+                for (int index = 0; index < fPath.countVerbs(); ++index) {
+                    draw_stroke(canvas, index);
+                }
+            }
+        }
+        if (fHideAll) {
+            return;
+        }
+        for (int index = 0; index < kControlCount; ++index) {
+            kControlList[index].fControl->draw(canvas, fControlPaints);
+        }
+        for (int index = 0; index < kButtonCount; ++index) {
+            kButtonList[index].fButton->draw(canvas, fButtonPaints);
+        }
+        if (fShowLegend) {
+            draw_legend(canvas);
+        }
+
+#if 0
+        SkPaint paint;
+        paint.setARGB(255, 34, 31, 31);
+        paint.setAntiAlias(true);
+
+        SkPath path;
+        path.moveTo(18,439);
+        path.lineTo(414,439);
+        path.lineTo(414,702);
+        path.lineTo(18,702);
+        path.lineTo(18,439);
+
+        path.moveTo(19,701);
+        path.lineTo(413,701);
+        path.lineTo(413,440);
+        path.lineTo(19,440);
+        path.lineTo(19,701);
+        path.close();
+        canvas->drawPath(path, paint);
+
+        canvas->scale(1.0f, -1.0f);
+        canvas->translate(0.0f, -800.0f);
+        canvas->drawPath(path, paint);
+#endif
+
+    }
+
+    int hittest_pt(SkPoint pt) {
+        for (int index = 0; index < fPath.countPoints(); ++index) {
+            if (SkPoint::Distance(fPath.getPoint(index), pt) <= kHitToleranace * 2) {
+                return index;
+            }
+        }
+        return -1;
+    }
+
+    virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) override {
+        SkPoint pt = {x, y};
+        int ptHit = hittest_pt(pt);
+        if (ptHit >= 0) {
+            return new MyClick(this, MyClick::kPtType, ptHit);
+        }
+        SkPath::Verb verb;
+        SkScalar weight;
+        int verbHit = hittest_verb(pt, &verb, &weight);
+        if (verbHit >= 0) {
+            return new MyClick(this, MyClick::kVerbType, verbHit, verb, weight);
+        }
+        if (!fHideAll) {
+            const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1);
+            for (int index = 0; index < kControlCount; ++index) {
+                if (kControlList[index].fControl->contains(rectPt)) {
+                    return new MyClick(this, MyClick::kControlType,
+                            kControlList[index].fControlType);
+                }
+            }
+            for (int index = 0; index < kButtonCount; ++index) {
+                if (kButtonList[index].fButton->contains(rectPt)) {
+                    return new MyClick(this, MyClick::kControlType, kButtonList[index].fButtonType);
+                }
+            }
+        }
+        fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
+                = fCubicButton.fVisible = fWeightControl.fVisible = fAddButton.fVisible
+                = fDeleteButton.fVisible = false;
+        fActiveVerb = -1;
+        fActivePt = -1;
+        if (fHandlePathMove) {
+            return new MyClick(this, MyClick::kPathType, MyClick::kPathMove);
+        }
+        return this->INHERITED::onFindClickHandler(x, y, modi);
+    }
+
+    static SkScalar MapScreenYtoValue(int y, const UniControl& control) {
+        return SkTMin(1.f, SkTMax(0.f,
+                SkIntToScalar(y) - control.fBounds.fTop) / control.fBounds.height())
+                * (control.fMax - control.fMin) + control.fMin;
+    }
+
+    bool onClick(Click* click) override {
+        MyClick* myClick = (MyClick*) click;
+        switch (myClick->fType) {
+            case MyClick::kPtType: {
+                savePath(click->fState);
+                fActivePt = myClick->ptHit();
+                SkPoint pt = fPath.getPoint((int) myClick->fControl);
+                pt.offset(SkIntToScalar(click->fICurr.fX - click->fIPrev.fX),
+                        SkIntToScalar(click->fICurr.fY - click->fIPrev.fY));
+                set_path_pt(fActivePt, pt, &fPath);
+                validatePath();
+                this->inval(NULL);
+                return true;
+                }
+            case MyClick::kPathType:
+                savePath(click->fState);
+                fPath.offset(SkIntToScalar(click->fICurr.fX - click->fIPrev.fX),
+                        SkIntToScalar(click->fICurr.fY - click->fIPrev.fY));
+                validatePath();
+                this->inval(NULL);
+                return true;
+            case MyClick::kVerbType: {
+                fActiveVerb = myClick->verbHit();
+                fLineButton.fVisible = fQuadButton.fVisible = fConicButton.fVisible
+                        = fCubicButton.fVisible = fAddButton.fVisible = fDeleteButton.fVisible
+                        = true;
+                fLineButton.setEnabled(myClick->fVerb == SkPath::kLine_Verb);
+                fQuadButton.setEnabled(myClick->fVerb == SkPath::kQuad_Verb);
+                fConicButton.setEnabled(myClick->fVerb == SkPath::kConic_Verb);
+                fCubicButton.setEnabled(myClick->fVerb == SkPath::kCubic_Verb);
+                fWeightControl.fValLo = myClick->fWeight;
+                fWeightControl.fVisible = myClick->fVerb == SkPath::kConic_Verb;
+                } break;
+            case MyClick::kControlType: {
+                if (click->fState != Click::kDown_State && myClick->isButton()) {
+                    return true;
+                }
+                switch (myClick->fControl) {
+                    case MyClick::kFilterControl: {
+                        SkScalar val = MapScreenYtoValue(click->fICurr.fY, fFilterControl);
+                        if (val - fFilterControl.fValLo < fFilterControl.fValHi - val) {
+                            fFilterControl.fValLo = SkTMax(0.f, val);
+                        } else {
+                            fFilterControl.fValHi = SkTMin(255.f, val);
+                        }
+                        } break;
+                    case MyClick::kResControl:
+                        fResControl.fValLo = MapScreenYtoValue(click->fICurr.fY, fResControl);
+                        break;
+                    case MyClick::kWeightControl: {
+                        savePath(click->fState);
+                        SkScalar w = MapScreenYtoValue(click->fICurr.fY, fWeightControl);
+                        set_path_weight(fActiveVerb, w, &fPath);
+                        validatePath();
+                        fWeightControl.fValLo = w;
+                        } break;
+                    case MyClick::kWidthControl:
+                        fWidthControl.fValLo = MapScreenYtoValue(click->fICurr.fY, fWidthControl);
+                        break;
+                    case MyClick::kLineButton:
+                        savePath(click->fState);
+                        enable_verb_button(myClick->fControl);
+                        fWeightControl.fVisible = false;
+                        set_path_verb(fActiveVerb, SkPath::kLine_Verb, &fPath, 1);
+                        validatePath();
+                        break;
+                    case MyClick::kQuadButton:
+                        savePath(click->fState);
+                        enable_verb_button(myClick->fControl);
+                        fWeightControl.fVisible = false;
+                        set_path_verb(fActiveVerb, SkPath::kQuad_Verb, &fPath, 1);
+                        validatePath();
+                        break;
+                    case MyClick::kConicButton: {
+                        savePath(click->fState);
+                        enable_verb_button(myClick->fControl);
+                        fWeightControl.fVisible = true;
+                        const SkScalar defaultConicWeight = 1.f / SkScalarSqrt(2);
+                        set_path_verb(fActiveVerb, SkPath::kConic_Verb, &fPath, defaultConicWeight);
+                        validatePath();
+                        fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
+                        } break;
+                    case MyClick::kCubicButton:
+                        savePath(click->fState);
+                        enable_verb_button(myClick->fControl);
+                        fWeightControl.fVisible = false;
+                        set_path_verb(fActiveVerb, SkPath::kCubic_Verb, &fPath, 1);
+                        validatePath();
+                        break;
+                    case MyClick::kAddButton:
+                        savePath(click->fState);
+                        add_path_segment(fActiveVerb, &fPath);
+                        validatePath();
+                        if (fWeightControl.fVisible) {
+                            fWeightControl.fValLo = get_path_weight(fActiveVerb, fPath);
+                        }
+                        break;
+                    case MyClick::kDeleteButton:
+                        savePath(click->fState);
+                        delete_path_segment(fActiveVerb, &fPath);
+                        validatePath();
+                        break;
+                    case MyClick::kFillButton:
+                        fFillButton.toggle();
+                        break;
+                    case MyClick::kSkeletonButton:
+                        fSkeletonButton.toggle();
+                        break;
+                    case MyClick::kFilterButton:
+                        fFilterButton.toggle();
+                        fFilterControl.fVisible = fFilterButton.fState == 2;
+                        break;
+                    case MyClick::kBisectButton:
+                        fBisectButton.toggle();
+                        break;
+                    case MyClick::kJoinButton:
+                        fJoinButton.toggle();
+                        break;
+                    case MyClick::kInOutButton:
+                        fInOutButton.toggle();
+                        break;
+                    default:
+                        SkASSERT(0);
+                        break;
+                }
+            } break;
+            default:
+                SkASSERT(0);
+                break;
+        }
+        setControlButtonsPos();
+        this->inval(NULL);
+        return true;
+    }
+
+private:
+    typedef SampleView INHERITED;
+};
+
+static struct KeyCommand {
+    char fKey;
+    char fAlternate;
+    const char* fDescriptionL;
+    const char* fDescriptionR;
+    bool (AAGeometryView::*fFunction)();
+} kKeyCommandList[] = {
+    { ' ',  0,  "space",   "center path", &AAGeometryView::scaleToFit },   
+    { '-',  0,  "-",          "zoom out", &AAGeometryView::scaleDown },   
+    { '+', '=', "+/=",         "zoom in", &AAGeometryView::scaleUp },   
+    { 'd',  0,  "d",   "dump to console", &AAGeometryView::pathDump },
+    { 'h',  0,  "h",     "hide controls", &AAGeometryView::hideAll },
+    { 'r',  0,  "r",        "reset path", &AAGeometryView::constructPath },
+    { 'z',  0,  "z",              "undo", &AAGeometryView::undo },
+    { '?',  0,  "?",       "show legend", &AAGeometryView::showLegend },
+};
+
+const int kKeyCommandCount = (int) SK_ARRAY_COUNT(kKeyCommandList);
+
+void AAGeometryView::draw_legend(SkCanvas* canvas) {
+    SkScalar bottomOffset = this->height() - 10;
+    for (int index = kKeyCommandCount - 1; index >= 0; --index) {
+        bottomOffset -= 15;
+        canvas->drawText(kKeyCommandList[index].fDescriptionL,
+                strlen(kKeyCommandList[index].fDescriptionL), this->width() - 160, bottomOffset,
+                fLegendLeftPaint);
+        canvas->drawText(kKeyCommandList[index].fDescriptionR,
+                strlen(kKeyCommandList[index].fDescriptionR), this->width() - 20, bottomOffset,
+                fLegendRightPaint);
+    }
+}
+
+// overrides from SkEventSink
+bool AAGeometryView::onQuery(SkEvent* evt) {
+    if (SampleCode::TitleQ(*evt)) {
+        SampleCode::TitleR(evt, "AAGeometry");
+        return true;
+    }
+    SkUnichar uni;
+    if (false) {
+        return this->INHERITED::onQuery(evt);
+    }
+    if (SampleCode::CharQ(*evt, &uni)) {
+        for (int index = 0; index < kButtonCount; ++index) {
+            Button* button = kButtonList[index].fButton;
+            if (button->fVisible && uni == button->fLabel) {
+                MyClick click(this, MyClick::kControlType, kButtonList[index].fButtonType);
+                click.fState = Click::kDown_State;
+                (void) this->onClick(&click);
+                return true;
+            }
+        }
+        for (int index = 0; index < kKeyCommandCount; ++index) {
+            KeyCommand& keyCommand = kKeyCommandList[index];
+            if (uni == keyCommand.fKey || uni == keyCommand.fAlternate) {
+                return (this->*keyCommand.fFunction)();
+            }
+        }
+        if (('A' <= uni && uni <= 'Z') || ('a' <= uni && uni <= 'z')) {
+            for (int index = 0; index < kButtonCount; ++index) {
+                Button* button = kButtonList[index].fButton;
+                if (button->fVisible && (uni & ~0x20) == (button->fLabel & ~0x20)) {
+                    MyClick click(this, MyClick::kControlType, kButtonList[index].fButtonType);
+                    click.fState = Click::kDown_State;
+                    (void) this->onClick(&click);
+                    return true;
+                }
+            }
+        }
+    }
+    return this->INHERITED::onQuery(evt);
+}
+    
+DEF_SAMPLE( return new AAGeometryView; )