Index: samplecode/SampleAAGeometry.cpp |
diff --git a/samplecode/SampleAAGeometry.cpp b/samplecode/SampleAAGeometry.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7d873032e49dc53bdec8c8f73a22bb8c8408b717 |
--- /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; ) |