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