This uses quad approximations of the outer and inner paths describing a stroke. Cubics and conics' thick strokes are approximated with quads as well.

The approximation uses a similar error term as the fill scan converter to determine the number of quads to use.

This also updates SampleApp QuadStroker test with conics, ovals, and stroked text.

Review URL: https://codereview.chromium.org/932113002
This commit is contained in:
caryclark 2015-02-20 06:33:57 -08:00 committed by Commit bot
parent a1f1ee98a1
commit 04e4d08556
5 changed files with 368 additions and 124 deletions

View File

@ -15,6 +15,7 @@
'SK_INTERNAL', 'SK_INTERNAL',
'SK_GAMMA_SRGB', 'SK_GAMMA_SRGB',
'SK_GAMMA_APPLY_TO_A8', 'SK_GAMMA_APPLY_TO_A8',
'SK_QUAD_STROKE_APPROXIMATION',
'SK_SCALAR_TO_FLOAT_EXCLUDED', # temporary to allow Chrome to call SkFloatToScalar 'SK_SCALAR_TO_FLOAT_EXCLUDED', # temporary to allow Chrome to call SkFloatToScalar
# 'SK_USE_DISCARDABLE_SCALEDIMAGECACHE', # TODO(reed): Re-enable when tests don't crash with this. # 'SK_USE_DISCARDABLE_SCALEDIMAGECACHE', # TODO(reed): Re-enable when tests don't crash with this.
], ],

View File

