1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/skia/trunk/src/core/SkStroke.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,734 @@ 1.4 +/* 1.5 + * Copyright 2008 The Android Open Source Project 1.6 + * 1.7 + * Use of this source code is governed by a BSD-style license that can be 1.8 + * found in the LICENSE file. 1.9 + */ 1.10 + 1.11 +#include "SkStrokerPriv.h" 1.12 +#include "SkGeometry.h" 1.13 +#include "SkPath.h" 1.14 + 1.15 +#define kMaxQuadSubdivide 5 1.16 +#define kMaxCubicSubdivide 7 1.17 + 1.18 +static inline bool degenerate_vector(const SkVector& v) { 1.19 + return !SkPoint::CanNormalize(v.fX, v.fY); 1.20 +} 1.21 + 1.22 +static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) { 1.23 + /* root2/2 is a 45-degree angle 1.24 + make this constant bigger for more subdivisions (but not >= 1) 1.25 + */ 1.26 + static const SkScalar kFlatEnoughNormalDotProd = 1.27 + SK_ScalarSqrt2/2 + SK_Scalar1/10; 1.28 + 1.29 + SkASSERT(kFlatEnoughNormalDotProd > 0 && 1.30 + kFlatEnoughNormalDotProd < SK_Scalar1); 1.31 + 1.32 + return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd; 1.33 +} 1.34 + 1.35 +static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) { 1.36 + // if the dot-product is -1, then we are definitely too pinchy. We tweak 1.37 + // that by an epsilon to ensure we have significant bits in our test 1.38 + static const int kMinSigBitsForDot = 8; 1.39 + static const SkScalar kDotEpsilon = FLT_EPSILON * (1 << kMinSigBitsForDot); 1.40 + static const SkScalar kTooPinchyNormalDotProd = kDotEpsilon - 1; 1.41 + 1.42 + // just some sanity asserts to help document the expected range 1.43 + SkASSERT(kTooPinchyNormalDotProd >= -1); 1.44 + SkASSERT(kTooPinchyNormalDotProd < SkDoubleToScalar(-0.999)); 1.45 + 1.46 + SkScalar dot = SkPoint::DotProduct(norm0, norm1); 1.47 + return dot <= kTooPinchyNormalDotProd; 1.48 +} 1.49 + 1.50 +static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after, 1.51 + SkScalar radius, 1.52 + SkVector* normal, SkVector* unitNormal) { 1.53 + if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) { 1.54 + return false; 1.55 + } 1.56 + unitNormal->rotateCCW(); 1.57 + unitNormal->scale(radius, normal); 1.58 + return true; 1.59 +} 1.60 + 1.61 +static bool set_normal_unitnormal(const SkVector& vec, 1.62 + SkScalar radius, 1.63 + SkVector* normal, SkVector* unitNormal) { 1.64 + if (!unitNormal->setNormalize(vec.fX, vec.fY)) { 1.65 + return false; 1.66 + } 1.67 + unitNormal->rotateCCW(); 1.68 + unitNormal->scale(radius, normal); 1.69 + return true; 1.70 +} 1.71 + 1.72 +/////////////////////////////////////////////////////////////////////////////// 1.73 + 1.74 +class SkPathStroker { 1.75 +public: 1.76 + SkPathStroker(const SkPath& src, 1.77 + SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap, 1.78 + SkPaint::Join join); 1.79 + 1.80 + void moveTo(const SkPoint&); 1.81 + void lineTo(const SkPoint&); 1.82 + void quadTo(const SkPoint&, const SkPoint&); 1.83 + void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&); 1.84 + void close(bool isLine) { this->finishContour(true, isLine); } 1.85 + 1.86 + void done(SkPath* dst, bool isLine) { 1.87 + this->finishContour(false, isLine); 1.88 + fOuter.addPath(fExtra); 1.89 + dst->swap(fOuter); 1.90 + } 1.91 + 1.92 +private: 1.93 + SkScalar fRadius; 1.94 + SkScalar fInvMiterLimit; 1.95 + 1.96 + SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal; 1.97 + SkPoint fFirstPt, fPrevPt; // on original path 1.98 + SkPoint fFirstOuterPt; 1.99 + int fSegmentCount; 1.100 + bool fPrevIsLine; 1.101 + 1.102 + SkStrokerPriv::CapProc fCapper; 1.103 + SkStrokerPriv::JoinProc fJoiner; 1.104 + 1.105 + SkPath fInner, fOuter; // outer is our working answer, inner is temp 1.106 + SkPath fExtra; // added as extra complete contours 1.107 + 1.108 + void finishContour(bool close, bool isLine); 1.109 + void preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal, 1.110 + bool isLine); 1.111 + void postJoinTo(const SkPoint&, const SkVector& normal, 1.112 + const SkVector& unitNormal); 1.113 + 1.114 + void line_to(const SkPoint& currPt, const SkVector& normal); 1.115 + void quad_to(const SkPoint pts[3], 1.116 + const SkVector& normalAB, const SkVector& unitNormalAB, 1.117 + SkVector* normalBC, SkVector* unitNormalBC, 1.118 + int subDivide); 1.119 + void cubic_to(const SkPoint pts[4], 1.120 + const SkVector& normalAB, const SkVector& unitNormalAB, 1.121 + SkVector* normalCD, SkVector* unitNormalCD, 1.122 + int subDivide); 1.123 +}; 1.124 + 1.125 +/////////////////////////////////////////////////////////////////////////////// 1.126 + 1.127 +void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal, 1.128 + SkVector* unitNormal, bool currIsLine) { 1.129 + SkASSERT(fSegmentCount >= 0); 1.130 + 1.131 + SkScalar prevX = fPrevPt.fX; 1.132 + SkScalar prevY = fPrevPt.fY; 1.133 + 1.134 + SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal, 1.135 + unitNormal)); 1.136 + 1.137 + if (fSegmentCount == 0) { 1.138 + fFirstNormal = *normal; 1.139 + fFirstUnitNormal = *unitNormal; 1.140 + fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY); 1.141 + 1.142 + fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY); 1.143 + fInner.moveTo(prevX - normal->fX, prevY - normal->fY); 1.144 + } else { // we have a previous segment 1.145 + fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal, 1.146 + fRadius, fInvMiterLimit, fPrevIsLine, currIsLine); 1.147 + } 1.148 + fPrevIsLine = currIsLine; 1.149 +} 1.150 + 1.151 +void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal, 1.152 + const SkVector& unitNormal) { 1.153 + fPrevPt = currPt; 1.154 + fPrevUnitNormal = unitNormal; 1.155 + fPrevNormal = normal; 1.156 + fSegmentCount += 1; 1.157 +} 1.158 + 1.159 +void SkPathStroker::finishContour(bool close, bool currIsLine) { 1.160 + if (fSegmentCount > 0) { 1.161 + SkPoint pt; 1.162 + 1.163 + if (close) { 1.164 + fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, 1.165 + fFirstUnitNormal, fRadius, fInvMiterLimit, 1.166 + fPrevIsLine, currIsLine); 1.167 + fOuter.close(); 1.168 + // now add fInner as its own contour 1.169 + fInner.getLastPt(&pt); 1.170 + fOuter.moveTo(pt.fX, pt.fY); 1.171 + fOuter.reversePathTo(fInner); 1.172 + fOuter.close(); 1.173 + } else { // add caps to start and end 1.174 + // cap the end 1.175 + fInner.getLastPt(&pt); 1.176 + fCapper(&fOuter, fPrevPt, fPrevNormal, pt, 1.177 + currIsLine ? &fInner : NULL); 1.178 + fOuter.reversePathTo(fInner); 1.179 + // cap the start 1.180 + fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt, 1.181 + fPrevIsLine ? &fInner : NULL); 1.182 + fOuter.close(); 1.183 + } 1.184 + } 1.185 + // since we may re-use fInner, we rewind instead of reset, to save on 1.186 + // reallocating its internal storage. 1.187 + fInner.rewind(); 1.188 + fSegmentCount = -1; 1.189 +} 1.190 + 1.191 +/////////////////////////////////////////////////////////////////////////////// 1.192 + 1.193 +SkPathStroker::SkPathStroker(const SkPath& src, 1.194 + SkScalar radius, SkScalar miterLimit, 1.195 + SkPaint::Cap cap, SkPaint::Join join) 1.196 + : fRadius(radius) { 1.197 + 1.198 + /* This is only used when join is miter_join, but we initialize it here 1.199 + so that it is always defined, to fis valgrind warnings. 1.200 + */ 1.201 + fInvMiterLimit = 0; 1.202 + 1.203 + if (join == SkPaint::kMiter_Join) { 1.204 + if (miterLimit <= SK_Scalar1) { 1.205 + join = SkPaint::kBevel_Join; 1.206 + } else { 1.207 + fInvMiterLimit = SkScalarInvert(miterLimit); 1.208 + } 1.209 + } 1.210 + fCapper = SkStrokerPriv::CapFactory(cap); 1.211 + fJoiner = SkStrokerPriv::JoinFactory(join); 1.212 + fSegmentCount = -1; 1.213 + fPrevIsLine = false; 1.214 + 1.215 + // Need some estimate of how large our final result (fOuter) 1.216 + // and our per-contour temp (fInner) will be, so we don't spend 1.217 + // extra time repeatedly growing these arrays. 1.218 + // 1.219 + // 3x for result == inner + outer + join (swag) 1.220 + // 1x for inner == 'wag' (worst contour length would be better guess) 1.221 + fOuter.incReserve(src.countPoints() * 3); 1.222 + fInner.incReserve(src.countPoints()); 1.223 +} 1.224 + 1.225 +void SkPathStroker::moveTo(const SkPoint& pt) { 1.226 + if (fSegmentCount > 0) { 1.227 + this->finishContour(false, false); 1.228 + } 1.229 + fSegmentCount = 0; 1.230 + fFirstPt = fPrevPt = pt; 1.231 +} 1.232 + 1.233 +void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) { 1.234 + fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY); 1.235 + fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY); 1.236 +} 1.237 + 1.238 +void SkPathStroker::lineTo(const SkPoint& currPt) { 1.239 + if (SkPath::IsLineDegenerate(fPrevPt, currPt)) { 1.240 + return; 1.241 + } 1.242 + SkVector normal, unitNormal; 1.243 + 1.244 + this->preJoinTo(currPt, &normal, &unitNormal, true); 1.245 + this->line_to(currPt, normal); 1.246 + this->postJoinTo(currPt, normal, unitNormal); 1.247 +} 1.248 + 1.249 +void SkPathStroker::quad_to(const SkPoint pts[3], 1.250 + const SkVector& normalAB, const SkVector& unitNormalAB, 1.251 + SkVector* normalBC, SkVector* unitNormalBC, 1.252 + int subDivide) { 1.253 + if (!set_normal_unitnormal(pts[1], pts[2], fRadius, 1.254 + normalBC, unitNormalBC)) { 1.255 + // pts[1] nearly equals pts[2], so just draw a line to pts[2] 1.256 + this->line_to(pts[2], normalAB); 1.257 + *normalBC = normalAB; 1.258 + *unitNormalBC = unitNormalAB; 1.259 + return; 1.260 + } 1.261 + 1.262 + if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) { 1.263 + SkPoint tmp[5]; 1.264 + SkVector norm, unit; 1.265 + 1.266 + SkChopQuadAtHalf(pts, tmp); 1.267 + this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide); 1.268 + this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide); 1.269 + } else { 1.270 + SkVector normalB; 1.271 + 1.272 + normalB = pts[2] - pts[0]; 1.273 + normalB.rotateCCW(); 1.274 + SkScalar dot = SkPoint::DotProduct(unitNormalAB, *unitNormalBC); 1.275 + SkAssertResult(normalB.setLength(SkScalarDiv(fRadius, 1.276 + SkScalarSqrt((SK_Scalar1 + dot)/2)))); 1.277 + 1.278 + fOuter.quadTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY, 1.279 + pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY); 1.280 + fInner.quadTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY, 1.281 + pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY); 1.282 + } 1.283 +} 1.284 + 1.285 +void SkPathStroker::cubic_to(const SkPoint pts[4], 1.286 + const SkVector& normalAB, const SkVector& unitNormalAB, 1.287 + SkVector* normalCD, SkVector* unitNormalCD, 1.288 + int subDivide) { 1.289 + SkVector ab = pts[1] - pts[0]; 1.290 + SkVector cd = pts[3] - pts[2]; 1.291 + SkVector normalBC, unitNormalBC; 1.292 + 1.293 + bool degenerateAB = degenerate_vector(ab); 1.294 + bool degenerateCD = degenerate_vector(cd); 1.295 + 1.296 + if (degenerateAB && degenerateCD) { 1.297 +DRAW_LINE: 1.298 + this->line_to(pts[3], normalAB); 1.299 + *normalCD = normalAB; 1.300 + *unitNormalCD = unitNormalAB; 1.301 + return; 1.302 + } 1.303 + 1.304 + if (degenerateAB) { 1.305 + ab = pts[2] - pts[0]; 1.306 + degenerateAB = degenerate_vector(ab); 1.307 + } 1.308 + if (degenerateCD) { 1.309 + cd = pts[3] - pts[1]; 1.310 + degenerateCD = degenerate_vector(cd); 1.311 + } 1.312 + if (degenerateAB || degenerateCD) { 1.313 + goto DRAW_LINE; 1.314 + } 1.315 + SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD)); 1.316 + bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius, 1.317 + &normalBC, &unitNormalBC); 1.318 +#ifndef SK_IGNORE_CUBIC_STROKE_FIX 1.319 + if (--subDivide < 0) { 1.320 + goto DRAW_LINE; 1.321 + } 1.322 +#endif 1.323 + if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) || 1.324 + normals_too_curvy(unitNormalBC, *unitNormalCD)) { 1.325 +#ifdef SK_IGNORE_CUBIC_STROKE_FIX 1.326 + // subdivide if we can 1.327 + if (--subDivide < 0) { 1.328 + goto DRAW_LINE; 1.329 + } 1.330 +#endif 1.331 + SkPoint tmp[7]; 1.332 + SkVector norm, unit, dummy, unitDummy; 1.333 + 1.334 + SkChopCubicAtHalf(pts, tmp); 1.335 + this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, 1.336 + subDivide); 1.337 + // we use dummys since we already have a valid (and more accurate) 1.338 + // normals for CD 1.339 + this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide); 1.340 + } else { 1.341 + SkVector normalB, normalC; 1.342 + 1.343 + // need normals to inset/outset the off-curve pts B and C 1.344 + 1.345 + SkVector unitBC = pts[2] - pts[1]; 1.346 + unitBC.normalize(); 1.347 + unitBC.rotateCCW(); 1.348 + 1.349 + normalB = unitNormalAB + unitBC; 1.350 + normalC = *unitNormalCD + unitBC; 1.351 + 1.352 + SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC); 1.353 + SkAssertResult(normalB.setLength(SkScalarDiv(fRadius, 1.354 + SkScalarSqrt((SK_Scalar1 + dot)/2)))); 1.355 + dot = SkPoint::DotProduct(*unitNormalCD, unitBC); 1.356 + SkAssertResult(normalC.setLength(SkScalarDiv(fRadius, 1.357 + SkScalarSqrt((SK_Scalar1 + dot)/2)))); 1.358 + 1.359 + fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY, 1.360 + pts[2].fX + normalC.fX, pts[2].fY + normalC.fY, 1.361 + pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY); 1.362 + 1.363 + fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY, 1.364 + pts[2].fX - normalC.fX, pts[2].fY - normalC.fY, 1.365 + pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY); 1.366 + } 1.367 +} 1.368 + 1.369 +void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) { 1.370 + bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1); 1.371 + bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2); 1.372 + 1.373 + if (degenerateAB | degenerateBC) { 1.374 + if (degenerateAB ^ degenerateBC) { 1.375 + this->lineTo(pt2); 1.376 + } 1.377 + return; 1.378 + } 1.379 + 1.380 + SkVector normalAB, unitAB, normalBC, unitBC; 1.381 + 1.382 + this->preJoinTo(pt1, &normalAB, &unitAB, false); 1.383 + 1.384 + { 1.385 + SkPoint pts[3], tmp[5]; 1.386 + pts[0] = fPrevPt; 1.387 + pts[1] = pt1; 1.388 + pts[2] = pt2; 1.389 + 1.390 + if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) { 1.391 + unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY); 1.392 + unitBC.rotateCCW(); 1.393 + if (normals_too_pinchy(unitAB, unitBC)) { 1.394 + normalBC = unitBC; 1.395 + normalBC.scale(fRadius); 1.396 + 1.397 + fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY); 1.398 + fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY); 1.399 + fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY); 1.400 + 1.401 + fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY); 1.402 + fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY); 1.403 + fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY); 1.404 + 1.405 + fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius, 1.406 + SkPath::kCW_Direction); 1.407 + } else { 1.408 + this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC, 1.409 + kMaxQuadSubdivide); 1.410 + SkVector n = normalBC; 1.411 + SkVector u = unitBC; 1.412 + this->quad_to(&tmp[2], n, u, &normalBC, &unitBC, 1.413 + kMaxQuadSubdivide); 1.414 + } 1.415 + } else { 1.416 + this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC, 1.417 + kMaxQuadSubdivide); 1.418 + } 1.419 + } 1.420 + 1.421 + this->postJoinTo(pt2, normalBC, unitBC); 1.422 +} 1.423 + 1.424 +void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2, 1.425 + const SkPoint& pt3) { 1.426 + bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1); 1.427 + bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2); 1.428 + bool degenerateCD = SkPath::IsLineDegenerate(pt2, pt3); 1.429 + 1.430 + if (degenerateAB + degenerateBC + degenerateCD >= 2) { 1.431 + this->lineTo(pt3); 1.432 + return; 1.433 + } 1.434 + 1.435 + SkVector normalAB, unitAB, normalCD, unitCD; 1.436 + 1.437 + // find the first tangent (which might be pt1 or pt2 1.438 + { 1.439 + const SkPoint* nextPt = &pt1; 1.440 + if (degenerateAB) 1.441 + nextPt = &pt2; 1.442 + this->preJoinTo(*nextPt, &normalAB, &unitAB, false); 1.443 + } 1.444 + 1.445 + { 1.446 + SkPoint pts[4], tmp[13]; 1.447 + int i, count; 1.448 + SkVector n, u; 1.449 + SkScalar tValues[3]; 1.450 + 1.451 + pts[0] = fPrevPt; 1.452 + pts[1] = pt1; 1.453 + pts[2] = pt2; 1.454 + pts[3] = pt3; 1.455 + 1.456 + count = SkChopCubicAtMaxCurvature(pts, tmp, tValues); 1.457 + n = normalAB; 1.458 + u = unitAB; 1.459 + for (i = 0; i < count; i++) { 1.460 + this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD, 1.461 + kMaxCubicSubdivide); 1.462 + if (i == count - 1) { 1.463 + break; 1.464 + } 1.465 + n = normalCD; 1.466 + u = unitCD; 1.467 + 1.468 + } 1.469 + } 1.470 + 1.471 + this->postJoinTo(pt3, normalCD, unitCD); 1.472 +} 1.473 + 1.474 +/////////////////////////////////////////////////////////////////////////////// 1.475 +/////////////////////////////////////////////////////////////////////////////// 1.476 + 1.477 +#include "SkPaintDefaults.h" 1.478 + 1.479 +SkStroke::SkStroke() { 1.480 + fWidth = SK_Scalar1; 1.481 + fMiterLimit = SkPaintDefaults_MiterLimit; 1.482 + fCap = SkPaint::kDefault_Cap; 1.483 + fJoin = SkPaint::kDefault_Join; 1.484 + fDoFill = false; 1.485 +} 1.486 + 1.487 +SkStroke::SkStroke(const SkPaint& p) { 1.488 + fWidth = p.getStrokeWidth(); 1.489 + fMiterLimit = p.getStrokeMiter(); 1.490 + fCap = (uint8_t)p.getStrokeCap(); 1.491 + fJoin = (uint8_t)p.getStrokeJoin(); 1.492 + fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); 1.493 +} 1.494 + 1.495 +SkStroke::SkStroke(const SkPaint& p, SkScalar width) { 1.496 + fWidth = width; 1.497 + fMiterLimit = p.getStrokeMiter(); 1.498 + fCap = (uint8_t)p.getStrokeCap(); 1.499 + fJoin = (uint8_t)p.getStrokeJoin(); 1.500 + fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); 1.501 +} 1.502 + 1.503 +void SkStroke::setWidth(SkScalar width) { 1.504 + SkASSERT(width >= 0); 1.505 + fWidth = width; 1.506 +} 1.507 + 1.508 +void SkStroke::setMiterLimit(SkScalar miterLimit) { 1.509 + SkASSERT(miterLimit >= 0); 1.510 + fMiterLimit = miterLimit; 1.511 +} 1.512 + 1.513 +void SkStroke::setCap(SkPaint::Cap cap) { 1.514 + SkASSERT((unsigned)cap < SkPaint::kCapCount); 1.515 + fCap = SkToU8(cap); 1.516 +} 1.517 + 1.518 +void SkStroke::setJoin(SkPaint::Join join) { 1.519 + SkASSERT((unsigned)join < SkPaint::kJoinCount); 1.520 + fJoin = SkToU8(join); 1.521 +} 1.522 + 1.523 +/////////////////////////////////////////////////////////////////////////////// 1.524 + 1.525 +// If src==dst, then we use a tmp path to record the stroke, and then swap 1.526 +// its contents with src when we're done. 1.527 +class AutoTmpPath { 1.528 +public: 1.529 + AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) { 1.530 + if (&src == *dst) { 1.531 + *dst = &fTmpDst; 1.532 + fSwapWithSrc = true; 1.533 + } else { 1.534 + (*dst)->reset(); 1.535 + fSwapWithSrc = false; 1.536 + } 1.537 + } 1.538 + 1.539 + ~AutoTmpPath() { 1.540 + if (fSwapWithSrc) { 1.541 + fTmpDst.swap(*const_cast<SkPath*>(&fSrc)); 1.542 + } 1.543 + } 1.544 + 1.545 +private: 1.546 + SkPath fTmpDst; 1.547 + const SkPath& fSrc; 1.548 + bool fSwapWithSrc; 1.549 +}; 1.550 + 1.551 +void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { 1.552 + SkASSERT(&src != NULL && dst != NULL); 1.553 + 1.554 + SkScalar radius = SkScalarHalf(fWidth); 1.555 + 1.556 + AutoTmpPath tmp(src, &dst); 1.557 + 1.558 + if (radius <= 0) { 1.559 + return; 1.560 + } 1.561 + 1.562 + // If src is really a rect, call our specialty strokeRect() method 1.563 + { 1.564 + bool isClosed; 1.565 + SkPath::Direction dir; 1.566 + if (src.isRect(&isClosed, &dir) && isClosed) { 1.567 + this->strokeRect(src.getBounds(), dst, dir); 1.568 + // our answer should preserve the inverseness of the src 1.569 + if (src.isInverseFillType()) { 1.570 + SkASSERT(!dst->isInverseFillType()); 1.571 + dst->toggleInverseFillType(); 1.572 + } 1.573 + return; 1.574 + } 1.575 + } 1.576 + 1.577 + SkAutoConicToQuads converter; 1.578 + const SkScalar conicTol = SK_Scalar1 / 4; 1.579 + 1.580 + SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), 1.581 + this->getJoin()); 1.582 + SkPath::Iter iter(src, false); 1.583 + SkPath::Verb lastSegment = SkPath::kMove_Verb; 1.584 + 1.585 + for (;;) { 1.586 + SkPoint pts[4]; 1.587 + switch (iter.next(pts, false)) { 1.588 + case SkPath::kMove_Verb: 1.589 + stroker.moveTo(pts[0]); 1.590 + break; 1.591 + case SkPath::kLine_Verb: 1.592 + stroker.lineTo(pts[1]); 1.593 + lastSegment = SkPath::kLine_Verb; 1.594 + break; 1.595 + case SkPath::kQuad_Verb: 1.596 + stroker.quadTo(pts[1], pts[2]); 1.597 + lastSegment = SkPath::kQuad_Verb; 1.598 + break; 1.599 + case SkPath::kConic_Verb: { 1.600 + // todo: if we had maxcurvature for conics, perhaps we should 1.601 + // natively extrude the conic instead of converting to quads. 1.602 + const SkPoint* quadPts = 1.603 + converter.computeQuads(pts, iter.conicWeight(), conicTol); 1.604 + for (int i = 0; i < converter.countQuads(); ++i) { 1.605 + stroker.quadTo(quadPts[1], quadPts[2]); 1.606 + quadPts += 2; 1.607 + } 1.608 + lastSegment = SkPath::kQuad_Verb; 1.609 + } break; 1.610 + case SkPath::kCubic_Verb: 1.611 + stroker.cubicTo(pts[1], pts[2], pts[3]); 1.612 + lastSegment = SkPath::kCubic_Verb; 1.613 + break; 1.614 + case SkPath::kClose_Verb: 1.615 + stroker.close(lastSegment == SkPath::kLine_Verb); 1.616 + break; 1.617 + case SkPath::kDone_Verb: 1.618 + goto DONE; 1.619 + } 1.620 + } 1.621 +DONE: 1.622 + stroker.done(dst, lastSegment == SkPath::kLine_Verb); 1.623 + 1.624 + if (fDoFill) { 1.625 + if (src.cheapIsDirection(SkPath::kCCW_Direction)) { 1.626 + dst->reverseAddPath(src); 1.627 + } else { 1.628 + dst->addPath(src); 1.629 + } 1.630 + } else { 1.631 + // Seems like we can assume that a 2-point src would always result in 1.632 + // a convex stroke, but testing has proved otherwise. 1.633 + // TODO: fix the stroker to make this assumption true (without making 1.634 + // it slower that the work that will be done in computeConvexity()) 1.635 +#if 0 1.636 + // this test results in a non-convex stroke :( 1.637 + static void test(SkCanvas* canvas) { 1.638 + SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 }; 1.639 + SkPaint paint; 1.640 + paint.setStrokeWidth(7); 1.641 + paint.setStrokeCap(SkPaint::kRound_Cap); 1.642 + canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); 1.643 + } 1.644 +#endif 1.645 +#if 0 1.646 + if (2 == src.countPoints()) { 1.647 + dst->setIsConvex(true); 1.648 + } 1.649 +#endif 1.650 + } 1.651 + 1.652 + // our answer should preserve the inverseness of the src 1.653 + if (src.isInverseFillType()) { 1.654 + SkASSERT(!dst->isInverseFillType()); 1.655 + dst->toggleInverseFillType(); 1.656 + } 1.657 +} 1.658 + 1.659 +static SkPath::Direction reverse_direction(SkPath::Direction dir) { 1.660 + SkASSERT(SkPath::kUnknown_Direction != dir); 1.661 + return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction; 1.662 +} 1.663 + 1.664 +static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) { 1.665 + SkPoint pts[8]; 1.666 + 1.667 + if (SkPath::kCW_Direction == dir) { 1.668 + pts[0].set(r.fLeft, outer.fTop); 1.669 + pts[1].set(r.fRight, outer.fTop); 1.670 + pts[2].set(outer.fRight, r.fTop); 1.671 + pts[3].set(outer.fRight, r.fBottom); 1.672 + pts[4].set(r.fRight, outer.fBottom); 1.673 + pts[5].set(r.fLeft, outer.fBottom); 1.674 + pts[6].set(outer.fLeft, r.fBottom); 1.675 + pts[7].set(outer.fLeft, r.fTop); 1.676 + } else { 1.677 + pts[7].set(r.fLeft, outer.fTop); 1.678 + pts[6].set(r.fRight, outer.fTop); 1.679 + pts[5].set(outer.fRight, r.fTop); 1.680 + pts[4].set(outer.fRight, r.fBottom); 1.681 + pts[3].set(r.fRight, outer.fBottom); 1.682 + pts[2].set(r.fLeft, outer.fBottom); 1.683 + pts[1].set(outer.fLeft, r.fBottom); 1.684 + pts[0].set(outer.fLeft, r.fTop); 1.685 + } 1.686 + path->addPoly(pts, 8, true); 1.687 +} 1.688 + 1.689 +void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst, 1.690 + SkPath::Direction dir) const { 1.691 + SkASSERT(dst != NULL); 1.692 + dst->reset(); 1.693 + 1.694 + SkScalar radius = SkScalarHalf(fWidth); 1.695 + if (radius <= 0) { 1.696 + return; 1.697 + } 1.698 + 1.699 + SkScalar rw = origRect.width(); 1.700 + SkScalar rh = origRect.height(); 1.701 + if ((rw < 0) ^ (rh < 0)) { 1.702 + dir = reverse_direction(dir); 1.703 + } 1.704 + SkRect rect(origRect); 1.705 + rect.sort(); 1.706 + // reassign these, now that we know they'll be >= 0 1.707 + rw = rect.width(); 1.708 + rh = rect.height(); 1.709 + 1.710 + SkRect r(rect); 1.711 + r.outset(radius, radius); 1.712 + 1.713 + SkPaint::Join join = (SkPaint::Join)fJoin; 1.714 + if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) { 1.715 + join = SkPaint::kBevel_Join; 1.716 + } 1.717 + 1.718 + switch (join) { 1.719 + case SkPaint::kMiter_Join: 1.720 + dst->addRect(r, dir); 1.721 + break; 1.722 + case SkPaint::kBevel_Join: 1.723 + addBevel(dst, rect, r, dir); 1.724 + break; 1.725 + case SkPaint::kRound_Join: 1.726 + dst->addRoundRect(r, radius, radius, dir); 1.727 + break; 1.728 + default: 1.729 + break; 1.730 + } 1.731 + 1.732 + if (fWidth < SkMinScalar(rw, rh) && !fDoFill) { 1.733 + r = rect; 1.734 + r.inset(radius, radius); 1.735 + dst->addRect(r, reverse_direction(dir)); 1.736 + } 1.737 +}