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" | |
11 #include "SkReadBuffer.h" | 9 #include "SkReadBuffer.h" |
12 #include "SkWriteBuffer.h" | 10 #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 } |
13 | 77 |
14 SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count, | 78 SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count, |
15 SkScalar phase) { | 79 SkScalar phase) { |
16 SkASSERT(intervals); | 80 SkASSERT(intervals); |
17 SkASSERT(count > 1 && SkAlign2(count) == count); | 81 SkASSERT(count > 1 && SkAlign2(count) == count); |
18 | 82 |
19 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count); | 83 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count); |
20 fCount = count; | 84 fCount = count; |
21 for (int i = 0; i < count; i++) { | 85 for (int i = 0; i < count; i++) { |
22 SkASSERT(intervals[i] >= 0); | 86 SkASSERT(intervals[i] >= 0); |
23 fIntervals[i] = intervals[i]; | 87 fIntervals[i] = intervals[i]; |
24 } | 88 } |
25 | 89 |
26 // set the internal data members | 90 this->setInternalMembers(phase); |
27 SkDashPath::CalcDashParameters(phase, fIntervals, fCount, &fInitialDashLengt
h, | |
28 &fInitialDashIndex, &fIntervalLength, &fPhase
); | |
29 } | 91 } |
30 | 92 |
31 SkDashPathEffect::~SkDashPathEffect() { | 93 SkDashPathEffect::~SkDashPathEffect() { |
32 sk_free(fIntervals); | 94 sk_free(fIntervals); |
33 } | 95 } |
34 | 96 |
| 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 |
35 bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src, | 237 bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src, |
36 SkStrokeRec* rec, const SkRect* cullRect) const { | 238 SkStrokeRec* rec, const SkRect* cullRect) const { |
37 return SkDashPath::FilterDashPath(dst, src, rec, cullRect, fIntervals, fCoun
t, | 239 // we do nothing if the src wants to be filled, or if our dashlength is 0 |
38 fInitialDashLength, fInitialDashIndex, fIn
tervalLength); | 240 if (rec->isFillStyle() || fInitialDashLength < 0) { |
| 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; |
39 } | 331 } |
40 | 332 |
41 // Currently asPoints is more restrictive then it needs to be. In the future | 333 // Currently asPoints is more restrictive then it needs to be. In the future |
42 // we need to: | 334 // we need to: |
43 // allow kRound_Cap capping (could allow rotations in the matrix with this) | 335 // allow kRound_Cap capping (could allow rotations in the matrix with this) |
44 // allow paths to be returned | 336 // allow paths to be returned |
45 bool SkDashPathEffect::asPoints(PointData* results, | 337 bool SkDashPathEffect::asPoints(PointData* results, |
46 const SkPath& src, | 338 const SkPath& src, |
47 const SkStrokeRec& rec, | 339 const SkStrokeRec& rec, |
48 const SkMatrix& matrix, | 340 const SkMatrix& matrix, |
(...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
271 | 563 |
272 if (useOldPic) { | 564 if (useOldPic) { |
273 fPhase = 0; | 565 fPhase = 0; |
274 if (fInitialDashLength != -1) { // Signal for bad dash interval | 566 if (fInitialDashLength != -1) { // Signal for bad dash interval |
275 for (int i = 0; i < fInitialDashIndex; ++i) { | 567 for (int i = 0; i < fInitialDashIndex; ++i) { |
276 fPhase += fIntervals[i]; | 568 fPhase += fIntervals[i]; |
277 } | 569 } |
278 fPhase += fIntervals[fInitialDashIndex] - fInitialDashLength; | 570 fPhase += fIntervals[fInitialDashIndex] - fInitialDashLength; |
279 } | 571 } |
280 } else { | 572 } else { |
281 // set the internal data members, fPhase should have been between 0 and
intervalLength | 573 this->setInternalMembers(fPhase); |
282 // when written to buffer so no need to adjust it | |
283 SkDashPath::CalcDashParameters(fPhase, fIntervals, fCount, &fInitialDash
Length, | |
284 &fInitialDashIndex, &fIntervalLength); | |
285 } | 574 } |
286 } | 575 } |
OLD | NEW |