@ -85,6 +85,10 @@ struct StrokeTypeButton {
bool fEnabled; bool fEnabled;
}; };
struct CircleTypeButton : public StrokeTypeButton {
bool fFill;
};
class QuadStrokerView : public SampleView { class QuadStrokerView : public SampleView {
enum { enum {
SKELETON_COLOR = 0xFF0000FF, SKELETON_COLOR = 0xFF0000FF,
@ -92,9 +96,10 @@ class QuadStrokerView : public SampleView {
}; };
enum { enum {
kCount = 10 kCount = 15
}; };
SkPoint fPts[kCount]; SkPoint fPts[kCount];
SkRect fWeightControl;
SkRect fErrorControl; SkRect fErrorControl;
SkRect fWidthControl; SkRect fWidthControl;
SkRect fBounds; SkRect fBounds;
@ -103,11 +108,14 @@ class QuadStrokerView : public SampleView {
SkAutoTUnref<SkSurface> fMinSurface; SkAutoTUnref<SkSurface> fMinSurface;
SkAutoTUnref<SkSurface> fMaxSurface; SkAutoTUnref<SkSurface> fMaxSurface;
StrokeTypeButton fCubicButton; StrokeTypeButton fCubicButton;
StrokeTypeButton fConicButton;
StrokeTypeButton fQuadButton; StrokeTypeButton fQuadButton;
StrokeTypeButton fRRectButton; StrokeTypeButton fRRectButton;
CircleTypeButton fCircleButton;
StrokeTypeButton fTextButton; StrokeTypeButton fTextButton;
SkString fText; SkString fText;
SkScalar fTextSize; SkScalar fTextSize;
SkScalar fWeight;
SkScalar fWidth, fDWidth; SkScalar fWidth, fDWidth;
SkScalar fWidthScale; SkScalar fWidthScale;
int fW, fH, fZoom; int fW, fH, fZoom;
@ -124,32 +132,44 @@ public:
QuadStrokerView() { QuadStrokerView() {
this->setBGColor(SK_ColorLTGRAY); this->setBGColor(SK_ColorLTGRAY);
fPts[0].set(50, 200); fPts[0].set(50, 200); // cubic
fPts[1].set(50, 100); fPts[1].set(50, 100);
fPts[2].set(150, 50); fPts[2].set(150, 50);
fPts[3].set(300, 50); fPts[3].set(300, 50);
fPts[4].set(350, 200); fPts[4].set(350, 200); // conic
fPts[5].set(350, 100); fPts[5].set(350, 100);
fPts[6].set(450, 50); fPts[6].set(450, 50);
fPts[7].set(200, 200); fPts[7].set(150, 300); // quad
fPts[8].set(400, 400); fPts[8].set(150, 200);
fPts[9].set(250, 150);
fPts[10].set(200, 200); // rrect
fPts[11].set(400, 400);
fPts[12].set(250, 250); // oval
fPts[13].set(450, 450);
fPts[9].set(250, 800);
fText = "a"; fText = "a";
fTextSize = 12; fTextSize = 12;
fWidth = 50; fWidth = 50;
fDWidth = 0.25f; fDWidth = 0.25f;
fWeight = 1;
fCubicButton.fLabel = 'C'; fCubicButton.fLabel = 'C';
fCubicButton.fEnabled = false; fCubicButton.fEnabled = false;
fConicButton.fLabel = 'K';
fConicButton.fEnabled = true;
fQuadButton.fLabel = 'Q'; fQuadButton.fLabel = 'Q';
fQuadButton.fEnabled = false; fQuadButton.fEnabled = false;
fRRectButton.fLabel = 'R'; fRRectButton.fLabel = 'R';
fRRectButton.fEnabled = false; fRRectButton.fEnabled = false;
fCircleButton.fLabel = 'O';
fCircleButton.fEnabled = false;
fCircleButton.fFill = false;
fTextButton.fLabel = 'T'; fTextButton.fLabel = 'T';
fTextButton.fEnabled = true; fTextButton.fEnabled = false;
fAnimate = true; fAnimate = true;
setAsNeeded(); setAsNeeded();
} }
@ -183,12 +203,21 @@ protected:
} }
void onSizeChange() SK_OVERRIDE { void onSizeChange() SK_OVERRIDE {
fWeightControl.setXYWH(this->width() - 150, 30, 30, 400);
fErrorControl.setXYWH(this->width() - 100, 30, 30, 400); fErrorControl.setXYWH(this->width() - 100, 30, 30, 400);
fWidthControl.setXYWH(this->width() - 50, 30, 30, 400); fWidthControl.setXYWH(this->width() - 50, 30, 30, 400);
fCubicButton.fBounds.setXYWH(this->width() - 50, 450, 30, 30); int buttonOffset = 450;
fQuadButton.fBounds.setXYWH(this->width() - 50, 500, 30, 30); fCubicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
fRRectButton.fBounds.setXYWH(this->width() - 50, 550, 30, 30); buttonOffset += 50;
fTextButton.fBounds.setXYWH(this->width() - 50, 600, 30, 30); fConicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
buttonOffset += 50;
fQuadButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
buttonOffset += 50;
fRRectButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
buttonOffset += 50;
fCircleButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
buttonOffset += 50;
fTextButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
this->INHERITED::onSizeChange(); this->INHERITED::onSizeChange();
} }
@ -273,25 +302,26 @@ protected:
} }
} }
void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, bool drawText) { void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, SkScalar scale,
bool drawText) {
SkRect bounds = path.getBounds(); SkRect bounds = path.getBounds();
if (bounds.isEmpty()) { if (bounds.isEmpty()) {
return; return;
} }
this->setWHZ(SkScalarCeilToInt(bounds.right()), SkScalarRoundToInt(fTextSize * 3 / 2), this->setWHZ(SkScalarCeilToInt(bounds.right()), drawText
SkScalarRoundToInt(950.0f / fTextSize)); ? SkScalarRoundToInt(scale * 3 / 2) : SkScalarRoundToInt(scale),
SkScalarRoundToInt(950.0f / scale));
erase(fMinSurface); erase(fMinSurface);
SkPaint paint; SkPaint paint;
paint.setColor(0x1f1f0f0f); paint.setColor(0x1f1f0f0f);
fMinSurface->getCanvas()->drawPath(path, paint);
paint.setStyle(SkPaint::kStroke_Style); paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(width * fTextSize * fTextSize); paint.setStrokeWidth(width * scale * scale);
paint.setColor(0x3f0f1f3f); paint.setColor(0x3f0f1f3f);
fMinSurface->getCanvas()->drawPath(path, paint); if (drawText) {
fMinSurface->getCanvas()->drawPath(path, paint);
this->copyMinToMax(); this->copyMinToMax();
fMaxSurface->draw(canvas, 0, 0, NULL); fMaxSurface->draw(canvas, 0, 0, NULL);
}
paint.setAntiAlias(true); paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style); paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(1); paint.setStrokeWidth(1);
@ -300,7 +330,7 @@ protected:
SkPath scaled; SkPath scaled;
SkMatrix matrix; SkMatrix matrix;
matrix.reset(); matrix.reset();
matrix.setScale(950 / fTextSize, 950 / fTextSize); matrix.setScale(950 / scale, 950 / scale);
if (drawText) { if (drawText) {
path.transform(matrix, &scaled); path.transform(matrix, &scaled);
} else { } else {
@ -317,8 +347,11 @@ protected:
SkPaint p; SkPaint p;
p.setStyle(SkPaint::kStroke_Style); p.setStyle(SkPaint::kStroke_Style);
p.setStrokeWidth(width * fTextSize * fTextSize); if (drawText) {
p.setStrokeWidth(width * scale * scale);
} else {
p.setStrokeWidth(width);
}
p.getFillPath(path, &fill); p.getFillPath(path, &fill);
SkPath scaledFill; SkPath scaledFill;
if (drawText) { if (drawText) {
@ -331,6 +364,33 @@ protected:
draw_points(canvas, scaledFill, WIREFRAME_COLOR, false); draw_points(canvas, scaledFill, WIREFRAME_COLOR, false);
} }
void draw_fill(SkCanvas* canvas, const SkRect& rect, SkScalar width) {
if (rect.isEmpty()) {
return;
}
SkPaint paint;
paint.setColor(0x1f1f0f0f);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(width);
SkPath path;
SkScalar maxSide = SkTMax(rect.width(), rect.height()) / 2;
SkPoint center = { rect.fLeft + maxSide, rect.fTop + maxSide };
path.addCircle(center.fX, center.fY, maxSide);
canvas->drawPath(path, paint);
paint.setStyle(SkPaint::kFill_Style);
path.reset();
path.addCircle(center.fX, center.fY, maxSide - width / 2);
paint.setColor(0x3f0f1f3f);
canvas->drawPath(path, paint);
path.reset();
path.setFillType(SkPath::kEvenOdd_FillType);
path.addCircle(center.fX, center.fY, maxSide + width / 2);
SkRect outside = SkRect::MakeXYWH(center.fX - maxSide - width, center.fY - maxSide - width,
(maxSide + width) * 2, (maxSide + width) * 2);
path.addRect(outside);
canvas->drawPath(path, paint);
}
void draw_button(SkCanvas* canvas, const StrokeTypeButton& button) { void draw_button(SkCanvas* canvas, const StrokeTypeButton& button) {
SkPaint paint; SkPaint paint;
paint.setAntiAlias(true); paint.setAntiAlias(true);
@ -377,7 +437,8 @@ protected:
} }
void setAsNeeded() { void setAsNeeded() {
if (fCubicButton.fEnabled || fQuadButton.fEnabled || fRRectButton.fEnabled) { if (fConicButton.fEnabled || fCubicButton.fEnabled || fQuadButton.fEnabled
|| fRRectButton.fEnabled || fCircleButton.fEnabled) {
setForGeometry(); setForGeometry();
} else { } else {
setForText(); setForText();
@ -392,27 +453,34 @@ protected:
path.moveTo(fPts[0]); path.moveTo(fPts[0]);
path.cubicTo(fPts[1], fPts[2], fPts[3]); path.cubicTo(fPts[1], fPts[2], fPts[3]);
setForGeometry(); setForGeometry();
draw_stroke(canvas, path, width, false); draw_stroke(canvas, path, width, 950, false);
}
if (fConicButton.fEnabled) {
path.moveTo(fPts[4]);
path.conicTo(fPts[5], fPts[6], fWeight);
setForGeometry();
draw_stroke(canvas, path, width, 950, false);
} }
if (fQuadButton.fEnabled) { if (fQuadButton.fEnabled) {
path.reset(); path.reset();
path.moveTo(fPts[4]); path.moveTo(fPts[7]);
path.quadTo(fPts[5], fPts[6]); path.quadTo(fPts[8], fPts[9]);
setForGeometry(); setForGeometry();
draw_stroke(canvas, path, width, false); draw_stroke(canvas, path, width, 950, false);
} }
if (fRRectButton.fEnabled) { if (fRRectButton.fEnabled) {
SkScalar rad = 32; SkScalar rad = 32;
SkRect r; SkRect r;
r.set(&fPts[7], 2); r.set(&fPts[10], 2);
path.reset(); path.reset();
SkRRect rr; SkRRect rr;
rr.setRectXY(r, rad, rad); rr.setRectXY(r, rad, rad);
path.addRRect(rr); path.addRRect(rr);
setForGeometry(); setForGeometry();
draw_stroke(canvas, path, width, false); draw_stroke(canvas, path, width, 950, false);
path.reset(); path.reset();
SkRRect rr2; SkRRect rr2;
@ -426,6 +494,19 @@ protected:
canvas->drawPath(path, paint); canvas->drawPath(path, paint);
} }
if (fCircleButton.fEnabled) {
path.reset();
SkRect r;
r.set(&fPts[12], 2);
path.addOval(r);
setForGeometry();
if (fCircleButton.fFill) {
draw_fill(canvas, r, width);
} else {
draw_stroke(canvas, path, width, 950, false);
}
}
if (fTextButton.fEnabled) { if (fTextButton.fEnabled) {
path.reset(); path.reset();
SkPaint paint; SkPaint paint;
@ -433,7 +514,7 @@ protected:
paint.setTextSize(fTextSize); paint.setTextSize(fTextSize);
paint.getTextPath(fText.c_str(), fText.size(), 0, fTextSize, &path); paint.getTextPath(fText.c_str(), fText.size(), 0, fTextSize, &path);
setForText(); setForText();
draw_stroke(canvas, path, width * fWidthScale / fTextSize, true); draw_stroke(canvas, path, width * fWidthScale / fTextSize, fTextSize, true);
} }
if (fAnimate) { if (fAnimate) {
@ -445,6 +526,9 @@ protected:
} }
} }
setAsNeeded(); setAsNeeded();
if (fConicButton.fEnabled) {
draw_control(canvas, fWeightControl, fWeight, 0, 5, "weight");
}
#if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG) #if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG)
draw_control(canvas, fErrorControl, gDebugStrokerError, kStrokerErrorMin, kStrokerErrorMax, draw_control(canvas, fErrorControl, gDebugStrokerError, kStrokerErrorMin, kStrokerErrorMax,
"error"); "error");
@ -453,7 +537,9 @@ protected:
kWidthMax * fWidthScale, "width"); kWidthMax * fWidthScale, "width");
draw_button(canvas, fQuadButton); draw_button(canvas, fQuadButton);
draw_button(canvas, fCubicButton); draw_button(canvas, fCubicButton);
draw_button(canvas, fConicButton);
draw_button(canvas, fRRectButton); draw_button(canvas, fRRectButton);
draw_button(canvas, fCircleButton);
draw_button(canvas, fTextButton); draw_button(canvas, fTextButton);
this->inval(NULL); this->inval(NULL);
} }
@ -472,9 +558,12 @@ protected:
} }
} }
const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1); const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1);
if (fWeightControl.contains(rectPt)) {
return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 1);
}
#if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG) #if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG)
if (fErrorControl.contains(rectPt)) { if (fErrorControl.contains(rectPt)) {
return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 1); return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 2);
} }
#endif #endif
if (fWidthControl.contains(rectPt)) { if (fWidthControl.contains(rectPt)) {
@ -484,17 +573,27 @@ protected:
fCubicButton.fEnabled ^= true; fCubicButton.fEnabled ^= true;
return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 4); return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 4);
} }
if (fConicButton.fBounds.contains(rectPt)) {
fConicButton.fEnabled ^= true;
return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 5);
}
if (fQuadButton.fBounds.contains(rectPt)) { if (fQuadButton.fBounds.contains(rectPt)) {
fQuadButton.fEnabled ^= true; fQuadButton.fEnabled ^= true;
return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 5); return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 6);
} }
if (fRRectButton.fBounds.contains(rectPt)) { if (fRRectButton.fBounds.contains(rectPt)) {
fRRectButton.fEnabled ^= true; fRRectButton.fEnabled ^= true;
return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 6); return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 7);
}
if (fCircleButton.fBounds.contains(rectPt)) {
bool wasEnabled = fCircleButton.fEnabled;
fCircleButton.fEnabled = !fCircleButton.fFill;
fCircleButton.fFill = wasEnabled && !fCircleButton.fFill;
return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 8);
} }
if (fTextButton.fBounds.contains(rectPt)) { if (fTextButton.fBounds.contains(rectPt)) {
fTextButton.fEnabled ^= true; fTextButton.fEnabled ^= true;
return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 7); return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 9);
} }
return this->INHERITED::onFindClickHandler(x, y, modi); return this->INHERITED::onFindClickHandler(x, y, modi);
} }
@ -510,9 +609,11 @@ protected:
fPts[index].offset(SkIntToScalar(click->fICurr.fX - click->fIPrev.fX), fPts[index].offset(SkIntToScalar(click->fICurr.fX - click->fIPrev.fX),
SkIntToScalar(click->fICurr.fY - click->fIPrev.fY)); SkIntToScalar(click->fICurr.fY - click->fIPrev.fY));
this->inval(NULL); this->inval(NULL);
} else if (index == (int) SK_ARRAY_COUNT(fPts) + 1) {
fWeight = MapScreenYtoValue(click->fICurr.fY, fWeightControl, 0, 5);
} }
#if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG) #if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG)
else if (index == (int) SK_ARRAY_COUNT(fPts) + 1) { else if (index == (int) SK_ARRAY_COUNT(fPts) + 2) {
gDebugStrokerError = SkTMax(FLT_EPSILON, MapScreenYtoValue(click->fICurr.fY, gDebugStrokerError = SkTMax(FLT_EPSILON, MapScreenYtoValue(click->fICurr.fY,
fErrorControl, kStrokerErrorMin, kStrokerErrorMax)); fErrorControl, kStrokerErrorMin, kStrokerErrorMax));
gDebugStrokerErrorSet = true; gDebugStrokerErrorSet = true;

View File

@ -9,26 +9,26 @@
#include "SkGeometry.h" #include "SkGeometry.h"
#include "SkPath.h" #include "SkPath.h"
#if QUAD_STROKE_APPROXIMATION #ifdef SK_QUAD_STROKE_APPROXIMATION
enum { enum {
kTangent_RecursiveLimit, kTangent_RecursiveLimit,
kCubic_RecursiveLimit, kCubic_RecursiveLimit,
kConic_RecursiveLimit,
kQuad_RecursiveLimit kQuad_RecursiveLimit
}; };
// quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure // quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure
// largest seen for normal cubics : 5, 26 // largest seen for normal cubics : 5, 26
// largest seen for normal quads : 11 // largest seen for normal quads : 11
static const int kRecursiveLimits[] = { 5*3, 26*3, 11*3 }; // 3x limits seen in practical tests static const int kRecursiveLimits[] = { 5*3, 26*3, 11*3, 11*3 }; // 3x limits seen in practice
SK_COMPILE_ASSERT(0 == kTangent_RecursiveLimit, cubic_stroke_relies_on_tangent_equalling_zero);
SK_COMPILE_ASSERT(1 == kCubic_RecursiveLimit, cubic_stroke_relies_on_cubic_equalling_one);
SK_COMPILE_ASSERT(SK_ARRAY_COUNT(kRecursiveLimits) == kQuad_RecursiveLimit + 1, SK_COMPILE_ASSERT(SK_ARRAY_COUNT(kRecursiveLimits) == kQuad_RecursiveLimit + 1,
recursive_limits_mismatch); recursive_limits_mismatch);
#ifdef SK_DEBUG // enables tweaking these values at runtime from SampleApp #ifdef SK_DEBUG
bool gDebugStrokerErrorSet = false;
SkScalar gDebugStrokerError;
int gMaxRecursion[SK_ARRAY_COUNT(kRecursiveLimits)] = { 0 }; int gMaxRecursion[SK_ARRAY_COUNT(kRecursiveLimits)] = { 0 };
#endif #endif
#ifndef DEBUG_QUAD_STROKER #ifndef DEBUG_QUAD_STROKER
@ -51,14 +51,16 @@
#endif #endif
#ifndef SK_QUAD_STROKE_APPROXIMATION
#define kMaxQuadSubdivide 5 #define kMaxQuadSubdivide 5
#define kMaxCubicSubdivide 7 #define kMaxCubicSubdivide 7
#endif
static inline bool degenerate_vector(const SkVector& v) { static inline bool degenerate_vector(const SkVector& v) {
return !SkPoint::CanNormalize(v.fX, v.fY); return !SkPoint::CanNormalize(v.fX, v.fY);
} }
#if !QUAD_STROKE_APPROXIMATION #ifndef SK_QUAD_STROKE_APPROXIMATION
static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) { static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) {
/* root2/2 is a 45-degree angle /* root2/2 is a 45-degree angle
make this constant bigger for more subdivisions (but not >= 1) make this constant bigger for more subdivisions (but not >= 1)
@ -111,7 +113,7 @@ static bool set_normal_unitnormal(const SkVector& vec,
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#if QUAD_STROKE_APPROXIMATION #ifdef SK_QUAD_STROKE_APPROXIMATION
struct SkQuadConstruct { // The state of the quad stroke under construction. struct SkQuadConstruct { // The state of the quad stroke under construction.
SkPoint fQuad[3]; // the stroked quad parallel to the original curve SkPoint fQuad[3]; // the stroked quad parallel to the original curve
@ -156,19 +158,16 @@ struct SkQuadConstruct { // The state of the quad stroke under construction.
class SkPathStroker { class SkPathStroker {
public: public:
#if QUAD_STROKE_APPROXIMATION
SkPathStroker(const SkPath& src,
SkScalar radius, SkScalar miterLimit, SkScalar error, SkPaint::Cap,
SkPaint::Join, SkScalar resScale);
#else
SkPathStroker(const SkPath& src, SkPathStroker(const SkPath& src,
SkScalar radius, SkScalar miterLimit, SkPaint::Cap, SkScalar radius, SkScalar miterLimit, SkPaint::Cap,
SkPaint::Join, SkScalar resScale); SkPaint::Join, SkScalar resScale);
#endif
void moveTo(const SkPoint&); void moveTo(const SkPoint&);
void lineTo(const SkPoint&); void lineTo(const SkPoint&);
void quadTo(const SkPoint&, const SkPoint&); void quadTo(const SkPoint&, const SkPoint&);
#ifdef SK_QUAD_STROKE_APPROXIMATION
void conicTo(const SkPoint&, const SkPoint&, SkScalar weight);
#endif
void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&); void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
void close(bool isLine) { this->finishContour(true, isLine); } void close(bool isLine) { this->finishContour(true, isLine); }
@ -181,12 +180,13 @@ public:
SkScalar getResScale() const { return fResScale; } SkScalar getResScale() const { return fResScale; }
private: private:
#if QUAD_STROKE_APPROXIMATION
SkScalar fError;
#endif
SkScalar fRadius; SkScalar fRadius;
SkScalar fInvMiterLimit; SkScalar fInvMiterLimit;
SkScalar fResScale; SkScalar fResScale;
#ifdef SK_QUAD_STROKE_APPROXIMATION
SkScalar fInvResScale;
SkScalar fInvResScaleSquared;
#endif
SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal; SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
SkPoint fFirstPt, fPrevPt; // on original path SkPoint fFirstPt, fPrevPt; // on original path
@ -200,7 +200,7 @@ private:
SkPath fInner, fOuter; // outer is our working answer, inner is temp SkPath fInner, fOuter; // outer is our working answer, inner is temp
SkPath fExtra; // added as extra complete contours SkPath fExtra; // added as extra complete contours
#if QUAD_STROKE_APPROXIMATION #ifdef SK_QUAD_STROKE_APPROXIMATION
enum StrokeType { enum StrokeType {
kOuter_StrokeType = 1, // use sign-opposite values later to flip perpendicular axis kOuter_StrokeType = 1, // use sign-opposite values later to flip perpendicular axis
kInner_StrokeType = -1 kInner_StrokeType = -1
@ -231,11 +231,17 @@ private:
bool fFoundTangents; // do less work until tangents meet (cubic) bool fFoundTangents; // do less work until tangents meet (cubic)
void addDegenerateLine(const SkQuadConstruct* ); void addDegenerateLine(const SkQuadConstruct* );
ReductionType CheckConicLinear(const SkConic& , SkPoint* reduction);
ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3], ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3],
const SkPoint** tanPtPtr); const SkPoint** tanPtPtr);
ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction); ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction);
ResultType compareQuadConic(const SkConic& , SkQuadConstruct* );
ResultType compareQuadCubic(const SkPoint cubic[4], SkQuadConstruct* ); ResultType compareQuadCubic(const SkPoint cubic[4], SkQuadConstruct* );
ResultType compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* ); ResultType compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* );
bool conicPerpRay(const SkConic& , SkScalar t, SkPoint* tPt, SkPoint* onPt,
SkPoint* tangent) const;
bool conicQuadEnds(const SkConic& , SkQuadConstruct* );
bool conicStroke(const SkConic& , SkQuadConstruct* );
bool cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* ) const; bool cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* ) const;
bool cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt, bool cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
SkPoint* tangent) const; SkPoint* tangent) const;
@ -248,6 +254,9 @@ private:
void quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt, void quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
SkPoint* tangent) const; SkPoint* tangent) const;
bool quadStroke(const SkPoint quad[3], SkQuadConstruct* ); bool quadStroke(const SkPoint quad[3], SkQuadConstruct* );
void setConicEndNormal(const SkConic& ,
const SkVector& normalAB, const SkVector& unitNormalAB,
SkVector* normalBC, SkVector* unitNormalBC);
void setCubicEndNormal(const SkPoint cubic[4], void setCubicEndNormal(const SkPoint cubic[4],
const SkVector& normalAB, const SkVector& unitNormalAB, const SkVector& normalAB, const SkVector& unitNormalAB,
SkVector* normalCD, SkVector* unitNormalCD); SkVector* normalCD, SkVector* unitNormalCD);
@ -268,7 +277,7 @@ private:
const SkVector& unitNormal); const SkVector& unitNormal);
void line_to(const SkPoint& currPt, const SkVector& normal); void line_to(const SkPoint& currPt, const SkVector& normal);
#if !QUAD_STROKE_APPROXIMATION #ifndef SK_QUAD_STROKE_APPROXIMATION
void quad_to(const SkPoint pts[3], void quad_to(const SkPoint pts[3],
const SkVector& normalAB, const SkVector& unitNormalAB, const SkVector& normalAB, const SkVector& unitNormalAB,
SkVector* normalBC, SkVector* unitNormalBC, SkVector* normalBC, SkVector* unitNormalBC,
@ -348,16 +357,11 @@ void SkPathStroker::finishContour(bool close, bool currIsLine) {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#if QUAD_STROKE_APPROXIMATION
SkPathStroker::SkPathStroker(const SkPath& src,
SkScalar radius, SkScalar miterLimit, SkScalar error,
SkPaint::Cap cap, SkPaint::Join join, SkScalar resScale)
#else
SkPathStroker::SkPathStroker(const SkPath& src, SkPathStroker::SkPathStroker(const SkPath& src,
SkScalar radius, SkScalar miterLimit, SkScalar radius, SkScalar miterLimit,
SkPaint::Cap cap, SkPaint::Join join, SkScalar resScale) SkPaint::Cap cap, SkPaint::Join join, SkScalar resScale)
#endif : fRadius(radius)
: fRadius(radius), fResScale(resScale) { , fResScale(resScale) {
/* This is only used when join is miter_join, but we initialize it here /* This is only used when join is miter_join, but we initialize it here
so that it is always defined, to fis valgrind warnings. so that it is always defined, to fis valgrind warnings.
@ -386,15 +390,11 @@ SkPathStroker::SkPathStroker(const SkPath& src,
fOuter.setIsVolatile(true); fOuter.setIsVolatile(true);
fInner.incReserve(src.countPoints()); fInner.incReserve(src.countPoints());
fInner.setIsVolatile(true); fInner.setIsVolatile(true);
#if QUAD_STROKE_APPROXIMATION #ifdef SK_QUAD_STROKE_APPROXIMATION
#ifdef SK_DEBUG // TODO : write a common error function used by stroking and filling
if (!gDebugStrokerErrorSet) { // The '4' below matches the fill scan converter's error term
gDebugStrokerError = error; fInvResScale = SkScalarInvert(resScale * 4);
} fInvResScaleSquared = fInvResScale * fInvResScale;
fError = gDebugStrokerError;
#else
fError = error;
#endif
fRecursionDepth = 0; fRecursionDepth = 0;
#endif #endif
} }
@ -423,7 +423,7 @@ void SkPathStroker::lineTo(const SkPoint& currPt) {
this->postJoinTo(currPt, normal, unitNormal); this->postJoinTo(currPt, normal, unitNormal);
} }
#if !QUAD_STROKE_APPROXIMATION #ifndef SK_QUAD_STROKE_APPROXIMATION
void SkPathStroker::quad_to(const SkPoint pts[3], void SkPathStroker::quad_to(const SkPoint pts[3],
const SkVector& normalAB, const SkVector& unitNormalAB, const SkVector& normalAB, const SkVector& unitNormalAB,
SkVector* normalBC, SkVector* unitNormalBC, SkVector* normalBC, SkVector* unitNormalBC,
@ -461,7 +461,7 @@ void SkPathStroker::quad_to(const SkPoint pts[3],
} }
#endif #endif
#if QUAD_STROKE_APPROXIMATION #ifdef SK_QUAD_STROKE_APPROXIMATION
void SkPathStroker::setQuadEndNormal(const SkPoint quad[3], const SkVector& normalAB, void SkPathStroker::setQuadEndNormal(const SkPoint quad[3], const SkVector& normalAB,
const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) { const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) {
if (!set_normal_unitnormal(quad[1], quad[2], fRadius, normalBC, unitNormalBC)) { if (!set_normal_unitnormal(quad[1], quad[2], fRadius, normalBC, unitNormalBC)) {
@ -470,6 +470,11 @@ void SkPathStroker::setQuadEndNormal(const SkPoint quad[3], const SkVector& norm
} }
} }
void SkPathStroker::setConicEndNormal(const SkConic& conic, const SkVector& normalAB,
const SkVector& unitNormalAB, SkVector* normalBC, SkVector* unitNormalBC) {
setQuadEndNormal(conic.fPts, normalAB, unitNormalAB, normalBC, unitNormalBC);
}
void SkPathStroker::setCubicEndNormal(const SkPoint cubic[4], const SkVector& normalAB, void SkPathStroker::setCubicEndNormal(const SkPoint cubic[4], const SkVector& normalAB,
const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD) { const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD) {
SkVector ab = cubic[1] - cubic[0]; SkVector ab = cubic[1] - cubic[0];
@ -547,7 +552,8 @@ static SkScalar pt_to_line(const SkPoint& pt, const SkPoint& lineStart, const Sk
*/ */
static bool cubic_in_line(const SkPoint cubic[4]) { static bool cubic_in_line(const SkPoint cubic[4]) {
SkScalar ptMax = -1; SkScalar ptMax = -1;
int outer1, outer2; int outer1 SK_INIT_TO_AVOID_WARNING;
int outer2 SK_INIT_TO_AVOID_WARNING;
for (int index = 0; index < 3; ++index) { for (int index = 0; index < 3; ++index) {
for (int inner = index + 1; inner < 4; ++inner) { for (int inner = index + 1; inner < 4; ++inner) {
SkVector testDiff = cubic[inner] - cubic[index]; SkVector testDiff = cubic[inner] - cubic[index];
@ -583,7 +589,8 @@ static bool cubic_in_line(const SkPoint cubic[4]) {
*/ */
static bool quad_in_line(const SkPoint quad[3]) { static bool quad_in_line(const SkPoint quad[3]) {
SkScalar ptMax = -1; SkScalar ptMax = -1;
int outer1, outer2; int outer1 SK_INIT_TO_AVOID_WARNING;
int outer2 SK_INIT_TO_AVOID_WARNING;
for (int index = 0; index < 2; ++index) { for (int index = 0; index < 2; ++index) {
for (int inner = index + 1; inner < 3; ++inner) { for (int inner = index + 1; inner < 3; ++inner) {
SkVector testDiff = quad[inner] - quad[index]; SkVector testDiff = quad[inner] - quad[index];
@ -603,6 +610,10 @@ static bool quad_in_line(const SkPoint quad[3]) {
return pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= lineSlop; return pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= lineSlop;
} }
static bool conic_in_line(const SkConic& conic) {
return quad_in_line(conic.fPts);
}
SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic[4], SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic[4],
SkPoint reduction[3], const SkPoint** tangentPtPtr) { SkPoint reduction[3], const SkPoint** tangentPtPtr) {
bool degenerateAB = degenerate_vector(cubic[1] - cubic[0]); bool degenerateAB = degenerate_vector(cubic[1] - cubic[0]);
@ -634,6 +645,27 @@ SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic
return (ReductionType) (kQuad_ReductionType + count); return (ReductionType) (kQuad_ReductionType + count);
} }
SkPathStroker::ReductionType SkPathStroker::CheckConicLinear(const SkConic& conic,
SkPoint* reduction) {
bool degenerateAB = degenerate_vector(conic.fPts[1] - conic.fPts[0]);
bool degenerateBC = degenerate_vector(conic.fPts[2] - conic.fPts[1]);
if (degenerateAB & degenerateBC) {
return kPoint_ReductionType;
}
if (degenerateAB | degenerateBC) {
return kLine_ReductionType;
}
if (!conic_in_line(conic)) {
return kQuad_ReductionType;
}
SkScalar t;
if (!conic.findMaxCurvature(&t) || 0 == t) {
return kLine_ReductionType;
}
conic.evalAt(t, reduction, NULL);
return kDegenerate_ReductionType;
}
SkPathStroker::ReductionType SkPathStroker::CheckQuadLinear(const SkPoint quad[3], SkPathStroker::ReductionType SkPathStroker::CheckQuadLinear(const SkPoint quad[3],
SkPoint* reduction) { SkPoint* reduction) {
bool degenerateAB = degenerate_vector(quad[1] - quad[0]); bool degenerateAB = degenerate_vector(quad[1] - quad[0]);
@ -742,8 +774,45 @@ DRAW_LINE:
} }
#endif #endif
#ifdef SK_QUAD_STROKE_APPROXIMATION
void SkPathStroker::conicTo(const SkPoint& pt1, const SkPoint& pt2, SkScalar weight) {
const SkConic conic(fPrevPt, pt1, pt2, weight);
SkPoint reduction;
ReductionType reductionType = CheckConicLinear(conic, &reduction);
if (kPoint_ReductionType == reductionType) {
return;
}
if (kLine_ReductionType == reductionType) {
this->lineTo(pt2);
return;
}
if (kDegenerate_ReductionType == reductionType) {
this->lineTo(reduction);
SkStrokerPriv::JoinProc saveJoiner = fJoiner;
fJoiner = SkStrokerPriv::JoinFactory(SkPaint::kRound_Join);
this->lineTo(pt2);
fJoiner = saveJoiner;
return;
}
SkASSERT(kQuad_ReductionType == reductionType);
SkVector normalAB, unitAB, normalBC, unitBC;
this->preJoinTo(pt1, &normalAB, &unitAB, false);
SkQuadConstruct quadPts;
this->init(kOuter_StrokeType, &quadPts, 0, 1);
if (!this->conicStroke(conic, &quadPts)) {
return;
}
this->init(kInner_StrokeType, &quadPts, 0, 1);
if (!this->conicStroke(conic, &quadPts)) {
return;
}
this->setConicEndNormal(conic, normalAB, unitAB, &normalBC, &unitBC);
this->postJoinTo(pt2, normalBC, unitBC);
}
#endif
void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) { void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
#if QUAD_STROKE_APPROXIMATION #ifdef SK_QUAD_STROKE_APPROXIMATION
const SkPoint quad[3] = { fPrevPt, pt1, pt2 }; const SkPoint quad[3] = { fPrevPt, pt1, pt2 };
SkPoint reduction; SkPoint reduction;
ReductionType reductionType = CheckQuadLinear(quad, &reduction); ReductionType reductionType = CheckQuadLinear(quad, &reduction);
@ -831,7 +900,7 @@ void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
this->postJoinTo(pt2, normalBC, unitBC); this->postJoinTo(pt2, normalBC, unitBC);
} }
#if QUAD_STROKE_APPROXIMATION #ifdef SK_QUAD_STROKE_APPROXIMATION
// Given a point on the curve and its derivative, scale the derivative by the radius, and // Given a point on the curve and its derivative, scale the derivative by the radius, and
// compute the perpendicular point and its tangent. // compute the perpendicular point and its tangent.
void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt, void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt,
@ -852,6 +921,41 @@ void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt,
} }
} }
// Given a conic and t, return the point on curve, its perpendicular, and the perpendicular tangent.
// Returns false if the perpendicular could not be computed (because the derivative collapsed to 0)
bool SkPathStroker::conicPerpRay(const SkConic& conic, SkScalar t, SkPoint* tPt, SkPoint* onPt,
SkPoint* tangent) const {
SkVector dxy;
conic.evalAt(t, tPt, &dxy);
if (dxy.fX == 0 && dxy.fY == 0) {
dxy = conic.fPts[2] - conic.fPts[0];
}
setRayPts(*tPt, &dxy, onPt, tangent);
return true;
}
// Given a conic and a t range, find the start and end if they haven't been found already.
bool SkPathStroker::conicQuadEnds(const SkConic& conic, SkQuadConstruct* quadPts) {
if (!quadPts->fStartSet) {
SkPoint conicStartPt;
if (!this->conicPerpRay(conic, quadPts->fStartT, &conicStartPt, &quadPts->fQuad[0],
&quadPts->fTangentStart)) {
return false;
}
quadPts->fStartSet = true;
}
if (!quadPts->fEndSet) {
SkPoint conicEndPt;
if (!this->conicPerpRay(conic, quadPts->fEndT, &conicEndPt, &quadPts->fQuad[2],
&quadPts->fTangentEnd)) {
return false;
}
quadPts->fEndSet = true;
}
return true;
}
// Given a cubic and t, return the point on curve, its perpendicular, and the perpendicular tangent. // Given a cubic and t, return the point on curve, its perpendicular, and the perpendicular tangent.
// Returns false if the perpendicular could not be computed (because the derivative collapsed to 0) // Returns false if the perpendicular could not be computed (because the derivative collapsed to 0)
bool SkPathStroker::cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt, bool SkPathStroker::cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
@ -930,10 +1034,6 @@ SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts,
// are small, a straight line is good enough // are small, a straight line is good enough
SkScalar dist1 = pt_to_line(start, end, quadPts->fTangentEnd); SkScalar dist1 = pt_to_line(start, end, quadPts->fTangentEnd);
SkScalar dist2 = pt_to_line(end, start, quadPts->fTangentStart); SkScalar dist2 = pt_to_line(end, start, quadPts->fTangentStart);
if (SkTMax(dist1, dist2) <= fError * fError) {
return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
"SkTMax(dist1=%g, dist2=%g) <= fError * fError", dist1, dist2);
}
if ((numerA >= 0) != (numerB >= 0)) { if ((numerA >= 0) != (numerB >= 0)) {
if (kCtrlPt_RayType == intersectRayType) { if (kCtrlPt_RayType == intersectRayType) {
numerA /= denom; numerA /= denom;
@ -944,6 +1044,10 @@ SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts,
return STROKER_RESULT(kQuad_ResultType, depth, quadPts, return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
"(numerA=%g >= 0) != (numerB=%g >= 0)", numerA, numerB); "(numerA=%g >= 0) != (numerB=%g >= 0)", numerA, numerB);
} }
if (SkTMax(dist1, dist2) <= fInvResScaleSquared) {
return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
"SkTMax(dist1=%g, dist2=%g) <= fInvResScaleSquared", dist1, dist2);
}
return STROKER_RESULT(kSplit_ResultType, depth, quadPts, return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
"(numerA=%g >= 0) == (numerB=%g >= 0)", numerA, numerB); "(numerA=%g >= 0) == (numerB=%g >= 0)", numerA, numerB);
} else { // if the lines are parallel, straight line is good enough } else { // if the lines are parallel, straight line is good enough
@ -979,19 +1083,19 @@ static int intersect_quad_ray(const SkPoint line[2], const SkPoint quad[3], SkSc
// Return true if the point is close to the bounds of the quad. This is used as a quick reject. // Return true if the point is close to the bounds of the quad. This is used as a quick reject.
bool SkPathStroker::ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const { bool SkPathStroker::ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const {
SkScalar xMin = SkTMin(SkTMin(quad[0].fX, quad[1].fX), quad[2].fX); SkScalar xMin = SkTMin(SkTMin(quad[0].fX, quad[1].fX), quad[2].fX);
if (pt.fX + fError < xMin) { if (pt.fX + fInvResScale < xMin) {
return false; return false;
} }
SkScalar xMax = SkTMax(SkTMax(quad[0].fX, quad[1].fX), quad[2].fX); SkScalar xMax = SkTMax(SkTMax(quad[0].fX, quad[1].fX), quad[2].fX);
if (pt.fX - fError > xMax) { if (pt.fX - fInvResScale > xMax) {
return false; return false;
} }
SkScalar yMin = SkTMin(SkTMin(quad[0].fY, quad[1].fY), quad[2].fY); SkScalar yMin = SkTMin(SkTMin(quad[0].fY, quad[1].fY), quad[2].fY);
if (pt.fY + fError < yMin) { if (pt.fY + fInvResScale < yMin) {
return false; return false;
} }
SkScalar yMax = SkTMax(SkTMax(quad[0].fY, quad[1].fY), quad[2].fY); SkScalar yMax = SkTMax(SkTMax(quad[0].fY, quad[1].fY), quad[2].fY);
if (pt.fY - fError > yMax) { if (pt.fY - fInvResScale > yMax) {
return false; return false;
} }
return true; return true;
@ -1022,7 +1126,7 @@ SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[
SkPoint strokeMid; SkPoint strokeMid;
SkEvalQuadAt(stroke, SK_ScalarHalf, &strokeMid); SkEvalQuadAt(stroke, SK_ScalarHalf, &strokeMid);
// measure the distance from the curve to the quad-stroke midpoint, compare to radius // measure the distance from the curve to the quad-stroke midpoint, compare to radius
if (points_within_dist(ray[0], strokeMid, fError)) { // if the difference is small if (points_within_dist(ray[0], strokeMid, fInvResScaleSquared)) { // if the difference is small
if (sharp_angle(quadPts->fQuad)) { if (sharp_angle(quadPts->fQuad)) {
return STROKER_RESULT(kSplit_ResultType, depth, quadPts, return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
"sharp_angle (1) =%g,%g, %g,%g, %g,%g", "sharp_angle (1) =%g,%g, %g,%g, %g,%g",
@ -1031,8 +1135,8 @@ SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[
quadPts->fQuad[2].fX, quadPts->fQuad[2].fY); quadPts->fQuad[2].fX, quadPts->fQuad[2].fY);
} }
return STROKER_RESULT(kQuad_ResultType, depth, quadPts, return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
"points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fError)", "points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fInvResScaleSquared=%g)",
ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY); ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY, fInvResScaleSquared);
} }
// measure the distance to quad's bounds (quick reject) // measure the distance to quad's bounds (quick reject)
// an alternative : look for point in triangle // an alternative : look for point in triangle
@ -1051,7 +1155,7 @@ SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[
} }
SkPoint quadPt; SkPoint quadPt;
SkEvalQuadAt(stroke, roots[0], &quadPt); SkEvalQuadAt(stroke, roots[0], &quadPt);
SkScalar error = fError * (SK_Scalar1 - SkScalarAbs(roots[0] - 0.5f) * 2); SkScalar error = fInvResScale * (SK_Scalar1 - SkScalarAbs(roots[0] - 0.5f) * 2);
if (points_within_dist(ray[0], quadPt, error)) { // if the difference is small, we're done if (points_within_dist(ray[0], quadPt, error)) { // if the difference is small, we're done
if (sharp_angle(quadPts->fQuad)) { if (sharp_angle(quadPts->fQuad)) {
return STROKER_RESULT(kSplit_ResultType, depth, quadPts, return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
@ -1080,14 +1184,32 @@ SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4]
return resultType; return resultType;
} }
// project a ray from the curve to the stroke // project a ray from the curve to the stroke
SkPoint ray[2]; // point near midpoint on quad, midpoint on cubic SkPoint ray[2]; // points near midpoint on quad, midpoint on cubic
if (!this->cubicPerpRay(cubic, quadPts->fMidT, &ray[1], &ray[0], NULL)) { if (!this->cubicPerpRay(cubic, quadPts->fMidT, &ray[1], &ray[0], NULL)) {
return kNormalError_ResultType; return kNormalError_ResultType;
} }
return strokeCloseEnough(quadPts->fQuad, ray, quadPts STROKER_DEBUG_PARAMS(fRecursionDepth)); return strokeCloseEnough(quadPts->fQuad, ray, quadPts STROKER_DEBUG_PARAMS(fRecursionDepth));
} }
// if false is returned, caller splits quadratic approximation SkPathStroker::ResultType SkPathStroker::compareQuadConic(const SkConic& conic,
SkQuadConstruct* quadPts) {
// get the quadratic approximation of the stroke
if (!this->conicQuadEnds(conic, quadPts)) {
return kNormalError_ResultType;
}
ResultType resultType = intersectRay(quadPts, kCtrlPt_RayType
STROKER_DEBUG_PARAMS(fRecursionDepth) );
if (resultType != kQuad_ResultType) {
return resultType;
}
// project a ray from the curve to the stroke
SkPoint ray[2]; // points near midpoint on quad, midpoint on conic
if (!this->conicPerpRay(conic, quadPts->fMidT, &ray[1], &ray[0], NULL)) {
return kNormalError_ResultType;
}
return strokeCloseEnough(quadPts->fQuad, ray, quadPts STROKER_DEBUG_PARAMS(fRecursionDepth));
}
SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3], SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3],
SkQuadConstruct* quadPts) { SkQuadConstruct* quadPts) {
// get the quadratic approximation of the stroke // get the quadratic approximation of the stroke
@ -1126,7 +1248,7 @@ bool SkPathStroker::cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct
return false; return false;
} }
SkScalar dist = pt_to_line(strokeMid, quadPts->fQuad[0], quadPts->fQuad[2]); SkScalar dist = pt_to_line(strokeMid, quadPts->fQuad[0], quadPts->fQuad[2]);
return dist < fError * fError; return dist < fInvResScaleSquared;
} }
bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts) { bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts) {
@ -1137,8 +1259,8 @@ bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts
return false; return false;
} }
if ((kDegenerate_ResultType == resultType if ((kDegenerate_ResultType == resultType
|| points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2], fError)) || points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2],
&& cubicMidOnLine(cubic, quadPts)) { fInvResScaleSquared)) && cubicMidOnLine(cubic, quadPts)) {
addDegenerateLine(quadPts); addDegenerateLine(quadPts);
return true; return true;
} }
@ -1189,6 +1311,36 @@ bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts
return true; return true;
} }
bool SkPathStroker::conicStroke(const SkConic& conic, SkQuadConstruct* quadPts) {
ResultType resultType = this->compareQuadConic(conic, quadPts);
if (kQuad_ResultType == resultType) {
const SkPoint* stroke = quadPts->fQuad;
SkPath* path = fStrokeType == kOuter_StrokeType ? &fOuter : &fInner;
path->quadTo(stroke[1].fX, stroke[1].fY, stroke[2].fX, stroke[2].fY);
return true;
}
if (kDegenerate_ResultType == resultType) {
addDegenerateLine(quadPts);
return true;
}
SkDEBUGCODE(gMaxRecursion[kConic_RecursiveLimit] = SkTMax(gMaxRecursion[kConic_RecursiveLimit],
fRecursionDepth + 1));
if (++fRecursionDepth > kRecursiveLimits[kConic_RecursiveLimit]) {
return false; // just abort if projected quad isn't representable
}
SkQuadConstruct half;
(void) half.initWithStart(quadPts);
if (!this->conicStroke(conic, &half)) {
return false;
}
(void) half.initWithEnd(quadPts);
if (!this->conicStroke(conic, &half)) {
return false;
}
--fRecursionDepth;
return true;
}
bool SkPathStroker::quadStroke(const SkPoint quad[3], SkQuadConstruct* quadPts) { bool SkPathStroker::quadStroke(const SkPoint quad[3], SkQuadConstruct* quadPts) {
ResultType resultType = this->compareQuadQuad(quad, quadPts); ResultType resultType = this->compareQuadQuad(quad, quadPts);
if (kQuad_ResultType == resultType) { if (kQuad_ResultType == resultType) {
@ -1223,7 +1375,7 @@ bool SkPathStroker::quadStroke(const SkPoint quad[3], SkQuadConstruct* quadPts)
void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2, void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
const SkPoint& pt3) { const SkPoint& pt3) {
#if QUAD_STROKE_APPROXIMATION #ifdef SK_QUAD_STROKE_APPROXIMATION
const SkPoint cubic[4] = { fPrevPt, pt1, pt2, pt3 }; const SkPoint cubic[4] = { fPrevPt, pt1, pt2, pt3 };
SkPoint reduction[3]; SkPoint reduction[3];
const SkPoint* tangentPt; const SkPoint* tangentPt;
@ -1349,13 +1501,6 @@ SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
} }
#if QUAD_STROKE_APPROXIMATION
void SkStroke::setError(SkScalar error) {
SkASSERT(error > 0);
fError = error;
}
#endif
void SkStroke::setWidth(SkScalar width) { void SkStroke::setWidth(SkScalar width) {
SkASSERT(width >= 0); SkASSERT(width >= 0);
fWidth = width; fWidth = width;
@ -1432,14 +1577,10 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
} }
SkAutoConicToQuads converter; SkAutoConicToQuads converter;
#ifndef SK_QUAD_STROKE_APPROXIMATION
const SkScalar conicTol = SK_Scalar1 / 4 / fResScale; const SkScalar conicTol = SK_Scalar1 / 4 / fResScale;
#if QUAD_STROKE_APPROXIMATION
SkPathStroker stroker(src, radius, fMiterLimit, fError, this->getCap(),
this->getJoin(), fResScale);
#else
SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(), fResScale);
#endif #endif
SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(), fResScale);
SkPath::Iter iter(src, false); SkPath::Iter iter(src, false);
SkPath::Verb lastSegment = SkPath::kMove_Verb; SkPath::Verb lastSegment = SkPath::kMove_Verb;
@ -1458,6 +1599,11 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
lastSegment = SkPath::kQuad_Verb; lastSegment = SkPath::kQuad_Verb;
break; break;
case SkPath::kConic_Verb: { case SkPath::kConic_Verb: {
#ifdef SK_QUAD_STROKE_APPROXIMATION
stroker.conicTo(pts[1], pts[2], iter.conicWeight());
lastSegment = SkPath::kConic_Verb;
break;
#else
// todo: if we had maxcurvature for conics, perhaps we should // todo: if we had maxcurvature for conics, perhaps we should
// natively extrude the conic instead of converting to quads. // natively extrude the conic instead of converting to quads.
const SkPoint* quadPts = const SkPoint* quadPts =
@ -1467,6 +1613,7 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
quadPts += 2; quadPts += 2;
} }
lastSegment = SkPath::kQuad_Verb; lastSegment = SkPath::kQuad_Verb;
#endif
} break; } break;
case SkPath::kCubic_Verb: case SkPath::kCubic_Verb:
stroker.cubicTo(pts[1], pts[2], pts[3]); stroker.cubicTo(pts[1], pts[2], pts[3]);

