You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1862 lines
62 KiB
1862 lines
62 KiB
/* |
|
* 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 "Sample.h" |
|
#include "SkBitmap.h" |
|
#include "SkCanvas.h" |
|
#include "SkGeometry.h" |
|
#include "SkIntersections.h" |
|
#include "SkMacros.h" |
|
#include "SkOpEdgeBuilder.h" |
|
// #include "SkPathOpsSimplifyAA.h" |
|
// #include "SkPathStroker.h" |
|
#include "SkPointPriv.h" |
|
#include "SkString.h" |
|
#include "SkTextUtils.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]; |
|
SkFont fLabelFont; |
|
|
|
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); |
|
fLabelFont.setSize(25.0f); |
|
} |
|
}; |
|
|
|
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]); |
|
SkTextUtils::Draw(canvas, &fLabel, 1, kUTF8_SkTextEncoding, fBounds.centerX(), fBounds.fBottom - 5, |
|
paints.fLabelFont, SkPaint(), SkTextUtils::kCenter_Align); |
|
} |
|
|
|
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; |
|
|
|
SkFont fLabelFont; |
|
SkFont fValueFont; |
|
|
|
ControlPaints() { |
|
fOutline.setAntiAlias(true); |
|
fOutline.setStyle(SkPaint::kStroke_Style); |
|
fIndicator = fOutline; |
|
fIndicator.setColor(SK_ColorRED); |
|
fFill.setAntiAlias(true); |
|
fFill.setColor(0x7fff0000); |
|
fLabel.setAntiAlias(true); |
|
fLabelFont.setSize(13.0f); |
|
fValue.setAntiAlias(true); |
|
fValueFont.setSize(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->drawString(label, fBounds.fLeft + 5, fYLo - 5, paints.fValueFont, paints.fValue); |
|
canvas->drawString(fName, fBounds.fLeft, fBounds.bottom() + 11, paints.fLabelFont, |
|
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->drawString(label, fBounds.fLeft + 5, yPos - 5, paints.fValueFont, paints.fValue); |
|
SkRect fill = { fBounds.fLeft, fYLo, fBounds.fRight, yPos }; |
|
canvas->drawRect(fill, paints.fFill); |
|
} |
|
}; |
|
|
|
|
|
class MyClick : public Sample::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(Sample* target, ClickType type, ControlType control) |
|
: Click(target) |
|
, fType(type) |
|
, fControl(control) |
|
, fVerb((SkPath::Verb) -1) |
|
, fWeight(1) { |
|
} |
|
|
|
MyClick(Sample* target, ClickType type, int index) |
|
: Click(target) |
|
, fType(type) |
|
, fControl((ControlType) index) |
|
, fVerb((SkPath::Verb) -1) |
|
, fWeight(1) { |
|
} |
|
|
|
MyClick(Sample* 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 = nullptr; |
|
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 Sample { |
|
SkPaint fActivePaint; |
|
SkPaint fComplexPaint; |
|
SkPaint fCoveragePaint; |
|
SkFont fLegendLeftFont; |
|
SkFont fLegendRightFont; |
|
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 = 25; |
|
|
|
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(nullptr) |
|
, 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); |
|
fLegendLeftFont.setSize(13); |
|
fLegendRightFont = fLegendLeftFont; |
|
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); |
|
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; |
|
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; |
|
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 |
|
|
|
bool onQuery(Sample::Event* 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(); |
|
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(); |
|
return true; |
|
} |
|
|
|
bool scaleUp() { |
|
SkMatrix matrix; |
|
SkRect bounds = fPath.getBounds(); |
|
matrix.setScale(1.5f, 1.5f, bounds.centerX(), bounds.centerY()); |
|
fPath.transform(matrix); |
|
validatePath(); |
|
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; |
|
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, pt + bisect, fSkeletonPaint); |
|
} |
|
lastV.setLength(fWidthControl.fValLo); |
|
if (fBisectButton.enabled()) { |
|
canvas->drawLine(pt, {pt.fX - lastV.fY, pt.fY + lastV.fX}, fSkeletonPaint); |
|
} |
|
nextV.setLength(fWidthControl.fValLo); |
|
if (fBisectButton.enabled()) { |
|
canvas->drawLine(pt, {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, {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, nullptr); |
|
tangent.setLength(fWidthControl.fValLo * 2); |
|
canvas->drawLine(maxPt, {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[3]; |
|
int 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 = SkPointPriv::LengthSqd(adjOpp); |
|
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 Sample::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(); |
|
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(); |
|
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(); |
|
return true; |
|
} |
|
|
|
private: |
|
typedef Sample 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; |
|
SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionL, this->width() - 160, bottomOffset, |
|
fLegendLeftFont, SkPaint()); |
|
SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionR, |
|
this->width() - 20, bottomOffset, |
|
fLegendRightFont, SkPaint(), SkTextUtils::kRight_Align); |
|
} |
|
} |
|
|
|
bool AAGeometryView::onQuery(Sample::Event* evt) { |
|
if (Sample::TitleQ(*evt)) { |
|
Sample::TitleR(evt, "AAGeometry"); |
|
return true; |
|
} |
|
SkUnichar uni; |
|
if (false) { |
|
return this->INHERITED::onQuery(evt); |
|
} |
|
if (Sample::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; )
|
|
|