OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2006 The Android Open Source Project | 2 * Copyright 2006 The Android Open Source Project |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "SkDashPathEffect.h" | 8 #include "SkDashPathEffect.h" |
| 9 |
| 10 #include "SkDashPathPriv.h" |
9 #include "SkReadBuffer.h" | 11 #include "SkReadBuffer.h" |
10 #include "SkWriteBuffer.h" | 12 #include "SkWriteBuffer.h" |
11 #include "SkPathMeasure.h" | |
12 | |
13 static inline int is_even(int x) { | |
14 return (~x) << 31; | |
15 } | |
16 | |
17 static SkScalar FindFirstInterval(const SkScalar intervals[], SkScalar phase, | |
18 int32_t* index, int count) { | |
19 for (int i = 0; i < count; ++i) { | |
20 if (phase > intervals[i]) { | |
21 phase -= intervals[i]; | |
22 } else { | |
23 *index = i; | |
24 return intervals[i] - phase; | |
25 } | |
26 } | |
27 // If we get here, phase "appears" to be larger than our length. This | |
28 // shouldn't happen with perfect precision, but we can accumulate errors | |
29 // during the initial length computation (rounding can make our sum be too | |
30 // big or too small. In that event, we just have to eat the error here. | |
31 *index = 0; | |
32 return intervals[0]; | |
33 } | |
34 | |
35 void SkDashPathEffect::setInternalMembers(SkScalar phase) { | |
36 SkScalar len = 0; | |
37 for (int i = 0; i < fCount; i++) { | |
38 len += fIntervals[i]; | |
39 } | |
40 fIntervalLength = len; | |
41 | |
42 // watch out for values that might make us go out of bounds | |
43 if ((len > 0) && SkScalarIsFinite(phase) && SkScalarIsFinite(len)) { | |
44 | |
45 // Adjust phase to be between 0 and len, "flipping" phase if negative. | |
46 // e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80 | |
47 if (phase < 0) { | |
48 phase = -phase; | |
49 if (phase > len) { | |
50 phase = SkScalarMod(phase, len); | |
51 } | |
52 phase = len - phase; | |
53 | |
54 // Due to finite precision, it's possible that phase == len, | |
55 // even after the subtract (if len >>> phase), so fix that here. | |
56 // This fixes http://crbug.com/124652 . | |
57 SkASSERT(phase <= len); | |
58 if (phase == len) { | |
59 phase = 0; | |
60 } | |
61 } else if (phase >= len) { | |
62 phase = SkScalarMod(phase, len); | |
63 } | |
64 SkASSERT(phase >= 0 && phase < len); | |
65 | |
66 fPhase = phase; | |
67 | |
68 fInitialDashLength = FindFirstInterval(fIntervals, fPhase, | |
69 &fInitialDashIndex, fCount); | |
70 | |
71 SkASSERT(fInitialDashLength >= 0); | |
72 SkASSERT(fInitialDashIndex >= 0 && fInitialDashIndex < fCount); | |
73 } else { | |
74 fInitialDashLength = -1; // signal bad dash intervals | |
75 } | |
76 } | |
77 | 13 |
78 SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count, | 14 SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count, |
79 SkScalar phase) { | 15 SkScalar phase) { |
80 SkASSERT(intervals); | 16 SkASSERT(intervals); |
81 SkASSERT(count > 1 && SkAlign2(count) == count); | 17 SkASSERT(count > 1 && SkAlign2(count) == count); |
82 | 18 |
83 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count); | 19 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count); |
84 fCount = count; | 20 fCount = count; |
85 for (int i = 0; i < count; i++) { | 21 for (int i = 0; i < count; i++) { |
86 SkASSERT(intervals[i] >= 0); | 22 SkASSERT(intervals[i] >= 0); |
87 fIntervals[i] = intervals[i]; | 23 fIntervals[i] = intervals[i]; |
88 } | 24 } |
89 | 25 |
90 this->setInternalMembers(phase); | 26 // set the internal data members |
| 27 SkDashPath::CalcDashParameters(phase, fIntervals, fCount, &fInitialDashLengt
h, |
| 28 &fInitialDashIndex, &fIntervalLength, &fPhase
); |
91 } | 29 } |
92 | 30 |
93 SkDashPathEffect::~SkDashPathEffect() { | 31 SkDashPathEffect::~SkDashPathEffect() { |
94 sk_free(fIntervals); | 32 sk_free(fIntervals); |
95 } | 33 } |
96 | 34 |
97 static void outset_for_stroke(SkRect* rect, const SkStrokeRec& rec) { | |
98 SkScalar radius = SkScalarHalf(rec.getWidth()); | |
99 if (0 == radius) { | |
100 radius = SK_Scalar1; // hairlines | |
101 } | |
102 if (SkPaint::kMiter_Join == rec.getJoin()) { | |
103 radius = SkScalarMul(radius, rec.getMiter()); | |
104 } | |
105 rect->outset(radius, radius); | |
106 } | |
107 | |
108 // Only handles lines for now. If returns true, dstPath is the new (smaller) | |
109 // path. If returns false, then dstPath parameter is ignored. | |
110 static bool cull_path(const SkPath& srcPath, const SkStrokeRec& rec, | |
111 const SkRect* cullRect, SkScalar intervalLength, | |
112 SkPath* dstPath) { | |
113 if (NULL == cullRect) { | |
114 return false; | |
115 } | |
116 | |
117 SkPoint pts[2]; | |
118 if (!srcPath.isLine(pts)) { | |
119 return false; | |
120 } | |
121 | |
122 SkRect bounds = *cullRect; | |
123 outset_for_stroke(&bounds, rec); | |
124 | |
125 SkScalar dx = pts[1].x() - pts[0].x(); | |
126 SkScalar dy = pts[1].y() - pts[0].y(); | |
127 | |
128 // just do horizontal lines for now (lazy) | |
129 if (dy) { | |
130 return false; | |
131 } | |
132 | |
133 SkScalar minX = pts[0].fX; | |
134 SkScalar maxX = pts[1].fX; | |
135 | |
136 if (maxX < bounds.fLeft || minX > bounds.fRight) { | |
137 return false; | |
138 } | |
139 | |
140 if (dx < 0) { | |
141 SkTSwap(minX, maxX); | |
142 } | |
143 | |
144 // Now we actually perform the chop, removing the excess to the left and | |
145 // right of the bounds (keeping our new line "in phase" with the dash, | |
146 // hence the (mod intervalLength). | |
147 | |
148 if (minX < bounds.fLeft) { | |
149 minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX, | |
150 intervalLength); | |
151 } | |
152 if (maxX > bounds.fRight) { | |
153 maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight, | |
154 intervalLength); | |
155 } | |
156 | |
157 SkASSERT(maxX >= minX); | |
158 if (dx < 0) { | |
159 SkTSwap(minX, maxX); | |
160 } | |
161 pts[0].fX = minX; | |
162 pts[1].fX = maxX; | |
163 | |
164 dstPath->moveTo(pts[0]); | |
165 dstPath->lineTo(pts[1]); | |
166 return true; | |
167 } | |
168 | |
169 class SpecialLineRec { | |
170 public: | |
171 bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec, | |
172 int intervalCount, SkScalar intervalLength) { | |
173 if (rec->isHairlineStyle() || !src.isLine(fPts)) { | |
174 return false; | |
175 } | |
176 | |
177 // can relax this in the future, if we handle square and round caps | |
178 if (SkPaint::kButt_Cap != rec->getCap()) { | |
179 return false; | |
180 } | |
181 | |
182 SkScalar pathLength = SkPoint::Distance(fPts[0], fPts[1]); | |
183 | |
184 fTangent = fPts[1] - fPts[0]; | |
185 if (fTangent.isZero()) { | |
186 return false; | |
187 } | |
188 | |
189 fPathLength = pathLength; | |
190 fTangent.scale(SkScalarInvert(pathLength)); | |
191 fTangent.rotateCCW(&fNormal); | |
192 fNormal.scale(SkScalarHalf(rec->getWidth())); | |
193 | |
194 // now estimate how many quads will be added to the path | |
195 // resulting segments = pathLen * intervalCount / intervalLen | |
196 // resulting points = 4 * segments | |
197 | |
198 SkScalar ptCount = SkScalarMulDiv(pathLength, | |
199 SkIntToScalar(intervalCount), | |
200 intervalLength); | |
201 int n = SkScalarCeilToInt(ptCount) << 2; | |
202 dst->incReserve(n); | |
203 | |
204 // we will take care of the stroking | |
205 rec->setFillStyle(); | |
206 return true; | |
207 } | |
208 | |
209 void addSegment(SkScalar d0, SkScalar d1, SkPath* path) const { | |
210 SkASSERT(d0 < fPathLength); | |
211 // clamp the segment to our length | |
212 if (d1 > fPathLength) { | |
213 d1 = fPathLength; | |
214 } | |
215 | |
216 SkScalar x0 = fPts[0].fX + SkScalarMul(fTangent.fX, d0); | |
217 SkScalar x1 = fPts[0].fX + SkScalarMul(fTangent.fX, d1); | |
218 SkScalar y0 = fPts[0].fY + SkScalarMul(fTangent.fY, d0); | |
219 SkScalar y1 = fPts[0].fY + SkScalarMul(fTangent.fY, d1); | |
220 | |
221 SkPoint pts[4]; | |
222 pts[0].set(x0 + fNormal.fX, y0 + fNormal.fY); // moveTo | |
223 pts[1].set(x1 + fNormal.fX, y1 + fNormal.fY); // lineTo | |
224 pts[2].set(x1 - fNormal.fX, y1 - fNormal.fY); // lineTo | |
225 pts[3].set(x0 - fNormal.fX, y0 - fNormal.fY); // lineTo | |
226 | |
227 path->addPoly(pts, SK_ARRAY_COUNT(pts), false); | |
228 } | |
229 | |
230 private: | |
231 SkPoint fPts[2]; | |
232 SkVector fTangent; | |
233 SkVector fNormal; | |
234 SkScalar fPathLength; | |
235 }; | |
236 | |
237 bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src, | 35 bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src, |
238 SkStrokeRec* rec, const SkRect* cullRect) const { | 36 SkStrokeRec* rec, const SkRect* cullRect) const { |
239 // we do nothing if the src wants to be filled, or if our dashlength is 0 | 37 return SkDashPath::FilterDashPath(dst, src, rec, cullRect, fIntervals, fCoun
t, |
240 if (rec->isFillStyle() || fInitialDashLength < 0) { | 38 fInitialDashLength, fInitialDashIndex, fIn
tervalLength); |
241 return false; | |
242 } | |
243 | |
244 const SkScalar* intervals = fIntervals; | |
245 SkScalar dashCount = 0; | |
246 int segCount = 0; | |
247 | |
248 SkPath cullPathStorage; | |
249 const SkPath* srcPtr = &src; | |
250 if (cull_path(src, *rec, cullRect, fIntervalLength, &cullPathStorage)) { | |
251 srcPtr = &cullPathStorage; | |
252 } | |
253 | |
254 SpecialLineRec lineRec; | |
255 bool specialLine = lineRec.init(*srcPtr, dst, rec, fCount >> 1, fIntervalLen
gth); | |
256 | |
257 SkPathMeasure meas(*srcPtr, false); | |
258 | |
259 do { | |
260 bool skipFirstSegment = meas.isClosed(); | |
261 bool addedSegment = false; | |
262 SkScalar length = meas.getLength(); | |
263 int index = fInitialDashIndex; | |
264 | |
265 // Since the path length / dash length ratio may be arbitrarily large, w
e can exert | |
266 // significant memory pressure while attempting to build the filtered pa
th. To avoid this, | |
267 // we simply give up dashing beyond a certain threshold. | |
268 // | |
269 // The original bug report (http://crbug.com/165432) is based on a path
yielding more than | |
270 // 90 million dash segments and crashing the memory allocator. A limit o
f 1 million | |
271 // segments seems reasonable: at 2 verbs per segment * 9 bytes per verb,
this caps the | |
272 // maximum dash memory overhead at roughly 17MB per path. | |
273 static const SkScalar kMaxDashCount = 1000000; | |
274 dashCount += length * (fCount >> 1) / fIntervalLength; | |
275 if (dashCount > kMaxDashCount) { | |
276 dst->reset(); | |
277 return false; | |
278 } | |
279 | |
280 // Using double precision to avoid looping indefinitely due to single pr
ecision rounding | |
281 // (for extreme path_length/dash_length ratios). See test_infinite_dash(
) unittest. | |
282 double distance = 0; | |
283 double dlen = fInitialDashLength; | |
284 | |
285 while (distance < length) { | |
286 SkASSERT(dlen >= 0); | |
287 addedSegment = false; | |
288 if (is_even(index) && dlen > 0 && !skipFirstSegment) { | |
289 addedSegment = true; | |
290 ++segCount; | |
291 | |
292 if (specialLine) { | |
293 lineRec.addSegment(SkDoubleToScalar(distance), | |
294 SkDoubleToScalar(distance + dlen), | |
295 dst); | |
296 } else { | |
297 meas.getSegment(SkDoubleToScalar(distance), | |
298 SkDoubleToScalar(distance + dlen), | |
299 dst, true); | |
300 } | |
301 } | |
302 distance += dlen; | |
303 | |
304 // clear this so we only respect it the first time around | |
305 skipFirstSegment = false; | |
306 | |
307 // wrap around our intervals array if necessary | |
308 index += 1; | |
309 SkASSERT(index <= fCount); | |
310 if (index == fCount) { | |
311 index = 0; | |
312 } | |
313 | |
314 // fetch our next dlen | |
315 dlen = intervals[index]; | |
316 } | |
317 | |
318 // extend if we ended on a segment and we need to join up with the (skip
ped) initial segment | |
319 if (meas.isClosed() && is_even(fInitialDashIndex) && | |
320 fInitialDashLength > 0) { | |
321 meas.getSegment(0, fInitialDashLength, dst, !addedSegment); | |
322 ++segCount; | |
323 } | |
324 } while (meas.nextContour()); | |
325 | |
326 if (segCount > 1) { | |
327 dst->setConvexity(SkPath::kConcave_Convexity); | |
328 } | |
329 | |
330 return true; | |
331 } | 39 } |
332 | 40 |
333 // Currently asPoints is more restrictive then it needs to be. In the future | 41 // Currently asPoints is more restrictive then it needs to be. In the future |
334 // we need to: | 42 // we need to: |
335 // allow kRound_Cap capping (could allow rotations in the matrix with this) | 43 // allow kRound_Cap capping (could allow rotations in the matrix with this) |
336 // allow paths to be returned | 44 // allow paths to be returned |
337 bool SkDashPathEffect::asPoints(PointData* results, | 45 bool SkDashPathEffect::asPoints(PointData* results, |
338 const SkPath& src, | 46 const SkPath& src, |
339 const SkStrokeRec& rec, | 47 const SkStrokeRec& rec, |
340 const SkMatrix& matrix, | 48 const SkMatrix& matrix, |
(...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
563 | 271 |
564 if (useOldPic) { | 272 if (useOldPic) { |
565 fPhase = 0; | 273 fPhase = 0; |
566 if (fInitialDashLength != -1) { // Signal for bad dash interval | 274 if (fInitialDashLength != -1) { // Signal for bad dash interval |
567 for (int i = 0; i < fInitialDashIndex; ++i) { | 275 for (int i = 0; i < fInitialDashIndex; ++i) { |
568 fPhase += fIntervals[i]; | 276 fPhase += fIntervals[i]; |
569 } | 277 } |
570 fPhase += fIntervals[fInitialDashIndex] - fInitialDashLength; | 278 fPhase += fIntervals[fInitialDashIndex] - fInitialDashLength; |
571 } | 279 } |
572 } else { | 280 } else { |
573 this->setInternalMembers(fPhase); | 281 // set the internal data members, fPhase should have been between 0 and
intervalLength |
| 282 // when written to buffer so no need to adjust it |
| 283 SkDashPath::CalcDashParameters(fPhase, fIntervals, fCount, &fInitialDash
Length, |
| 284 &fInitialDashIndex, &fIntervalLength); |
574 } | 285 } |
575 } | 286 } |
OLD | NEW |