View File

@ -13,15 +13,9 @@
#include "SkPaint.h" #include "SkPaint.h"
#include "SkStrokerPriv.h" #include "SkStrokerPriv.h"
// set to 1 to use experimental outset stroking with quads #if defined SK_QUAD_STROKE_APPROXIMATION && defined SK_DEBUG
#ifndef QUAD_STROKE_APPROXIMATION
#define QUAD_STROKE_APPROXIMATION 0
#endif
#if QUAD_STROKE_APPROXIMATION && defined(SK_DEBUG)
extern bool gDebugStrokerErrorSet; extern bool gDebugStrokerErrorSet;
extern SkScalar gDebugStrokerError; extern SkScalar gDebugStrokerError;
extern int gMaxRecursion[]; extern int gMaxRecursion[];
#endif #endif
@ -43,9 +37,6 @@ public:
SkPaint::Join getJoin() const { return (SkPaint::Join)fJoin; } SkPaint::Join getJoin() const { return (SkPaint::Join)fJoin; }
void setJoin(SkPaint::Join); void setJoin(SkPaint::Join);
#if QUAD_STROKE_APPROXIMATION
void setError(SkScalar);
#endif
void setMiterLimit(SkScalar); void setMiterLimit(SkScalar);
void setWidth(SkScalar); void setWidth(SkScalar);
@ -76,9 +67,6 @@ public:
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
private: private:
#if QUAD_STROKE_APPROXIMATION
SkScalar fError;
#endif
SkScalar fWidth, fMiterLimit; SkScalar fWidth, fMiterLimit;
SkScalar fResScale; SkScalar fResScale;
uint8_t fCap, fJoin; uint8_t fCap, fJoin;

