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; )