|
1 /* |
|
2 * Copyright 2008 The Android Open Source Project |
|
3 * |
|
4 * Use of this source code is governed by a BSD-style license that can be |
|
5 * found in the LICENSE file. |
|
6 */ |
|
7 |
|
8 #include "SkStrokerPriv.h" |
|
9 #include "SkGeometry.h" |
|
10 #include "SkPath.h" |
|
11 |
|
12 #define kMaxQuadSubdivide 5 |
|
13 #define kMaxCubicSubdivide 7 |
|
14 |
|
15 static inline bool degenerate_vector(const SkVector& v) { |
|
16 return !SkPoint::CanNormalize(v.fX, v.fY); |
|
17 } |
|
18 |
|
19 static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) { |
|
20 /* root2/2 is a 45-degree angle |
|
21 make this constant bigger for more subdivisions (but not >= 1) |
|
22 */ |
|
23 static const SkScalar kFlatEnoughNormalDotProd = |
|
24 SK_ScalarSqrt2/2 + SK_Scalar1/10; |
|
25 |
|
26 SkASSERT(kFlatEnoughNormalDotProd > 0 && |
|
27 kFlatEnoughNormalDotProd < SK_Scalar1); |
|
28 |
|
29 return SkPoint::DotProduct(norm0, norm1) <= kFlatEnoughNormalDotProd; |
|
30 } |
|
31 |
|
32 static inline bool normals_too_pinchy(const SkVector& norm0, SkVector& norm1) { |
|
33 // if the dot-product is -1, then we are definitely too pinchy. We tweak |
|
34 // that by an epsilon to ensure we have significant bits in our test |
|
35 static const int kMinSigBitsForDot = 8; |
|
36 static const SkScalar kDotEpsilon = FLT_EPSILON * (1 << kMinSigBitsForDot); |
|
37 static const SkScalar kTooPinchyNormalDotProd = kDotEpsilon - 1; |
|
38 |
|
39 // just some sanity asserts to help document the expected range |
|
40 SkASSERT(kTooPinchyNormalDotProd >= -1); |
|
41 SkASSERT(kTooPinchyNormalDotProd < SkDoubleToScalar(-0.999)); |
|
42 |
|
43 SkScalar dot = SkPoint::DotProduct(norm0, norm1); |
|
44 return dot <= kTooPinchyNormalDotProd; |
|
45 } |
|
46 |
|
47 static bool set_normal_unitnormal(const SkPoint& before, const SkPoint& after, |
|
48 SkScalar radius, |
|
49 SkVector* normal, SkVector* unitNormal) { |
|
50 if (!unitNormal->setNormalize(after.fX - before.fX, after.fY - before.fY)) { |
|
51 return false; |
|
52 } |
|
53 unitNormal->rotateCCW(); |
|
54 unitNormal->scale(radius, normal); |
|
55 return true; |
|
56 } |
|
57 |
|
58 static bool set_normal_unitnormal(const SkVector& vec, |
|
59 SkScalar radius, |
|
60 SkVector* normal, SkVector* unitNormal) { |
|
61 if (!unitNormal->setNormalize(vec.fX, vec.fY)) { |
|
62 return false; |
|
63 } |
|
64 unitNormal->rotateCCW(); |
|
65 unitNormal->scale(radius, normal); |
|
66 return true; |
|
67 } |
|
68 |
|
69 /////////////////////////////////////////////////////////////////////////////// |
|
70 |
|
71 class SkPathStroker { |
|
72 public: |
|
73 SkPathStroker(const SkPath& src, |
|
74 SkScalar radius, SkScalar miterLimit, SkPaint::Cap cap, |
|
75 SkPaint::Join join); |
|
76 |
|
77 void moveTo(const SkPoint&); |
|
78 void lineTo(const SkPoint&); |
|
79 void quadTo(const SkPoint&, const SkPoint&); |
|
80 void cubicTo(const SkPoint&, const SkPoint&, const SkPoint&); |
|
81 void close(bool isLine) { this->finishContour(true, isLine); } |
|
82 |
|
83 void done(SkPath* dst, bool isLine) { |
|
84 this->finishContour(false, isLine); |
|
85 fOuter.addPath(fExtra); |
|
86 dst->swap(fOuter); |
|
87 } |
|
88 |
|
89 private: |
|
90 SkScalar fRadius; |
|
91 SkScalar fInvMiterLimit; |
|
92 |
|
93 SkVector fFirstNormal, fPrevNormal, fFirstUnitNormal, fPrevUnitNormal; |
|
94 SkPoint fFirstPt, fPrevPt; // on original path |
|
95 SkPoint fFirstOuterPt; |
|
96 int fSegmentCount; |
|
97 bool fPrevIsLine; |
|
98 |
|
99 SkStrokerPriv::CapProc fCapper; |
|
100 SkStrokerPriv::JoinProc fJoiner; |
|
101 |
|
102 SkPath fInner, fOuter; // outer is our working answer, inner is temp |
|
103 SkPath fExtra; // added as extra complete contours |
|
104 |
|
105 void finishContour(bool close, bool isLine); |
|
106 void preJoinTo(const SkPoint&, SkVector* normal, SkVector* unitNormal, |
|
107 bool isLine); |
|
108 void postJoinTo(const SkPoint&, const SkVector& normal, |
|
109 const SkVector& unitNormal); |
|
110 |
|
111 void line_to(const SkPoint& currPt, const SkVector& normal); |
|
112 void quad_to(const SkPoint pts[3], |
|
113 const SkVector& normalAB, const SkVector& unitNormalAB, |
|
114 SkVector* normalBC, SkVector* unitNormalBC, |
|
115 int subDivide); |
|
116 void cubic_to(const SkPoint pts[4], |
|
117 const SkVector& normalAB, const SkVector& unitNormalAB, |
|
118 SkVector* normalCD, SkVector* unitNormalCD, |
|
119 int subDivide); |
|
120 }; |
|
121 |
|
122 /////////////////////////////////////////////////////////////////////////////// |
|
123 |
|
124 void SkPathStroker::preJoinTo(const SkPoint& currPt, SkVector* normal, |
|
125 SkVector* unitNormal, bool currIsLine) { |
|
126 SkASSERT(fSegmentCount >= 0); |
|
127 |
|
128 SkScalar prevX = fPrevPt.fX; |
|
129 SkScalar prevY = fPrevPt.fY; |
|
130 |
|
131 SkAssertResult(set_normal_unitnormal(fPrevPt, currPt, fRadius, normal, |
|
132 unitNormal)); |
|
133 |
|
134 if (fSegmentCount == 0) { |
|
135 fFirstNormal = *normal; |
|
136 fFirstUnitNormal = *unitNormal; |
|
137 fFirstOuterPt.set(prevX + normal->fX, prevY + normal->fY); |
|
138 |
|
139 fOuter.moveTo(fFirstOuterPt.fX, fFirstOuterPt.fY); |
|
140 fInner.moveTo(prevX - normal->fX, prevY - normal->fY); |
|
141 } else { // we have a previous segment |
|
142 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, *unitNormal, |
|
143 fRadius, fInvMiterLimit, fPrevIsLine, currIsLine); |
|
144 } |
|
145 fPrevIsLine = currIsLine; |
|
146 } |
|
147 |
|
148 void SkPathStroker::postJoinTo(const SkPoint& currPt, const SkVector& normal, |
|
149 const SkVector& unitNormal) { |
|
150 fPrevPt = currPt; |
|
151 fPrevUnitNormal = unitNormal; |
|
152 fPrevNormal = normal; |
|
153 fSegmentCount += 1; |
|
154 } |
|
155 |
|
156 void SkPathStroker::finishContour(bool close, bool currIsLine) { |
|
157 if (fSegmentCount > 0) { |
|
158 SkPoint pt; |
|
159 |
|
160 if (close) { |
|
161 fJoiner(&fOuter, &fInner, fPrevUnitNormal, fPrevPt, |
|
162 fFirstUnitNormal, fRadius, fInvMiterLimit, |
|
163 fPrevIsLine, currIsLine); |
|
164 fOuter.close(); |
|
165 // now add fInner as its own contour |
|
166 fInner.getLastPt(&pt); |
|
167 fOuter.moveTo(pt.fX, pt.fY); |
|
168 fOuter.reversePathTo(fInner); |
|
169 fOuter.close(); |
|
170 } else { // add caps to start and end |
|
171 // cap the end |
|
172 fInner.getLastPt(&pt); |
|
173 fCapper(&fOuter, fPrevPt, fPrevNormal, pt, |
|
174 currIsLine ? &fInner : NULL); |
|
175 fOuter.reversePathTo(fInner); |
|
176 // cap the start |
|
177 fCapper(&fOuter, fFirstPt, -fFirstNormal, fFirstOuterPt, |
|
178 fPrevIsLine ? &fInner : NULL); |
|
179 fOuter.close(); |
|
180 } |
|
181 } |
|
182 // since we may re-use fInner, we rewind instead of reset, to save on |
|
183 // reallocating its internal storage. |
|
184 fInner.rewind(); |
|
185 fSegmentCount = -1; |
|
186 } |
|
187 |
|
188 /////////////////////////////////////////////////////////////////////////////// |
|
189 |
|
190 SkPathStroker::SkPathStroker(const SkPath& src, |
|
191 SkScalar radius, SkScalar miterLimit, |
|
192 SkPaint::Cap cap, SkPaint::Join join) |
|
193 : fRadius(radius) { |
|
194 |
|
195 /* This is only used when join is miter_join, but we initialize it here |
|
196 so that it is always defined, to fis valgrind warnings. |
|
197 */ |
|
198 fInvMiterLimit = 0; |
|
199 |
|
200 if (join == SkPaint::kMiter_Join) { |
|
201 if (miterLimit <= SK_Scalar1) { |
|
202 join = SkPaint::kBevel_Join; |
|
203 } else { |
|
204 fInvMiterLimit = SkScalarInvert(miterLimit); |
|
205 } |
|
206 } |
|
207 fCapper = SkStrokerPriv::CapFactory(cap); |
|
208 fJoiner = SkStrokerPriv::JoinFactory(join); |
|
209 fSegmentCount = -1; |
|
210 fPrevIsLine = false; |
|
211 |
|
212 // Need some estimate of how large our final result (fOuter) |
|
213 // and our per-contour temp (fInner) will be, so we don't spend |
|
214 // extra time repeatedly growing these arrays. |
|
215 // |
|
216 // 3x for result == inner + outer + join (swag) |
|
217 // 1x for inner == 'wag' (worst contour length would be better guess) |
|
218 fOuter.incReserve(src.countPoints() * 3); |
|
219 fInner.incReserve(src.countPoints()); |
|
220 } |
|
221 |
|
222 void SkPathStroker::moveTo(const SkPoint& pt) { |
|
223 if (fSegmentCount > 0) { |
|
224 this->finishContour(false, false); |
|
225 } |
|
226 fSegmentCount = 0; |
|
227 fFirstPt = fPrevPt = pt; |
|
228 } |
|
229 |
|
230 void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) { |
|
231 fOuter.lineTo(currPt.fX + normal.fX, currPt.fY + normal.fY); |
|
232 fInner.lineTo(currPt.fX - normal.fX, currPt.fY - normal.fY); |
|
233 } |
|
234 |
|
235 void SkPathStroker::lineTo(const SkPoint& currPt) { |
|
236 if (SkPath::IsLineDegenerate(fPrevPt, currPt)) { |
|
237 return; |
|
238 } |
|
239 SkVector normal, unitNormal; |
|
240 |
|
241 this->preJoinTo(currPt, &normal, &unitNormal, true); |
|
242 this->line_to(currPt, normal); |
|
243 this->postJoinTo(currPt, normal, unitNormal); |
|
244 } |
|
245 |
|
246 void SkPathStroker::quad_to(const SkPoint pts[3], |
|
247 const SkVector& normalAB, const SkVector& unitNormalAB, |
|
248 SkVector* normalBC, SkVector* unitNormalBC, |
|
249 int subDivide) { |
|
250 if (!set_normal_unitnormal(pts[1], pts[2], fRadius, |
|
251 normalBC, unitNormalBC)) { |
|
252 // pts[1] nearly equals pts[2], so just draw a line to pts[2] |
|
253 this->line_to(pts[2], normalAB); |
|
254 *normalBC = normalAB; |
|
255 *unitNormalBC = unitNormalAB; |
|
256 return; |
|
257 } |
|
258 |
|
259 if (--subDivide >= 0 && normals_too_curvy(unitNormalAB, *unitNormalBC)) { |
|
260 SkPoint tmp[5]; |
|
261 SkVector norm, unit; |
|
262 |
|
263 SkChopQuadAtHalf(pts, tmp); |
|
264 this->quad_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, subDivide); |
|
265 this->quad_to(&tmp[2], norm, unit, normalBC, unitNormalBC, subDivide); |
|
266 } else { |
|
267 SkVector normalB; |
|
268 |
|
269 normalB = pts[2] - pts[0]; |
|
270 normalB.rotateCCW(); |
|
271 SkScalar dot = SkPoint::DotProduct(unitNormalAB, *unitNormalBC); |
|
272 SkAssertResult(normalB.setLength(SkScalarDiv(fRadius, |
|
273 SkScalarSqrt((SK_Scalar1 + dot)/2)))); |
|
274 |
|
275 fOuter.quadTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY, |
|
276 pts[2].fX + normalBC->fX, pts[2].fY + normalBC->fY); |
|
277 fInner.quadTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY, |
|
278 pts[2].fX - normalBC->fX, pts[2].fY - normalBC->fY); |
|
279 } |
|
280 } |
|
281 |
|
282 void SkPathStroker::cubic_to(const SkPoint pts[4], |
|
283 const SkVector& normalAB, const SkVector& unitNormalAB, |
|
284 SkVector* normalCD, SkVector* unitNormalCD, |
|
285 int subDivide) { |
|
286 SkVector ab = pts[1] - pts[0]; |
|
287 SkVector cd = pts[3] - pts[2]; |
|
288 SkVector normalBC, unitNormalBC; |
|
289 |
|
290 bool degenerateAB = degenerate_vector(ab); |
|
291 bool degenerateCD = degenerate_vector(cd); |
|
292 |
|
293 if (degenerateAB && degenerateCD) { |
|
294 DRAW_LINE: |
|
295 this->line_to(pts[3], normalAB); |
|
296 *normalCD = normalAB; |
|
297 *unitNormalCD = unitNormalAB; |
|
298 return; |
|
299 } |
|
300 |
|
301 if (degenerateAB) { |
|
302 ab = pts[2] - pts[0]; |
|
303 degenerateAB = degenerate_vector(ab); |
|
304 } |
|
305 if (degenerateCD) { |
|
306 cd = pts[3] - pts[1]; |
|
307 degenerateCD = degenerate_vector(cd); |
|
308 } |
|
309 if (degenerateAB || degenerateCD) { |
|
310 goto DRAW_LINE; |
|
311 } |
|
312 SkAssertResult(set_normal_unitnormal(cd, fRadius, normalCD, unitNormalCD)); |
|
313 bool degenerateBC = !set_normal_unitnormal(pts[1], pts[2], fRadius, |
|
314 &normalBC, &unitNormalBC); |
|
315 #ifndef SK_IGNORE_CUBIC_STROKE_FIX |
|
316 if (--subDivide < 0) { |
|
317 goto DRAW_LINE; |
|
318 } |
|
319 #endif |
|
320 if (degenerateBC || normals_too_curvy(unitNormalAB, unitNormalBC) || |
|
321 normals_too_curvy(unitNormalBC, *unitNormalCD)) { |
|
322 #ifdef SK_IGNORE_CUBIC_STROKE_FIX |
|
323 // subdivide if we can |
|
324 if (--subDivide < 0) { |
|
325 goto DRAW_LINE; |
|
326 } |
|
327 #endif |
|
328 SkPoint tmp[7]; |
|
329 SkVector norm, unit, dummy, unitDummy; |
|
330 |
|
331 SkChopCubicAtHalf(pts, tmp); |
|
332 this->cubic_to(&tmp[0], normalAB, unitNormalAB, &norm, &unit, |
|
333 subDivide); |
|
334 // we use dummys since we already have a valid (and more accurate) |
|
335 // normals for CD |
|
336 this->cubic_to(&tmp[3], norm, unit, &dummy, &unitDummy, subDivide); |
|
337 } else { |
|
338 SkVector normalB, normalC; |
|
339 |
|
340 // need normals to inset/outset the off-curve pts B and C |
|
341 |
|
342 SkVector unitBC = pts[2] - pts[1]; |
|
343 unitBC.normalize(); |
|
344 unitBC.rotateCCW(); |
|
345 |
|
346 normalB = unitNormalAB + unitBC; |
|
347 normalC = *unitNormalCD + unitBC; |
|
348 |
|
349 SkScalar dot = SkPoint::DotProduct(unitNormalAB, unitBC); |
|
350 SkAssertResult(normalB.setLength(SkScalarDiv(fRadius, |
|
351 SkScalarSqrt((SK_Scalar1 + dot)/2)))); |
|
352 dot = SkPoint::DotProduct(*unitNormalCD, unitBC); |
|
353 SkAssertResult(normalC.setLength(SkScalarDiv(fRadius, |
|
354 SkScalarSqrt((SK_Scalar1 + dot)/2)))); |
|
355 |
|
356 fOuter.cubicTo( pts[1].fX + normalB.fX, pts[1].fY + normalB.fY, |
|
357 pts[2].fX + normalC.fX, pts[2].fY + normalC.fY, |
|
358 pts[3].fX + normalCD->fX, pts[3].fY + normalCD->fY); |
|
359 |
|
360 fInner.cubicTo( pts[1].fX - normalB.fX, pts[1].fY - normalB.fY, |
|
361 pts[2].fX - normalC.fX, pts[2].fY - normalC.fY, |
|
362 pts[3].fX - normalCD->fX, pts[3].fY - normalCD->fY); |
|
363 } |
|
364 } |
|
365 |
|
366 void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) { |
|
367 bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1); |
|
368 bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2); |
|
369 |
|
370 if (degenerateAB | degenerateBC) { |
|
371 if (degenerateAB ^ degenerateBC) { |
|
372 this->lineTo(pt2); |
|
373 } |
|
374 return; |
|
375 } |
|
376 |
|
377 SkVector normalAB, unitAB, normalBC, unitBC; |
|
378 |
|
379 this->preJoinTo(pt1, &normalAB, &unitAB, false); |
|
380 |
|
381 { |
|
382 SkPoint pts[3], tmp[5]; |
|
383 pts[0] = fPrevPt; |
|
384 pts[1] = pt1; |
|
385 pts[2] = pt2; |
|
386 |
|
387 if (SkChopQuadAtMaxCurvature(pts, tmp) == 2) { |
|
388 unitBC.setNormalize(pts[2].fX - pts[1].fX, pts[2].fY - pts[1].fY); |
|
389 unitBC.rotateCCW(); |
|
390 if (normals_too_pinchy(unitAB, unitBC)) { |
|
391 normalBC = unitBC; |
|
392 normalBC.scale(fRadius); |
|
393 |
|
394 fOuter.lineTo(tmp[2].fX + normalAB.fX, tmp[2].fY + normalAB.fY); |
|
395 fOuter.lineTo(tmp[2].fX + normalBC.fX, tmp[2].fY + normalBC.fY); |
|
396 fOuter.lineTo(tmp[4].fX + normalBC.fX, tmp[4].fY + normalBC.fY); |
|
397 |
|
398 fInner.lineTo(tmp[2].fX - normalAB.fX, tmp[2].fY - normalAB.fY); |
|
399 fInner.lineTo(tmp[2].fX - normalBC.fX, tmp[2].fY - normalBC.fY); |
|
400 fInner.lineTo(tmp[4].fX - normalBC.fX, tmp[4].fY - normalBC.fY); |
|
401 |
|
402 fExtra.addCircle(tmp[2].fX, tmp[2].fY, fRadius, |
|
403 SkPath::kCW_Direction); |
|
404 } else { |
|
405 this->quad_to(&tmp[0], normalAB, unitAB, &normalBC, &unitBC, |
|
406 kMaxQuadSubdivide); |
|
407 SkVector n = normalBC; |
|
408 SkVector u = unitBC; |
|
409 this->quad_to(&tmp[2], n, u, &normalBC, &unitBC, |
|
410 kMaxQuadSubdivide); |
|
411 } |
|
412 } else { |
|
413 this->quad_to(pts, normalAB, unitAB, &normalBC, &unitBC, |
|
414 kMaxQuadSubdivide); |
|
415 } |
|
416 } |
|
417 |
|
418 this->postJoinTo(pt2, normalBC, unitBC); |
|
419 } |
|
420 |
|
421 void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2, |
|
422 const SkPoint& pt3) { |
|
423 bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1); |
|
424 bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2); |
|
425 bool degenerateCD = SkPath::IsLineDegenerate(pt2, pt3); |
|
426 |
|
427 if (degenerateAB + degenerateBC + degenerateCD >= 2) { |
|
428 this->lineTo(pt3); |
|
429 return; |
|
430 } |
|
431 |
|
432 SkVector normalAB, unitAB, normalCD, unitCD; |
|
433 |
|
434 // find the first tangent (which might be pt1 or pt2 |
|
435 { |
|
436 const SkPoint* nextPt = &pt1; |
|
437 if (degenerateAB) |
|
438 nextPt = &pt2; |
|
439 this->preJoinTo(*nextPt, &normalAB, &unitAB, false); |
|
440 } |
|
441 |
|
442 { |
|
443 SkPoint pts[4], tmp[13]; |
|
444 int i, count; |
|
445 SkVector n, u; |
|
446 SkScalar tValues[3]; |
|
447 |
|
448 pts[0] = fPrevPt; |
|
449 pts[1] = pt1; |
|
450 pts[2] = pt2; |
|
451 pts[3] = pt3; |
|
452 |
|
453 count = SkChopCubicAtMaxCurvature(pts, tmp, tValues); |
|
454 n = normalAB; |
|
455 u = unitAB; |
|
456 for (i = 0; i < count; i++) { |
|
457 this->cubic_to(&tmp[i * 3], n, u, &normalCD, &unitCD, |
|
458 kMaxCubicSubdivide); |
|
459 if (i == count - 1) { |
|
460 break; |
|
461 } |
|
462 n = normalCD; |
|
463 u = unitCD; |
|
464 |
|
465 } |
|
466 } |
|
467 |
|
468 this->postJoinTo(pt3, normalCD, unitCD); |
|
469 } |
|
470 |
|
471 /////////////////////////////////////////////////////////////////////////////// |
|
472 /////////////////////////////////////////////////////////////////////////////// |
|
473 |
|
474 #include "SkPaintDefaults.h" |
|
475 |
|
476 SkStroke::SkStroke() { |
|
477 fWidth = SK_Scalar1; |
|
478 fMiterLimit = SkPaintDefaults_MiterLimit; |
|
479 fCap = SkPaint::kDefault_Cap; |
|
480 fJoin = SkPaint::kDefault_Join; |
|
481 fDoFill = false; |
|
482 } |
|
483 |
|
484 SkStroke::SkStroke(const SkPaint& p) { |
|
485 fWidth = p.getStrokeWidth(); |
|
486 fMiterLimit = p.getStrokeMiter(); |
|
487 fCap = (uint8_t)p.getStrokeCap(); |
|
488 fJoin = (uint8_t)p.getStrokeJoin(); |
|
489 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); |
|
490 } |
|
491 |
|
492 SkStroke::SkStroke(const SkPaint& p, SkScalar width) { |
|
493 fWidth = width; |
|
494 fMiterLimit = p.getStrokeMiter(); |
|
495 fCap = (uint8_t)p.getStrokeCap(); |
|
496 fJoin = (uint8_t)p.getStrokeJoin(); |
|
497 fDoFill = SkToU8(p.getStyle() == SkPaint::kStrokeAndFill_Style); |
|
498 } |
|
499 |
|
500 void SkStroke::setWidth(SkScalar width) { |
|
501 SkASSERT(width >= 0); |
|
502 fWidth = width; |
|
503 } |
|
504 |
|
505 void SkStroke::setMiterLimit(SkScalar miterLimit) { |
|
506 SkASSERT(miterLimit >= 0); |
|
507 fMiterLimit = miterLimit; |
|
508 } |
|
509 |
|
510 void SkStroke::setCap(SkPaint::Cap cap) { |
|
511 SkASSERT((unsigned)cap < SkPaint::kCapCount); |
|
512 fCap = SkToU8(cap); |
|
513 } |
|
514 |
|
515 void SkStroke::setJoin(SkPaint::Join join) { |
|
516 SkASSERT((unsigned)join < SkPaint::kJoinCount); |
|
517 fJoin = SkToU8(join); |
|
518 } |
|
519 |
|
520 /////////////////////////////////////////////////////////////////////////////// |
|
521 |
|
522 // If src==dst, then we use a tmp path to record the stroke, and then swap |
|
523 // its contents with src when we're done. |
|
524 class AutoTmpPath { |
|
525 public: |
|
526 AutoTmpPath(const SkPath& src, SkPath** dst) : fSrc(src) { |
|
527 if (&src == *dst) { |
|
528 *dst = &fTmpDst; |
|
529 fSwapWithSrc = true; |
|
530 } else { |
|
531 (*dst)->reset(); |
|
532 fSwapWithSrc = false; |
|
533 } |
|
534 } |
|
535 |
|
536 ~AutoTmpPath() { |
|
537 if (fSwapWithSrc) { |
|
538 fTmpDst.swap(*const_cast<SkPath*>(&fSrc)); |
|
539 } |
|
540 } |
|
541 |
|
542 private: |
|
543 SkPath fTmpDst; |
|
544 const SkPath& fSrc; |
|
545 bool fSwapWithSrc; |
|
546 }; |
|
547 |
|
548 void SkStroke::strokePath(const SkPath& src, SkPath* dst) const { |
|
549 SkASSERT(&src != NULL && dst != NULL); |
|
550 |
|
551 SkScalar radius = SkScalarHalf(fWidth); |
|
552 |
|
553 AutoTmpPath tmp(src, &dst); |
|
554 |
|
555 if (radius <= 0) { |
|
556 return; |
|
557 } |
|
558 |
|
559 // If src is really a rect, call our specialty strokeRect() method |
|
560 { |
|
561 bool isClosed; |
|
562 SkPath::Direction dir; |
|
563 if (src.isRect(&isClosed, &dir) && isClosed) { |
|
564 this->strokeRect(src.getBounds(), dst, dir); |
|
565 // our answer should preserve the inverseness of the src |
|
566 if (src.isInverseFillType()) { |
|
567 SkASSERT(!dst->isInverseFillType()); |
|
568 dst->toggleInverseFillType(); |
|
569 } |
|
570 return; |
|
571 } |
|
572 } |
|
573 |
|
574 SkAutoConicToQuads converter; |
|
575 const SkScalar conicTol = SK_Scalar1 / 4; |
|
576 |
|
577 SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), |
|
578 this->getJoin()); |
|
579 SkPath::Iter iter(src, false); |
|
580 SkPath::Verb lastSegment = SkPath::kMove_Verb; |
|
581 |
|
582 for (;;) { |
|
583 SkPoint pts[4]; |
|
584 switch (iter.next(pts, false)) { |
|
585 case SkPath::kMove_Verb: |
|
586 stroker.moveTo(pts[0]); |
|
587 break; |
|
588 case SkPath::kLine_Verb: |
|
589 stroker.lineTo(pts[1]); |
|
590 lastSegment = SkPath::kLine_Verb; |
|
591 break; |
|
592 case SkPath::kQuad_Verb: |
|
593 stroker.quadTo(pts[1], pts[2]); |
|
594 lastSegment = SkPath::kQuad_Verb; |
|
595 break; |
|
596 case SkPath::kConic_Verb: { |
|
597 // todo: if we had maxcurvature for conics, perhaps we should |
|
598 // natively extrude the conic instead of converting to quads. |
|
599 const SkPoint* quadPts = |
|
600 converter.computeQuads(pts, iter.conicWeight(), conicTol); |
|
601 for (int i = 0; i < converter.countQuads(); ++i) { |
|
602 stroker.quadTo(quadPts[1], quadPts[2]); |
|
603 quadPts += 2; |
|
604 } |
|
605 lastSegment = SkPath::kQuad_Verb; |
|
606 } break; |
|
607 case SkPath::kCubic_Verb: |
|
608 stroker.cubicTo(pts[1], pts[2], pts[3]); |
|
609 lastSegment = SkPath::kCubic_Verb; |
|
610 break; |
|
611 case SkPath::kClose_Verb: |
|
612 stroker.close(lastSegment == SkPath::kLine_Verb); |
|
613 break; |
|
614 case SkPath::kDone_Verb: |
|
615 goto DONE; |
|
616 } |
|
617 } |
|
618 DONE: |
|
619 stroker.done(dst, lastSegment == SkPath::kLine_Verb); |
|
620 |
|
621 if (fDoFill) { |
|
622 if (src.cheapIsDirection(SkPath::kCCW_Direction)) { |
|
623 dst->reverseAddPath(src); |
|
624 } else { |
|
625 dst->addPath(src); |
|
626 } |
|
627 } else { |
|
628 // Seems like we can assume that a 2-point src would always result in |
|
629 // a convex stroke, but testing has proved otherwise. |
|
630 // TODO: fix the stroker to make this assumption true (without making |
|
631 // it slower that the work that will be done in computeConvexity()) |
|
632 #if 0 |
|
633 // this test results in a non-convex stroke :( |
|
634 static void test(SkCanvas* canvas) { |
|
635 SkPoint pts[] = { 146.333328, 192.333328, 300.333344, 293.333344 }; |
|
636 SkPaint paint; |
|
637 paint.setStrokeWidth(7); |
|
638 paint.setStrokeCap(SkPaint::kRound_Cap); |
|
639 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); |
|
640 } |
|
641 #endif |
|
642 #if 0 |
|
643 if (2 == src.countPoints()) { |
|
644 dst->setIsConvex(true); |
|
645 } |
|
646 #endif |
|
647 } |
|
648 |
|
649 // our answer should preserve the inverseness of the src |
|
650 if (src.isInverseFillType()) { |
|
651 SkASSERT(!dst->isInverseFillType()); |
|
652 dst->toggleInverseFillType(); |
|
653 } |
|
654 } |
|
655 |
|
656 static SkPath::Direction reverse_direction(SkPath::Direction dir) { |
|
657 SkASSERT(SkPath::kUnknown_Direction != dir); |
|
658 return SkPath::kCW_Direction == dir ? SkPath::kCCW_Direction : SkPath::kCW_Direction; |
|
659 } |
|
660 |
|
661 static void addBevel(SkPath* path, const SkRect& r, const SkRect& outer, SkPath::Direction dir) { |
|
662 SkPoint pts[8]; |
|
663 |
|
664 if (SkPath::kCW_Direction == dir) { |
|
665 pts[0].set(r.fLeft, outer.fTop); |
|
666 pts[1].set(r.fRight, outer.fTop); |
|
667 pts[2].set(outer.fRight, r.fTop); |
|
668 pts[3].set(outer.fRight, r.fBottom); |
|
669 pts[4].set(r.fRight, outer.fBottom); |
|
670 pts[5].set(r.fLeft, outer.fBottom); |
|
671 pts[6].set(outer.fLeft, r.fBottom); |
|
672 pts[7].set(outer.fLeft, r.fTop); |
|
673 } else { |
|
674 pts[7].set(r.fLeft, outer.fTop); |
|
675 pts[6].set(r.fRight, outer.fTop); |
|
676 pts[5].set(outer.fRight, r.fTop); |
|
677 pts[4].set(outer.fRight, r.fBottom); |
|
678 pts[3].set(r.fRight, outer.fBottom); |
|
679 pts[2].set(r.fLeft, outer.fBottom); |
|
680 pts[1].set(outer.fLeft, r.fBottom); |
|
681 pts[0].set(outer.fLeft, r.fTop); |
|
682 } |
|
683 path->addPoly(pts, 8, true); |
|
684 } |
|
685 |
|
686 void SkStroke::strokeRect(const SkRect& origRect, SkPath* dst, |
|
687 SkPath::Direction dir) const { |
|
688 SkASSERT(dst != NULL); |
|
689 dst->reset(); |
|
690 |
|
691 SkScalar radius = SkScalarHalf(fWidth); |
|
692 if (radius <= 0) { |
|
693 return; |
|
694 } |
|
695 |
|
696 SkScalar rw = origRect.width(); |
|
697 SkScalar rh = origRect.height(); |
|
698 if ((rw < 0) ^ (rh < 0)) { |
|
699 dir = reverse_direction(dir); |
|
700 } |
|
701 SkRect rect(origRect); |
|
702 rect.sort(); |
|
703 // reassign these, now that we know they'll be >= 0 |
|
704 rw = rect.width(); |
|
705 rh = rect.height(); |
|
706 |
|
707 SkRect r(rect); |
|
708 r.outset(radius, radius); |
|
709 |
|
710 SkPaint::Join join = (SkPaint::Join)fJoin; |
|
711 if (SkPaint::kMiter_Join == join && fMiterLimit < SK_ScalarSqrt2) { |
|
712 join = SkPaint::kBevel_Join; |
|
713 } |
|
714 |
|
715 switch (join) { |
|
716 case SkPaint::kMiter_Join: |
|
717 dst->addRect(r, dir); |
|
718 break; |
|
719 case SkPaint::kBevel_Join: |
|
720 addBevel(dst, rect, r, dir); |
|
721 break; |
|
722 case SkPaint::kRound_Join: |
|
723 dst->addRoundRect(r, radius, radius, dir); |
|
724 break; |
|
725 default: |
|
726 break; |
|
727 } |
|
728 |
|
729 if (fWidth < SkMinScalar(rw, rh) && !fDoFill) { |
|
730 r = rect; |
|
731 r.inset(radius, radius); |
|
732 dst->addRect(r, reverse_direction(dir)); |
|
733 } |
|
734 } |