View File

@ -100,6 +100,12 @@ void SkStrokeRec::setStrokeStyle(SkScalar width, bool strokeAndFill) {
#include "SkStroke.h" #include "SkStroke.h"
#if defined SK_QUAD_STROKE_APPROXIMATION && defined SK_DEBUG
// enables tweaking these values at runtime from SampleApp
bool gDebugStrokerErrorSet = false;
SkScalar gDebugStrokerError;
#endif
bool SkStrokeRec::applyToPath(SkPath* dst, const SkPath& src) const { bool SkStrokeRec::applyToPath(SkPath* dst, const SkPath& src) const {
if (fWidth <= 0) { // hairline or fill if (fWidth <= 0) { // hairline or fill
return false; return false;
@ -111,9 +117,10 @@ bool SkStrokeRec::applyToPath(SkPath* dst, const SkPath& src) const {
stroker.setMiterLimit(fMiterLimit); stroker.setMiterLimit(fMiterLimit);
stroker.setWidth(fWidth); stroker.setWidth(fWidth);
stroker.setDoFill(fStrokeAndFill); stroker.setDoFill(fStrokeAndFill);
#if defined SK_QUAD_STROKE_APPROXIMATION && defined SK_DEBUG
stroker.setResScale(gDebugStrokerErrorSet ? gDebugStrokerError : fResScale);
#else
stroker.setResScale(fResScale); stroker.setResScale(fResScale);
#if QUAD_STROKE_APPROXIMATION
stroker.setError(1);
#endif #endif
stroker.strokePath(src, dst); stroker.strokePath(src, dst);
return true; return true;