blob: 6f132e0be573a568dcda60a45ee11f31d8daecb4 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkDashPathEffect.h"
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +000011#include "SkReadBuffer.h"
12#include "SkWriteBuffer.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000013#include "SkPathMeasure.h"
14
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000015static inline int is_even(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000016 return (~x) << 31;
17}
18
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000019static SkScalar FindFirstInterval(const SkScalar intervals[], SkScalar phase,
reed@google.comd9ee3482012-08-06 14:58:35 +000020 int32_t* index, int count) {
21 for (int i = 0; i < count; ++i) {
22 if (phase > intervals[i]) {
23 phase -= intervals[i];
24 } else {
25 *index = i;
26 return intervals[i] - phase;
27 }
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000028 }
reed@google.comd9ee3482012-08-06 14:58:35 +000029 // If we get here, phase "appears" to be larger than our length. This
30 // shouldn't happen with perfect precision, but we can accumulate errors
31 // during the initial length computation (rounding can make our sum be too
32 // big or too small. In that event, we just have to eat the error here.
33 *index = 0;
34 return intervals[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +000035}
36
commit-bot@chromium.orgaec14382014-04-22 15:21:18 +000037void SkDashPathEffect::setInternalMembers(SkScalar phase) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000038 SkScalar len = 0;
commit-bot@chromium.orgaec14382014-04-22 15:21:18 +000039 for (int i = 0; i < fCount; i++) {
40 len += fIntervals[i];
reed@android.com8a1c16f2008-12-17 15:59:43 +000041 }
42 fIntervalLength = len;
43
epoger@google.com20bf4ca2012-04-27 13:34:52 +000044 // watch out for values that might make us go out of bounds
45 if ((len > 0) && SkScalarIsFinite(phase) && SkScalarIsFinite(len)) {
46
47 // Adjust phase to be between 0 and len, "flipping" phase if negative.
48 // e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000049 if (phase < 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000050 phase = -phase;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000051 if (phase > len) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000052 phase = SkScalarMod(phase, len);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000053 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000054 phase = len - phase;
epoger@google.com20bf4ca2012-04-27 13:34:52 +000055
56 // Due to finite precision, it's possible that phase == len,
57 // even after the subtract (if len >>> phase), so fix that here.
58 // This fixes http://crbug.com/124652 .
reed@google.com1df888b2012-04-24 22:47:21 +000059 SkASSERT(phase <= len);
60 if (phase == len) {
61 phase = 0;
62 }
epoger@google.com20bf4ca2012-04-27 13:34:52 +000063 } else if (phase >= len) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 phase = SkScalarMod(phase, len);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000065 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000066 SkASSERT(phase >= 0 && phase < len);
epoger@google.com20bf4ca2012-04-27 13:34:52 +000067
commit-bot@chromium.orgaec14382014-04-22 15:21:18 +000068 fPhase = phase;
69
70 fInitialDashLength = FindFirstInterval(fIntervals, fPhase,
71 &fInitialDashIndex, fCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +000072
73 SkASSERT(fInitialDashLength >= 0);
74 SkASSERT(fInitialDashIndex >= 0 && fInitialDashIndex < fCount);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000075 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 fInitialDashLength = -1; // signal bad dash intervals
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000077 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000078}
79
commit-bot@chromium.orgaec14382014-04-22 15:21:18 +000080SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count,
81 SkScalar phase) {
82 SkASSERT(intervals);
83 SkASSERT(count > 1 && SkAlign2(count) == count);
84
85 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count);
86 fCount = count;
87 for (int i = 0; i < count; i++) {
88 SkASSERT(intervals[i] >= 0);
89 fIntervals[i] = intervals[i];
90 }
91
92 this->setInternalMembers(phase);
93}
94
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000095SkDashPathEffect::~SkDashPathEffect() {
reed@android.com8a1c16f2008-12-17 15:59:43 +000096 sk_free(fIntervals);
97}
98
reed@google.com4bbdeac2013-01-24 21:03:11 +000099static void outset_for_stroke(SkRect* rect, const SkStrokeRec& rec) {
100 SkScalar radius = SkScalarHalf(rec.getWidth());
101 if (0 == radius) {
102 radius = SK_Scalar1; // hairlines
103 }
104 if (SkPaint::kMiter_Join == rec.getJoin()) {
105 radius = SkScalarMul(radius, rec.getMiter());
106 }
107 rect->outset(radius, radius);
108}
109
110// Only handles lines for now. If returns true, dstPath is the new (smaller)
111// path. If returns false, then dstPath parameter is ignored.
112static bool cull_path(const SkPath& srcPath, const SkStrokeRec& rec,
113 const SkRect* cullRect, SkScalar intervalLength,
114 SkPath* dstPath) {
115 if (NULL == cullRect) {
116 return false;
117 }
118
119 SkPoint pts[2];
120 if (!srcPath.isLine(pts)) {
121 return false;
122 }
123
124 SkRect bounds = *cullRect;
125 outset_for_stroke(&bounds, rec);
126
127 SkScalar dx = pts[1].x() - pts[0].x();
128 SkScalar dy = pts[1].y() - pts[0].y();
skia.committer@gmail.com4024f322013-01-25 07:06:46 +0000129
reed@google.com4bbdeac2013-01-24 21:03:11 +0000130 // just do horizontal lines for now (lazy)
131 if (dy) {
132 return false;
133 }
134
135 SkScalar minX = pts[0].fX;
136 SkScalar maxX = pts[1].fX;
skia.committer@gmail.com4024f322013-01-25 07:06:46 +0000137
reed@google.com4bbdeac2013-01-24 21:03:11 +0000138 if (maxX < bounds.fLeft || minX > bounds.fRight) {
139 return false;
140 }
skia.committer@gmail.com4024f322013-01-25 07:06:46 +0000141
reed@google.com4bbdeac2013-01-24 21:03:11 +0000142 if (dx < 0) {
143 SkTSwap(minX, maxX);
144 }
145
146 // Now we actually perform the chop, removing the excess to the left and
147 // right of the bounds (keeping our new line "in phase" with the dash,
148 // hence the (mod intervalLength).
149
150 if (minX < bounds.fLeft) {
151 minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX,
152 intervalLength);
153 }
154 if (maxX > bounds.fRight) {
155 maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight,
156 intervalLength);
157 }
skia.committer@gmail.com4024f322013-01-25 07:06:46 +0000158
robertphillips@google.com68f670f2013-01-28 20:55:42 +0000159 SkASSERT(maxX >= minX);
reed@google.com4bbdeac2013-01-24 21:03:11 +0000160 if (dx < 0) {
161 SkTSwap(minX, maxX);
162 }
163 pts[0].fX = minX;
164 pts[1].fX = maxX;
skia.committer@gmail.com4024f322013-01-25 07:06:46 +0000165
reed@google.com4bbdeac2013-01-24 21:03:11 +0000166 dstPath->moveTo(pts[0]);
167 dstPath->lineTo(pts[1]);
168 return true;
169}
170
reed@google.com3ec68f02012-05-29 20:48:50 +0000171class SpecialLineRec {
172public:
173 bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec,
reed@google.com3ec68f02012-05-29 20:48:50 +0000174 int intervalCount, SkScalar intervalLength) {
175 if (rec->isHairlineStyle() || !src.isLine(fPts)) {
176 return false;
177 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000178
reed@google.com3ec68f02012-05-29 20:48:50 +0000179 // can relax this in the future, if we handle square and round caps
180 if (SkPaint::kButt_Cap != rec->getCap()) {
181 return false;
182 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000183
reed@google.com4bbdeac2013-01-24 21:03:11 +0000184 SkScalar pathLength = SkPoint::Distance(fPts[0], fPts[1]);
185
reed@google.com3ec68f02012-05-29 20:48:50 +0000186 fTangent = fPts[1] - fPts[0];
187 if (fTangent.isZero()) {
188 return false;
189 }
190
191 fPathLength = pathLength;
192 fTangent.scale(SkScalarInvert(pathLength));
193 fTangent.rotateCCW(&fNormal);
194 fNormal.scale(SkScalarHalf(rec->getWidth()));
195
196 // now estimate how many quads will be added to the path
197 // resulting segments = pathLen * intervalCount / intervalLen
198 // resulting points = 4 * segments
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000199
reed@google.com3ec68f02012-05-29 20:48:50 +0000200 SkScalar ptCount = SkScalarMulDiv(pathLength,
201 SkIntToScalar(intervalCount),
202 intervalLength);
203 int n = SkScalarCeilToInt(ptCount) << 2;
204 dst->incReserve(n);
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000205
reed@google.com3ec68f02012-05-29 20:48:50 +0000206 // we will take care of the stroking
207 rec->setFillStyle();
208 return true;
209 }
210
211 void addSegment(SkScalar d0, SkScalar d1, SkPath* path) const {
212 SkASSERT(d0 < fPathLength);
213 // clamp the segment to our length
214 if (d1 > fPathLength) {
215 d1 = fPathLength;
216 }
217
218 SkScalar x0 = fPts[0].fX + SkScalarMul(fTangent.fX, d0);
219 SkScalar x1 = fPts[0].fX + SkScalarMul(fTangent.fX, d1);
220 SkScalar y0 = fPts[0].fY + SkScalarMul(fTangent.fY, d0);
221 SkScalar y1 = fPts[0].fY + SkScalarMul(fTangent.fY, d1);
222
223 SkPoint pts[4];
224 pts[0].set(x0 + fNormal.fX, y0 + fNormal.fY); // moveTo
225 pts[1].set(x1 + fNormal.fX, y1 + fNormal.fY); // lineTo
226 pts[2].set(x1 - fNormal.fX, y1 - fNormal.fY); // lineTo
227 pts[3].set(x0 - fNormal.fX, y0 - fNormal.fY); // lineTo
228
229 path->addPoly(pts, SK_ARRAY_COUNT(pts), false);
230 }
231
232private:
233 SkPoint fPts[2];
234 SkVector fTangent;
235 SkVector fNormal;
236 SkScalar fPathLength;
237};
238
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000239bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
reed@google.com4bbdeac2013-01-24 21:03:11 +0000240 SkStrokeRec* rec, const SkRect* cullRect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000241 // we do nothing if the src wants to be filled, or if our dashlength is 0
reed@google.comfd4be262012-05-25 01:04:12 +0000242 if (rec->isFillStyle() || fInitialDashLength < 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243 return false;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000244 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000245
reed@android.com8a1c16f2008-12-17 15:59:43 +0000246 const SkScalar* intervals = fIntervals;
fmalita@google.com6b18d242012-12-17 16:27:34 +0000247 SkScalar dashCount = 0;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000248 int segCount = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000249
reed@google.com4bbdeac2013-01-24 21:03:11 +0000250 SkPath cullPathStorage;
251 const SkPath* srcPtr = &src;
252 if (cull_path(src, *rec, cullRect, fIntervalLength, &cullPathStorage)) {
253 srcPtr = &cullPathStorage;
254 }
skia.committer@gmail.com4024f322013-01-25 07:06:46 +0000255
reed@google.com3ec68f02012-05-29 20:48:50 +0000256 SpecialLineRec lineRec;
reed@google.com70375522013-01-25 14:52:11 +0000257 bool specialLine = lineRec.init(*srcPtr, dst, rec, fCount >> 1, fIntervalLength);
reed@google.com4bbdeac2013-01-24 21:03:11 +0000258
259 SkPathMeasure meas(*srcPtr, false);
reed@google.com3ec68f02012-05-29 20:48:50 +0000260
reed@android.com8a1c16f2008-12-17 15:59:43 +0000261 do {
262 bool skipFirstSegment = meas.isClosed();
263 bool addedSegment = false;
264 SkScalar length = meas.getLength();
265 int index = fInitialDashIndex;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000266
fmalita@google.com6b18d242012-12-17 16:27:34 +0000267 // Since the path length / dash length ratio may be arbitrarily large, we can exert
268 // significant memory pressure while attempting to build the filtered path. To avoid this,
269 // we simply give up dashing beyond a certain threshold.
270 //
271 // The original bug report (http://crbug.com/165432) is based on a path yielding more than
272 // 90 million dash segments and crashing the memory allocator. A limit of 1 million
273 // segments seems reasonable: at 2 verbs per segment * 9 bytes per verb, this caps the
274 // maximum dash memory overhead at roughly 17MB per path.
275 static const SkScalar kMaxDashCount = 1000000;
276 dashCount += length * (fCount >> 1) / fIntervalLength;
277 if (dashCount > kMaxDashCount) {
278 dst->reset();
279 return false;
280 }
281
fmalita@google.combfa04012012-12-12 22:13:58 +0000282 // Using double precision to avoid looping indefinitely due to single precision rounding
283 // (for extreme path_length/dash_length ratios). See test_infinite_dash() unittest.
284 double distance = 0;
commit-bot@chromium.org35c03fb2014-03-31 18:52:51 +0000285 double dlen = fInitialDashLength;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000286
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000287 while (distance < length) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000288 SkASSERT(dlen >= 0);
289 addedSegment = false;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000290 if (is_even(index) && dlen > 0 && !skipFirstSegment) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000291 addedSegment = true;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000292 ++segCount;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000293
reed@google.com3ec68f02012-05-29 20:48:50 +0000294 if (specialLine) {
skia.committer@gmail.coma7aedfe2012-12-15 02:03:10 +0000295 lineRec.addSegment(SkDoubleToScalar(distance),
296 SkDoubleToScalar(distance + dlen),
robertphillips@google.com441a0052012-12-14 13:55:06 +0000297 dst);
reed@google.com3ec68f02012-05-29 20:48:50 +0000298 } else {
skia.committer@gmail.coma7aedfe2012-12-15 02:03:10 +0000299 meas.getSegment(SkDoubleToScalar(distance),
300 SkDoubleToScalar(distance + dlen),
robertphillips@google.com441a0052012-12-14 13:55:06 +0000301 dst, true);
reed@google.com3ec68f02012-05-29 20:48:50 +0000302 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000303 }
304 distance += dlen;
305
306 // clear this so we only respect it the first time around
307 skipFirstSegment = false;
308
309 // wrap around our intervals array if necessary
310 index += 1;
311 SkASSERT(index <= fCount);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000312 if (index == fCount) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000313 index = 0;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000314 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000315
316 // fetch our next dlen
commit-bot@chromium.org35c03fb2014-03-31 18:52:51 +0000317 dlen = intervals[index];
reed@android.com8a1c16f2008-12-17 15:59:43 +0000318 }
319
320 // extend if we ended on a segment and we need to join up with the (skipped) initial segment
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000321 if (meas.isClosed() && is_even(fInitialDashIndex) &&
322 fInitialDashLength > 0) {
commit-bot@chromium.org35c03fb2014-03-31 18:52:51 +0000323 meas.getSegment(0, fInitialDashLength, dst, !addedSegment);
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000324 ++segCount;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000325 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000326 } while (meas.nextContour());
reed@google.com3ec68f02012-05-29 20:48:50 +0000327
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000328 if (segCount > 1) {
329 dst->setConvexity(SkPath::kConcave_Convexity);
330 }
331
reed@android.com8a1c16f2008-12-17 15:59:43 +0000332 return true;
333}
334
robertphillips@google.com629ab542012-11-28 17:18:11 +0000335// Currently asPoints is more restrictive then it needs to be. In the future
336// we need to:
337// allow kRound_Cap capping (could allow rotations in the matrix with this)
robertphillips@google.com6d875572012-12-17 18:56:29 +0000338// allow paths to be returned
robertphillips@google.com629ab542012-11-28 17:18:11 +0000339bool SkDashPathEffect::asPoints(PointData* results,
340 const SkPath& src,
341 const SkStrokeRec& rec,
reed@google.com4bbdeac2013-01-24 21:03:11 +0000342 const SkMatrix& matrix,
343 const SkRect* cullRect) const {
robertphillips@google.com6d875572012-12-17 18:56:29 +0000344 // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out
345 if (fInitialDashLength < 0 || 0 >= rec.getWidth()) {
robertphillips@google.com629ab542012-11-28 17:18:11 +0000346 return false;
347 }
348
robertphillips@google.com6d875572012-12-17 18:56:29 +0000349 // TODO: this next test could be eased up. We could allow any number of
350 // intervals as long as all the ons match and all the offs match.
351 // Additionally, they do not necessarily need to be integers.
352 // We cannot allow arbitrary intervals since we want the returned points
353 // to be uniformly sized.
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000354 if (fCount != 2 ||
robertphillips@google.com6d875572012-12-17 18:56:29 +0000355 !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) ||
356 !SkScalarIsInt(fIntervals[0]) ||
357 !SkScalarIsInt(fIntervals[1])) {
robertphillips@google.com629ab542012-11-28 17:18:11 +0000358 return false;
359 }
360
robertphillips@google.com629ab542012-11-28 17:18:11 +0000361 SkPoint pts[2];
362
robertphillips@google.com6d875572012-12-17 18:56:29 +0000363 if (!src.isLine(pts)) {
robertphillips@google.com629ab542012-11-28 17:18:11 +0000364 return false;
365 }
366
robertphillips@google.com6d875572012-12-17 18:56:29 +0000367 // TODO: this test could be eased up to allow circles
robertphillips@google.com629ab542012-11-28 17:18:11 +0000368 if (SkPaint::kButt_Cap != rec.getCap()) {
369 return false;
370 }
371
robertphillips@google.com6d875572012-12-17 18:56:29 +0000372 // TODO: this test could be eased up for circles. Rotations could be allowed.
robertphillips@google.com629ab542012-11-28 17:18:11 +0000373 if (!matrix.rectStaysRect()) {
374 return false;
375 }
376
robertphillips@google.com6d875572012-12-17 18:56:29 +0000377 SkScalar length = SkPoint::Distance(pts[1], pts[0]);
robertphillips@google.com629ab542012-11-28 17:18:11 +0000378
robertphillips@google.com6d875572012-12-17 18:56:29 +0000379 SkVector tangent = pts[1] - pts[0];
380 if (tangent.isZero()) {
381 return false;
382 }
383
384 tangent.scale(SkScalarInvert(length));
385
386 // TODO: make this test for horizontal & vertical lines more robust
387 bool isXAxis = true;
388 if (SK_Scalar1 == tangent.fX || -SK_Scalar1 == tangent.fX) {
389 results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth()));
390 } else if (SK_Scalar1 == tangent.fY || -SK_Scalar1 == tangent.fY) {
391 results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0]));
392 isXAxis = false;
393 } else if (SkPaint::kRound_Cap != rec.getCap()) {
394 // Angled lines don't have axis-aligned boxes.
robertphillips@google.com629ab542012-11-28 17:18:11 +0000395 return false;
396 }
397
398 if (NULL != results) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000399 results->fFlags = 0;
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000400 SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength);
robertphillips@google.com629ab542012-11-28 17:18:11 +0000401
robertphillips@google.com6d875572012-12-17 18:56:29 +0000402 if (SkPaint::kRound_Cap == rec.getCap()) {
403 results->fFlags |= PointData::kCircles_PointFlag;
robertphillips@google.com629ab542012-11-28 17:18:11 +0000404 }
405
robertphillips@google.com6d875572012-12-17 18:56:29 +0000406 results->fNumPoints = 0;
407 SkScalar len2 = length;
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000408 if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
409 SkASSERT(len2 >= clampedInitialDashLength);
robertphillips@google.com6d875572012-12-17 18:56:29 +0000410 if (0 == fInitialDashIndex) {
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000411 if (clampedInitialDashLength > 0) {
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000412 if (clampedInitialDashLength >= fIntervals[0]) {
robertphillips@google.com6d875572012-12-17 18:56:29 +0000413 ++results->fNumPoints; // partial first dash
414 }
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000415 len2 -= clampedInitialDashLength;
robertphillips@google.com6d875572012-12-17 18:56:29 +0000416 }
417 len2 -= fIntervals[1]; // also skip first space
418 if (len2 < 0) {
419 len2 = 0;
420 }
421 } else {
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000422 len2 -= clampedInitialDashLength; // skip initial partial empty
robertphillips@google.com6d875572012-12-17 18:56:29 +0000423 }
424 }
425 int numMidPoints = SkScalarFloorToInt(SkScalarDiv(len2, fIntervalLength));
426 results->fNumPoints += numMidPoints;
427 len2 -= numMidPoints * fIntervalLength;
428 bool partialLast = false;
429 if (len2 > 0) {
430 if (len2 < fIntervals[0]) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000431 partialLast = true;
robertphillips@google.com6d875572012-12-17 18:56:29 +0000432 } else {
433 ++numMidPoints;
434 ++results->fNumPoints;
435 }
436 }
robertphillips@google.com629ab542012-11-28 17:18:11 +0000437
robertphillips@google.com935ad022012-12-05 19:07:21 +0000438 results->fPoints = new SkPoint[results->fNumPoints];
robertphillips@google.com629ab542012-11-28 17:18:11 +0000439
robertphillips@google.com6d875572012-12-17 18:56:29 +0000440 SkScalar distance = 0;
441 int curPt = 0;
robertphillips@google.com629ab542012-11-28 17:18:11 +0000442
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000443 if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
444 SkASSERT(clampedInitialDashLength <= length);
robertphillips@google.com629ab542012-11-28 17:18:11 +0000445
robertphillips@google.com6d875572012-12-17 18:56:29 +0000446 if (0 == fInitialDashIndex) {
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000447 if (clampedInitialDashLength > 0) {
robertphillips@google.com6d875572012-12-17 18:56:29 +0000448 // partial first block
449 SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000450 SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, SkScalarHalf(clampedInitialDashLength));
451 SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, SkScalarHalf(clampedInitialDashLength));
robertphillips@google.com6d875572012-12-17 18:56:29 +0000452 SkScalar halfWidth, halfHeight;
453 if (isXAxis) {
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000454 halfWidth = SkScalarHalf(clampedInitialDashLength);
robertphillips@google.com6d875572012-12-17 18:56:29 +0000455 halfHeight = SkScalarHalf(rec.getWidth());
456 } else {
457 halfWidth = SkScalarHalf(rec.getWidth());
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000458 halfHeight = SkScalarHalf(clampedInitialDashLength);
robertphillips@google.com6d875572012-12-17 18:56:29 +0000459 }
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000460 if (clampedInitialDashLength < fIntervals[0]) {
robertphillips@google.com6d875572012-12-17 18:56:29 +0000461 // This one will not be like the others
462 results->fFirst.addRect(x - halfWidth, y - halfHeight,
463 x + halfWidth, y + halfHeight);
464 } else {
465 SkASSERT(curPt < results->fNumPoints);
466 results->fPoints[curPt].set(x, y);
467 ++curPt;
468 }
469
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000470 distance += clampedInitialDashLength;
robertphillips@google.com6d875572012-12-17 18:56:29 +0000471 }
472
473 distance += fIntervals[1]; // skip over the next blank block too
474 } else {
robertphillips@google.com5c4d5582013-01-15 12:53:31 +0000475 distance += clampedInitialDashLength;
robertphillips@google.com629ab542012-11-28 17:18:11 +0000476 }
robertphillips@google.com629ab542012-11-28 17:18:11 +0000477 }
robertphillips@google.com935ad022012-12-05 19:07:21 +0000478
robertphillips@google.com6d875572012-12-17 18:56:29 +0000479 if (0 != numMidPoints) {
480 distance += SkScalarHalf(fIntervals[0]);
481
482 for (int i = 0; i < numMidPoints; ++i) {
483 SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance);
484 SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance);
485
486 SkASSERT(curPt < results->fNumPoints);
487 results->fPoints[curPt].set(x, y);
488 ++curPt;
489
490 distance += fIntervalLength;
491 }
492
493 distance -= SkScalarHalf(fIntervals[0]);
494 }
495
496 if (partialLast) {
497 // partial final block
498 SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
499 SkScalar temp = length - distance;
500 SkASSERT(temp < fIntervals[0]);
501 SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance + SkScalarHalf(temp));
502 SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance + SkScalarHalf(temp));
503 SkScalar halfWidth, halfHeight;
504 if (isXAxis) {
505 halfWidth = SkScalarHalf(temp);
506 halfHeight = SkScalarHalf(rec.getWidth());
507 } else {
508 halfWidth = SkScalarHalf(rec.getWidth());
509 halfHeight = SkScalarHalf(temp);
510 }
511 results->fLast.addRect(x - halfWidth, y - halfHeight,
512 x + halfWidth, y + halfHeight);
513 }
514
515 SkASSERT(curPt == results->fNumPoints);
robertphillips@google.com629ab542012-11-28 17:18:11 +0000516 }
517
518 return true;
519}
520
commit-bot@chromium.orgaec14382014-04-22 15:21:18 +0000521SkPathEffect::DashType SkDashPathEffect::asADash(DashInfo* info) const {
522 if (info) {
523 if (info->fCount >= fCount && NULL != info->fIntervals) {
524 memcpy(info->fIntervals, fIntervals, fCount * sizeof(SkScalar));
525 }
526 info->fCount = fCount;
527 info->fPhase = fPhase;
528 }
529 return kDash_DashType;
530}
531
robertphillips@google.comc2eae472013-10-21 12:26:10 +0000532SkFlattenable::Factory SkDashPathEffect::getFactory() const {
commit-bot@chromium.org7fc22282014-03-07 14:43:00 +0000533 return CreateProc;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000534}
535
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +0000536void SkDashPathEffect::flatten(SkWriteBuffer& buffer) const {
djsollen@google.com54924242012-03-29 15:18:04 +0000537 this->INHERITED::flatten(buffer);
commit-bot@chromium.orgaec14382014-04-22 15:21:18 +0000538 buffer.writeScalar(fPhase);
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000539 buffer.writeScalarArray(fIntervals, fCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000540}
541
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +0000542SkFlattenable* SkDashPathEffect::CreateProc(SkReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000543 return SkNEW_ARGS(SkDashPathEffect, (buffer));
544}
545
commit-bot@chromium.org8b0e8ac2014-01-30 18:58:24 +0000546SkDashPathEffect::SkDashPathEffect(SkReadBuffer& buffer) : INHERITED(buffer) {
commit-bot@chromium.orgaec14382014-04-22 15:21:18 +0000547 bool useOldPic = buffer.pictureVersion() < 25 && 0 != buffer.pictureVersion();
548 if (useOldPic) {
549 fInitialDashIndex = buffer.readInt();
550 fInitialDashLength = buffer.readScalar();
551 fIntervalLength = buffer.readScalar();
552 buffer.readBool(); // Dummy for old ScalarToFit field
553 } else {
554 fPhase = buffer.readScalar();
555 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000556
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000557 fCount = buffer.getArrayCount();
commit-bot@chromium.orgef74fa12013-12-17 20:49:46 +0000558 size_t allocSize = sizeof(SkScalar) * fCount;
559 if (buffer.validateAvailable(allocSize)) {
560 fIntervals = (SkScalar*)sk_malloc_throw(allocSize);
561 buffer.readScalarArray(fIntervals, fCount);
562 } else {
563 fIntervals = NULL;
564 }
commit-bot@chromium.orgaec14382014-04-22 15:21:18 +0000565
566 if (useOldPic) {
567 fPhase = 0;
568 for (int i = 0; i < fInitialDashIndex; ++i) {
569 fPhase += fIntervals[i];
570 }
571 fPhase += fInitialDashLength;
572 } else {
573 this->setInternalMembers(fPhase);
574 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000575}