skia2/samplecode/SampleAAGeometry.cpp
caryclark 414c4295f9 allow conic chop to fail
Fuzzy values may cause the conic chop to fail.

Check to see if the values are all finite, and
require the caller to do the same.

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

Review-Url: https://codereview.chromium.org/2368993002
2016-09-26 11:03:54 -07:00

1873 lines
63 KiB
C++

/*
* 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());
if (!conic.chopAt(0.5f, chop)) {
return;
}
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);
if (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; )