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