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_GAMMA_SRGB',
'SK_GAMMA_APPLY_TO_A8',
'SK_QUAD_STROKE_APPROXIMATION',
'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.
],

View File

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

View File

@ -9,26 +9,26 @@
#include "SkGeometry.h"
#include "SkPath.h"
#if QUAD_STROKE_APPROXIMATION
#ifdef SK_QUAD_STROKE_APPROXIMATION
enum {
kTangent_RecursiveLimit,
kCubic_RecursiveLimit,
kConic_RecursiveLimit,
kQuad_RecursiveLimit
};
// 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 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,
recursive_limits_mismatch);
#ifdef SK_DEBUG // enables tweaking these values at runtime from SampleApp
bool gDebugStrokerErrorSet = false;
SkScalar gDebugStrokerError;
#ifdef SK_DEBUG
int gMaxRecursion[SK_ARRAY_COUNT(kRecursiveLimits)] = { 0 };
#endif
#ifndef DEBUG_QUAD_STROKER
@ -51,14 +51,16 @@
#endif
#ifndef SK_QUAD_STROKE_APPROXIMATION
#define kMaxQuadSubdivide 5
#define kMaxCubicSubdivide 7
#endif
static inline bool degenerate_vector(const SkVector& v) {
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) {
/* root2/2 is a 45-degree angle
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.
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 {
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,
SkScalar radius, SkScalar miterLimit, SkPaint::Cap,
SkPaint::Join, SkScalar resScale);
#endif
void moveTo(const SkPoint&);
void lineTo(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 close(bool isLine) { this->finishContour(true, isLine); }
@ -181,12 +180,13 @@ public:
SkScalar getResScale() const { return fResScale; }
private:
#if QUAD_STROKE_APPROXIMATION
SkScalar fError;
#endif
SkScalar fRadius;
SkScalar fInvMiterLimit;
SkScalar fResScale;
#ifdef SK_QUAD_STROKE_APPROXIMATION
SkScalar fInvResScale;
SkScalar fInvResScaleSquared;
#endif
SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
SkPoint fFirstPt, fPrevPt; // on original path
@ -200,7 +200,7 @@ private:
SkPath fInner, fOuter; // outer is our working answer, inner is temp
SkPath fExtra; // added as extra complete contours
#if QUAD_STROKE_APPROXIMATION
#ifdef SK_QUAD_STROKE_APPROXIMATION
enum StrokeType {
kOuter_StrokeType = 1, // use sign-opposite values later to flip perpendicular axis
kInner_StrokeType = -1
@ -231,11 +231,17 @@ private:
bool fFoundTangents; // do less work until tangents meet (cubic)
void addDegenerateLine(const SkQuadConstruct* );
ReductionType CheckConicLinear(const SkConic& , SkPoint* reduction);
ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3],
const SkPoint** tanPtPtr);
ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction);
ResultType compareQuadConic(const SkConic& , SkQuadConstruct* );
ResultType compareQuadCubic(const SkPoint cubic[4], 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 cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
SkPoint* tangent) const;
@ -248,6 +254,9 @@ private:
void quadPerpRay(const SkPoint quad[3], SkScalar t, SkPoint* tPt, SkPoint* onPt,
SkPoint* tangent) const;
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],
const SkVector& normalAB, const SkVector& unitNormalAB,
SkVector* normalCD, SkVector* unitNormalCD);
@ -268,7 +277,7 @@ private:
const SkVector& unitNormal);
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],
const SkVector& normalAB, const SkVector& unitNormalAB,
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,
SkScalar radius, SkScalar miterLimit,
SkPaint::Cap cap, SkPaint::Join join, SkScalar resScale)
#endif
: fRadius(radius), fResScale(resScale) {
: fRadius(radius)
, fResScale(resScale) {
/* This is only used when join is miter_join, but we initialize it here
so that it is always defined, to fis valgrind warnings.
@ -386,15 +390,11 @@ SkPathStroker::SkPathStroker(const SkPath& src,
fOuter.setIsVolatile(true);
fInner.incReserve(src.countPoints());
fInner.setIsVolatile(true);
#if QUAD_STROKE_APPROXIMATION
#ifdef SK_DEBUG
if (!gDebugStrokerErrorSet) {
gDebugStrokerError = error;
}
fError = gDebugStrokerError;
#else
fError = error;
#endif
#ifdef SK_QUAD_STROKE_APPROXIMATION
// TODO : write a common error function used by stroking and filling
// The '4' below matches the fill scan converter's error term
fInvResScale = SkScalarInvert(resScale * 4);
fInvResScaleSquared = fInvResScale * fInvResScale;
fRecursionDepth = 0;
#endif
}
@ -423,7 +423,7 @@ void SkPathStroker::lineTo(const SkPoint& currPt) {
this->postJoinTo(currPt, normal, unitNormal);
}
#if !QUAD_STROKE_APPROXIMATION
#ifndef SK_QUAD_STROKE_APPROXIMATION
void SkPathStroker::quad_to(const SkPoint pts[3],
const SkVector& normalAB, const SkVector& unitNormalAB,
SkVector* normalBC, SkVector* unitNormalBC,
@ -461,7 +461,7 @@ void SkPathStroker::quad_to(const SkPoint pts[3],
}
#endif
#if QUAD_STROKE_APPROXIMATION
#ifdef SK_QUAD_STROKE_APPROXIMATION
void SkPathStroker::setQuadEndNormal(const SkPoint quad[3], const SkVector& normalAB,
const SkVector& unitNormalAB, SkVector* normalBC, SkVector* 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,
const SkVector& unitNormalAB, SkVector* normalCD, SkVector* unitNormalCD) {
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]) {
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 inner = index + 1; inner < 4; ++inner) {
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]) {
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 inner = index + 1; inner < 3; ++inner) {
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;
}
static bool conic_in_line(const SkConic& conic) {
return quad_in_line(conic.fPts);
}
SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic[4],
SkPoint reduction[3], const SkPoint** tangentPtPtr) {
bool degenerateAB = degenerate_vector(cubic[1] - cubic[0]);
@ -634,6 +645,27 @@ SkPathStroker::ReductionType SkPathStroker::CheckCubicLinear(const SkPoint cubic
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],
SkPoint* reduction) {
bool degenerateAB = degenerate_vector(quad[1] - quad[0]);
@ -742,8 +774,45 @@ DRAW_LINE:
}
#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) {
#if QUAD_STROKE_APPROXIMATION
#ifdef SK_QUAD_STROKE_APPROXIMATION
const SkPoint quad[3] = { fPrevPt, pt1, pt2 };
SkPoint reduction;
ReductionType reductionType = CheckQuadLinear(quad, &reduction);
@ -831,7 +900,7 @@ void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
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
// compute the perpendicular point and its tangent.
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.
// 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,
@ -930,10 +1034,6 @@ SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts,
// are small, a straight line is good enough
SkScalar dist1 = pt_to_line(start, end, quadPts->fTangentEnd);
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 (kCtrlPt_RayType == intersectRayType) {
numerA /= denom;
@ -944,6 +1044,10 @@ SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts,
return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
"(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,
"(numerA=%g >= 0) == (numerB=%g >= 0)", numerA, numerB);
} 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.
bool SkPathStroker::ptInQuadBounds(const SkPoint quad[3], const SkPoint& pt) const {
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;
}
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;
}
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;
}
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 true;
@ -1022,7 +1126,7 @@ SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[
SkPoint strokeMid;
SkEvalQuadAt(stroke, SK_ScalarHalf, &strokeMid);
// 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)) {
return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
"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);
}
return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
"points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fError)",
ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY);
"points_within_dist(ray[0]=%g,%g, strokeMid=%g,%g, fInvResScaleSquared=%g)",
ray[0].fX, ray[0].fY, strokeMid.fX, strokeMid.fY, fInvResScaleSquared);
}
// measure the distance to quad's bounds (quick reject)
// an alternative : look for point in triangle
@ -1051,7 +1155,7 @@ SkPathStroker::ResultType SkPathStroker::strokeCloseEnough(const SkPoint stroke[
}
SkPoint 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 (sharp_angle(quadPts->fQuad)) {
return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
@ -1080,14 +1184,32 @@ SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4]
return resultType;
}
// 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)) {
return kNormalError_ResultType;
}
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],
SkQuadConstruct* quadPts) {
// get the quadratic approximation of the stroke
@ -1126,7 +1248,7 @@ bool SkPathStroker::cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct
return false;
}
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) {
@ -1137,8 +1259,8 @@ bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts
return false;
}
if ((kDegenerate_ResultType == resultType
|| points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2], fError))
&& cubicMidOnLine(cubic, quadPts)) {
|| points_within_dist(quadPts->fQuad[0], quadPts->fQuad[2],
fInvResScaleSquared)) && cubicMidOnLine(cubic, quadPts)) {
addDegenerateLine(quadPts);
return true;
}
@ -1189,6 +1311,36 @@ bool SkPathStroker::cubicStroke(const SkPoint cubic[4], SkQuadConstruct* quadPts
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) {
ResultType resultType = this->compareQuadQuad(quad, quadPts);
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,
const SkPoint& pt3) {
#if QUAD_STROKE_APPROXIMATION
#ifdef SK_QUAD_STROKE_APPROXIMATION
const SkPoint cubic[4] = { fPrevPt, pt1, pt2, pt3 };
SkPoint reduction[3];
const SkPoint* tangentPt;
@ -1349,13 +1501,6 @@ SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
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) {
SkASSERT(width >= 0);
fWidth = width;
@ -1432,14 +1577,10 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
}
SkAutoConicToQuads converter;
#ifndef SK_QUAD_STROKE_APPROXIMATION
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
SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(), fResScale);
SkPath::Iter iter(src, false);
SkPath::Verb lastSegment = SkPath::kMove_Verb;
@ -1458,6 +1599,11 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
lastSegment = SkPath::kQuad_Verb;
break;
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
// natively extrude the conic instead of converting to quads.
const SkPoint* quadPts =
@ -1467,6 +1613,7 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
quadPts += 2;
}
lastSegment = SkPath::kQuad_Verb;
#endif
} break;
case SkPath::kCubic_Verb:
stroker.cubicTo(pts[1], pts[2], pts[3]);

View File

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

View File

@ -100,6 +100,12 @@ void SkStrokeRec::setStrokeStyle(SkScalar width, bool strokeAndFill) {
#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 {
if (fWidth <= 0) { // hairline or fill
return false;
@ -111,9 +117,10 @@ bool SkStrokeRec::applyToPath(SkPath* dst, const SkPath& src) const {
stroker.setMiterLimit(fMiterLimit);
stroker.setWidth(fWidth);
stroker.setDoFill(fStrokeAndFill);
#if defined SK_QUAD_STROKE_APPROXIMATION && defined SK_DEBUG
stroker.setResScale(gDebugStrokerErrorSet ? gDebugStrokerError : fResScale);
#else
stroker.setResScale(fResScale);
#if QUAD_STROKE_APPROXIMATION
stroker.setError(1);
#endif
stroker.strokePath(src, dst);
return true;