/*
* Copyright 2008 The Android Open Source Project
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkStrokerPriv.h"
#include "SkGeometry.h"
#include "SkPath.h"
#define kMaxQuadSubdivide 5
#define kMaxCubicSubdivide 7
static inline bool degenerate_vector(const SkVector& v) {
return !SkPoint::CanNormalize(v.fX, v.fY);
}
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)
*/
static const SkScalar kFlatEnoughNormalDotProd =
SK_ScalarSqrt2/2 + SK_Scalar1/10;
SkASSERT(kFlatEnoughNormalDotProd > 0 &&
kFlatEnoughNormalDotProd < SK_Scalar1);
return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd;
}
static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) {
// if the dot-product is -1, then we are definitely too pinchy. We tweak
// that by an epsilon to ensure we have significant bits in our test
static const int kMinSigBitsForDot = 8;
static const SkScalar kDotEpsilon = FLT_EPSILON * (1 << kMinSigBitsForDot);
static const SkScalar kTooPinchyNormalDotProd = kDotEpsilon - 1;
// just some sanity asserts to help document the expected range
SkASSERT(kTooPinchyNormalDotProd >= -1);
SkASSERT(kTooPinchyNormalDotProd < SkDoubleToScalar(-0.999));
SkScalar dot = SkPoint::DotProduct(norm0, norm1);
return dot <= kTooPinchyNormalDotProd;
}
static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after,
SkScalar radius,
SkVector* normal, SkVector* unitNormal) {
if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) {
return false;
}
unitNormal->rotateCCW();
unitNormal->scale(radius, normal);
return true;
}
static bool set_normal_unitnormal(const SkVector& vec,
SkScalar radius,
SkVector* normal, SkVector* unitNormal) {
if (!unitNormal->setNormalize(vec.fX, vec.fY)) {
return false;
}
unitNormal->rotateCCW();
unitNormal->scale(radius, normal);
return true;
}
///////////////////////////////////////////////////////////////////////////////
class SkPathStroker {
public:
SkPathStroker(const SkPath& src,
SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap,
SkPaint::Join join);
void moveTo(const SkPoint&);
void lineTo(const SkPoint&);
void quadTo(const SkPoint&, const SkPoint&);
void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&);
void close(bool isLine) { this->finishContour(true, isLine); }
void done(SkPath* dst, bool isLine) {
this->finishContour(false, isLine);
fOuter.addPath(fExtra);
dst->swap(fOuter);
}
private:
SkScalar fRadius;
SkScalar fInvMiterLimit;
SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal;
SkPoint fFirstPt, fPrevPt; // on original path
SkPoint fFirstOuterPt;
int fSegmentCount;
bool fPrevIsLine;
SkStrokerPriv::CapProc fCapper;
SkStrokerPriv::JoinProc fJoiner;
SkPath fInner, fOuter; // outer is our working answer, inner is temp
SkPath fExtra; // added as extra complete contours
void finishContour(bool close, bool isLine);
void preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal,
bool isLine);
void postJoinTo(const SkPoint&, const SkVector& normal,
const SkVector& unitNormal);
void line_to(const SkPoint& currPt, const SkVector& normal);
void quad_to(const SkPoint pts[3],
const SkVector& normalAB, const SkVector& unitNormalAB,
SkVector* normalBC, SkVector* unitNormalBC,
int subDivide);
void cubic_to(const SkPoint pts[4],
const SkVector& normalAB, const SkVector& unitNormalAB,
SkVector* normalCD, SkVector* unitNormalCD,
int subDivide);
};
///////////////////////////////////////////////////////////////////////////////
void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal,
SkVector* unitNormal, bool currIsLine) {
SkASSERT(fSegmentCount >= 0);
SkScalar prevX = fPrevPt.fX;
SkScalar prevY = fPrevPt.fY;
SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal,
unitNormal));
if (fSegmentCount == 0) {
fFirstNormal = *normal;
fFirstUnitNormal = *unitNormal;
fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY);
fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY);
fInner.moveTo(prevX - normal->fX, prevY - normal->fY);
} else { // we have a previous segment
fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal,
fRadius, fInvMiterLimit, fPrevIsLine, currIsLine);
}
fPrevIsLine = currIsLine;
}
void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal,
const SkVector& unitNormal) {
fPrevPt = currPt;
fPrevUnitNormal = unitNormal;
fPrevNormal = normal;
fSegmentCount += 1;
}
void SkPathStroker::finishContour(bool close, bool currIsLine) {
if (fSegmentCount > 0) {
SkPoint pt;
if (close) {
fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt,
fFirstUnitNormal, fRadius, fInvMiterLimit,
fPrevIsLine, currIsLine);
fOuter.close();
// now add fInner as its own contour
fInner.getLastPt(&pt);
fOuter.moveTo(pt.fX, pt.fY);
fOuter.reversePathTo(fInner);
fOuter.close();
} else { // add caps to start and end
// cap the end
fInner.getLastPt(&pt);
fCapper(&fOuter, fPrevPt, fPrevNormal, pt,
currIsLine ? &fInner : NULL);
fOuter.reversePathTo(fInner);
// cap the start
fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt,
fPrevIsLine ? &fInner : NULL);
fOuter.close();
}
}
// since we may re-use fInner, we rewind instead of reset, to save on
// reallocating its internal storage.
fInner.rewind();
fSegmentCount = -1;
}
///////////////////////////////////////////////////////////////////////////////
SkPathStroker::SkPathStroker(const SkPath& src,
SkScalar radius, SkScalar miterLimit,
SkPaint::Cap cap, SkPaint::Join join)
: fRadius(radius) {
/* This is only used when join is miter_join, but we initialize it here
so that it is always defined, to fis valgrind warnings.
*/
fInvMiterLimit = 0;
if (join == SkPaint::kMiter_Join) {
if (miterLimit <= SK_Scalar1) {
join = SkPaint::kBevel_Join;
} else {
fInvMiterLimit = SkScalarInvert(miterLimit);
}
}
fCapper = SkStrokerPriv::CapFactory(cap);
fJoiner = SkStrokerPriv::JoinFactory(join);
fSegmentCount = -1;
fPrevIsLine = false;
// Need some estimate of how large our final result (fOuter)
// and our per-contour temp (fInner) will be, so we don't spend
// extra time repeatedly growing these arrays.
//
// 3x for result == inner + outer + join (swag)
// 1x for inner == 'wag' (worst contour length would be better guess)
fOuter.incReserve(src.countPoints() * 3);
fInner.incReserve(src.countPoints());
}
void SkPathStroker::moveTo(const SkPoint& pt) {
if (fSegmentCount > 0) {
this->finishContour(false, false);
}
fSegmentCount = 0;
fFirstPt = fPrevPt = pt;
}
void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY);
fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY);
}
void SkPathStroker::lineTo(const SkPoint& currPt) {
if (SkPath::IsLineDegenerate(fPrevPt, currPt)) {
return;
}
SkVector normal, unitNormal;
this->preJoinTo(currPt, &normal, &unitNormal, true);
this->line_to(currPt, normal);
this->postJoinTo(currPt, normal, unitNormal);
}
void SkPathStroker::quad_to(const SkPoint pts[3],
const SkVector& normalAB, const SkVector& unitNormalAB,
SkVector* normalBC, SkVector* unitNormalBC,
int subDivide) {
if (!set_normal_unitnormal(pts[1], pts[2], fRadius,
normalBC, unitNormalBC)) {
// pts[1] nearly equals pts[2], so just draw a line to pts[2]
this->line_to(pts[2], normalAB);
*normalBC = normalAB;
*unitNormalBC = unitNormalAB;
return;
}
if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) {
SkPoint tmp[5];
SkVector norm, unit;
SkChopQuadAtHalf(pts, tmp);
this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide);
this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide);
} else {
SkVector normalB;
normalB = pts[2] - pts[0];
normalB.rotateCCW();
SkScalar dot = SkPoint::DotProduct(unitNormalAB, *unitNormalBC);
SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
SkScalarSqrt((SK_Scalar1 + dot)/2))));
fOuter.quadTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY);
fInner.quadTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY);
}
}
void SkPathStroker::cubic_to(const SkPoint pts[4],
const SkVector& normalAB, const SkVector& unitNormalAB,
SkVector* normalCD, SkVector* unitNormalCD,
int subDivide) {
SkVector ab = pts[1] - pts[0];
SkVector cd = pts[3] - pts[2];
SkVector normalBC, unitNormalBC;
bool degenerateAB = degenerate_vector(ab);
bool degenerateCD = degenerate_vector(cd);
if (degenerateAB && degenerateCD) {
DRAW_LINE:
this->line_to(pts[3], normalAB);
*normalCD = normalAB;
*unitNormalCD = unitNormalAB;
return;
}
if (degenerateAB) {
ab = pts[2] - pts[0];
degenerateAB = degenerate_vector(ab);
}
if (degenerateCD) {
cd = pts[3] - pts[1];
degenerateCD = degenerate_vector(cd);
}
if (degenerateAB || degenerateCD) {
goto DRAW_LINE;
}
SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD));
bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius,
&normalBC, &unitNormalBC);
#ifndef SK_IGNORE_CUBIC_STROKE_FIX
if (--subDivide < 0) {
goto DRAW_LINE;
}
#endif
if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) ||
normals_too_curvy(unitNormalBC, *unitNormalCD)) {
#ifdef SK_IGNORE_CUBIC_STROKE_FIX
// subdivide if we can
if (--subDivide < 0) {
goto DRAW_LINE;
}
#endif
SkPoint tmp[7];
SkVector norm, unit, dummy, unitDummy;
SkChopCubicAtHalf(pts, tmp);
this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit,
subDivide);
// we use dummys since we already have a valid (and more accurate)
// normals for CD
this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide);
} else {
SkVector normalB, normalC;
// need normals to inset/outset the off-curve pts B and C
SkVector unitBC = pts[2] - pts[1];
unitBC.normalize();
unitBC.rotateCCW();
normalB = unitNormalAB + unitBC;
normalC = *unitNormalCD + unitBC;
SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC);
SkAssertResult(normalB.setLength(SkScalarDiv(fRadius,
SkScalarSqrt((SK_Scalar1 + dot)/2))));
dot = SkPoint::DotProduct(*unitNormalCD, unitBC);
SkAssertResult(normalC.setLength(SkScalarDiv(fRadius,
SkScalarSqrt((SK_Scalar1 + dot)/2))));
fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY,
pts[2].fX + normalC.fX, pts[2].fY + normalC.fY,
pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY);
fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY,
pts[2].fX - normalC.fX, pts[2].fY - normalC.fY,
pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY);
}
}
void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
if (degenerateAB | degenerateBC) {
if (degenerateAB ^ degenerateBC) {
this->lineTo(pt2);
}
return;
}
SkVector normalAB, unitAB, normalBC, unitBC;
this->preJoinTo(pt1, &normalAB, &unitAB, false);
{
SkPoint pts[3], tmp[5];
pts[0] = fPrevPt;
pts[1] = pt1;
pts[2] = pt2;
if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) {
unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY);
unitBC.rotateCCW();
if (normals_too_pinchy(unitAB, unitBC)) {
normalBC = unitBC;
normalBC.scale(fRadius);
fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY);
fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY);
fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY);
fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY);
fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY);
fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY);
fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius,
SkPath::kCW_Direction);
} else {
this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC,
kMaxQuadSubdivide);
SkVector n = normalBC;
SkVector u = unitBC;
this->quad_to(&tmp[2], n, u, &normalBC, &unitBC,
kMaxQuadSubdivide);
}
} else {
this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC,
kMaxQuadSubdivide);
}
}
this->postJoinTo(pt2, normalBC, unitBC);
}
void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
const SkPoint& pt3) {
bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
bool degenerateCD = SkPath::IsLineDegenerate(pt2, pt3);
if (degenerateAB + degenerateBC + degenerateCD >= 2) {
this->lineTo(pt3);
return;
}
SkVector normalAB, unitAB, normalCD, unitCD;
// find the first tangent (which might be pt1 or pt2
{
const SkPoint* nextPt = &pt1;
if (degenerateAB)
nextPt = &pt2;
this->preJoinTo(*nextPt, &normalAB, &unitAB, false);
}
{
SkPoint pts[4], tmp[13];
int i, count;
SkVector n, u;
SkScalar tValues[3];
pts[0] = fPrevPt;
pts[1] = pt1;
pts[2] = pt2;
pts[3] = pt3;
count = SkChopCubicAtMaxCurvature(pts, tmp, tValues);
n = normalAB;
u = unitAB;
for (i = 0; i < count; i++) {
this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD,
kMaxCubicSubdivide);
if (i == count - 1) {
break;
}
n = normalCD;
u = unitCD;
}
}
this->postJoinTo(pt3, normalCD, unitCD);
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#include "SkPaintDefaults.h"
SkStroke::SkStroke() {
fWidth = SK_Scalar1;
fMiterLimit = SkPaintDefaults_MiterLimit;
fCap = SkPaint::kDefault_Cap;
fJoin = SkPaint::kDefault_Join;
fDoFill = false;
}
SkStroke::SkStroke(const SkPaint& p) {
fWidth = p.getStrokeWidth();
fMiterLimit = p.getStrokeMiter();
fCap = (uint8_t)p.getStrokeCap();
fJoin = (uint8_t)p.getStrokeJoin();
fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
}
SkStroke::SkStroke(const SkPaint& p, SkScalar width) {
fWidth = width;
fMiterLimit = p.getStrokeMiter();
fCap = (uint8_t)p.getStrokeCap();
fJoin = (uint8_t)p.getStrokeJoin();
fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style);
}
void SkStroke::setWidth(SkScalar width) {
SkASSERT(width >= 0);
fWidth = width;
}
void SkStroke::setMiterLimit(SkScalar miterLimit) {
SkASSERT(miterLimit >= 0);
fMiterLimit = miterLimit;
}
void SkStroke::setCap(SkPaint::Cap cap) {
SkASSERT((unsigned)cap < SkPaint::kCapCount);
fCap = SkToU8(cap);
}
void SkStroke::setJoin(SkPaint::Join join) {
SkASSERT((unsigned)join < SkPaint::kJoinCount);
fJoin = SkToU8(join);
}
///////////////////////////////////////////////////////////////////////////////
// If src==dst, then we use a tmp path to record the stroke, and then swap
// its contents with src when we're done.
class AutoTmpPath {
public:
AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) {
if (&src == *dst) {
*dst = &fTmpDst;
fSwapWithSrc = true;
} else {
(*dst)->reset();
fSwapWithSrc = false;
}
}
~AutoTmpPath() {
if (fSwapWithSrc) {
fTmpDst.swap(*const_cast<SkPath*>(&fSrc));
}
}
private:
SkPath fTmpDst;
const SkPath& fSrc;
bool fSwapWithSrc;
};
void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
SkASSERT(&src != NULL && dst != NULL);
SkScalar radius = SkScalarHalf(fWidth);
AutoTmpPath tmp(src, &dst);
if (radius <= 0) {
return;
}
// If src is really a rect, call our specialty strokeRect() method
{
bool isClosed;
SkPath::Direction dir;
if (src.isRect(&isClosed, &dir) && isClosed) {
this->strokeRect(src.getBounds(), dst, dir);
// our answer should preserve the inverseness of the src
if (src.isInverseFillType()) {
SkASSERT(!dst->isInverseFillType());
dst->toggleInverseFillType();
}
return;
}
}
SkAutoConicToQuads converter;
const SkScalar conicTol = SK_Scalar1 / 4;
SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(),
this->getJoin());
SkPath::Iter iter(src, false);
SkPath::Verb lastSegment = SkPath::kMove_Verb;
for (;;) {
SkPoint pts[4];
switch (iter.next(pts, false)) {
case SkPath::kMove_Verb:
stroker.moveTo(pts[0]);
break;
case SkPath::kLine_Verb:
stroker.lineTo(pts[1]);
lastSegment = SkPath::kLine_Verb;
break;
case SkPath::kQuad_Verb:
stroker.quadTo(pts[1], pts[2]);
lastSegment = SkPath::kQuad_Verb;
break;
case SkPath::kConic_Verb: {
// todo: if we had maxcurvature for conics, perhaps we should
// natively extrude the conic instead of converting to quads.
const SkPoint* quadPts =
converter.computeQuads(pts, iter.conicWeight(), conicTol);
for (int i = 0; i < converter.countQuads(); ++i) {
stroker.quadTo(quadPts[1], quadPts[2]);
quadPts += 2;
}
lastSegment = SkPath::kQuad_Verb;
} break;
case SkPath::kCubic_Verb:
stroker.cubicTo(pts[1], pts[2], pts[3]);
lastSegment = SkPath::kCubic_Verb;
break;
case SkPath::kClose_Verb:
stroker.close(lastSegment == SkPath::kLine_Verb);
break;
case SkPath::kDone_Verb:
goto DONE;
}
}
DONE:
stroker.done(dst, lastSegment == SkPath::kLine_Verb);
if (fDoFill) {
if (src.cheapIsDirection(SkPath::kCCW_Direction)) {
dst->reverseAddPath(src);
} else {
dst->addPath(src);
}
} else {
// Seems like we can assume that a 2-point src would always result in
// a convex stroke, but testing has proved otherwise.
// TODO: fix the stroker to make this assumption true (without making
// it slower that the work that will be done in computeConvexity())
#if 0
// this test results in a non-convex stroke :(
static void test(SkCanvas* canvas) {
SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 };
SkPaint paint;
paint.setStrokeWidth(7);
paint.setStrokeCap(SkPaint::kRound_Cap);
canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
}
#endif
#if 0
if (2 == src.countPoints()) {
dst->setIsConvex(true);
}
#endif
}
// our answer should preserve the inverseness of the src
if (src.isInverseFillType()) {
SkASSERT(!dst->isInverseFillType());
dst->toggleInverseFillType();
}
}
static SkPath::Direction reverse_direction(SkPath::Direction dir) {
SkASSERT(SkPath::kUnknown_Direction != dir);
return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
}
static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) {
SkPoint pts[8];
if (SkPath::kCW_Direction == dir) {
pts[0].set(r.fLeft, outer.fTop);
pts[1].set(r.fRight, outer.fTop);
pts[2].set(outer.fRight, r.fTop);
pts[3].set(outer.fRight, r.fBottom);
pts[4].set(r.fRight, outer.fBottom);
pts[5].set(r.fLeft, outer.fBottom);
pts[6].set(outer.fLeft, r.fBottom);
pts[7].set(outer.fLeft, r.fTop);
} else {
pts[7].set(r.fLeft, outer.fTop);
pts[6].set(r.fRight, outer.fTop);
pts[5].set(outer.fRight, r.fTop);
pts[4].set(outer.fRight, r.fBottom);
pts[3].set(r.fRight, outer.fBottom);
pts[2].set(r.fLeft, outer.fBottom);
pts[1].set(outer.fLeft, r.fBottom);
pts[0].set(outer.fLeft, r.fTop);
}
path->addPoly(pts, 8, true);
}
void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst,
SkPath::Direction dir) const {
SkASSERT(dst != NULL);
dst->reset();
SkScalar radius = SkScalarHalf(fWidth);
if (radius <= 0) {
return;
}
SkScalar rw = origRect.width();
SkScalar rh = origRect.height();
if ((rw < 0) ^ (rh < 0)) {
dir = reverse_direction(dir);
}
SkRect rect(origRect);
rect.sort();
// reassign these, now that we know they'll be >= 0
rw = rect.width();
rh = rect.height();
SkRect r(rect);
r.outset(radius, radius);
SkPaint::Join join = (SkPaint::Join)fJoin;
if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) {
join = SkPaint::kBevel_Join;
}
switch (join) {
case SkPaint::kMiter_Join:
dst->addRect(r, dir);
break;
case SkPaint::kBevel_Join:
addBevel(dst, rect, r, dir);
break;
case SkPaint::kRound_Join:
dst->addRoundRect(r, radius, radius, dir);
break;
default:
break;
}
if (fWidth < SkMinScalar(rw, rh) && !fDoFill) {
r = rect;
r.inset(radius, radius);
dst->addRect(r, reverse_direction(dir));
}
}