blob: e2d3648ac18c2b5e06dfde84f4e062c2f0480d72 [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"
djsollen@google.comc73dd5c2012-08-07 15:54:32 +000011#include "SkFlattenableBuffers.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkPathMeasure.h"
13
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000014static inline int is_even(int x) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000015 return (~x) << 31;
16}
17
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000018static SkScalar FindFirstInterval(const SkScalar intervals[], SkScalar phase,
reed@google.comd9ee3482012-08-06 14:58:35 +000019 int32_t* index, int count) {
20 for (int i = 0; i < count; ++i) {
21 if (phase > intervals[i]) {
22 phase -= intervals[i];
23 } else {
24 *index = i;
25 return intervals[i] - phase;
26 }
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000027 }
reed@google.comd9ee3482012-08-06 14:58:35 +000028 // If we get here, phase "appears" to be larger than our length. This
29 // shouldn't happen with perfect precision, but we can accumulate errors
30 // during the initial length computation (rounding can make our sum be too
31 // big or too small. In that event, we just have to eat the error here.
32 *index = 0;
33 return intervals[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +000034}
35
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000036SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count,
37 SkScalar phase, bool scaleToFit)
38 : fScaleToFit(scaleToFit) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000039 SkASSERT(intervals);
40 SkASSERT(count > 1 && SkAlign2(count) == count);
41
42 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count);
43 fCount = count;
44
45 SkScalar len = 0;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000046 for (int i = 0; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000047 SkASSERT(intervals[i] >= 0);
48 fIntervals[i] = intervals[i];
49 len += intervals[i];
50 }
51 fIntervalLength = len;
52
epoger@google.com20bf4ca2012-04-27 13:34:52 +000053 // watch out for values that might make us go out of bounds
54 if ((len > 0) && SkScalarIsFinite(phase) && SkScalarIsFinite(len)) {
55
56 // Adjust phase to be between 0 and len, "flipping" phase if negative.
57 // e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000058 if (phase < 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000059 phase = -phase;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000060 if (phase > len) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000061 phase = SkScalarMod(phase, len);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000062 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000063 phase = len - phase;
epoger@google.com20bf4ca2012-04-27 13:34:52 +000064
65 // Due to finite precision, it's possible that phase == len,
66 // even after the subtract (if len >>> phase), so fix that here.
67 // This fixes http://crbug.com/124652 .
reed@google.com1df888b2012-04-24 22:47:21 +000068 SkASSERT(phase <= len);
69 if (phase == len) {
70 phase = 0;
71 }
epoger@google.com20bf4ca2012-04-27 13:34:52 +000072 } else if (phase >= len) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000073 phase = SkScalarMod(phase, len);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000074 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000075 SkASSERT(phase >= 0 && phase < len);
epoger@google.com20bf4ca2012-04-27 13:34:52 +000076
reed@google.comd9ee3482012-08-06 14:58:35 +000077 fInitialDashLength = FindFirstInterval(intervals, phase,
78 &fInitialDashIndex, count);
reed@android.com8a1c16f2008-12-17 15:59:43 +000079
80 SkASSERT(fInitialDashLength >= 0);
81 SkASSERT(fInitialDashIndex >= 0 && fInitialDashIndex < fCount);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000082 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +000083 fInitialDashLength = -1; // signal bad dash intervals
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000084 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000085}
86
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000087SkDashPathEffect::~SkDashPathEffect() {
reed@android.com8a1c16f2008-12-17 15:59:43 +000088 sk_free(fIntervals);
89}
90
reed@google.com3ec68f02012-05-29 20:48:50 +000091class SpecialLineRec {
92public:
93 bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec,
94 SkScalar pathLength,
95 int intervalCount, SkScalar intervalLength) {
96 if (rec->isHairlineStyle() || !src.isLine(fPts)) {
97 return false;
98 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +000099
reed@google.com3ec68f02012-05-29 20:48:50 +0000100 // can relax this in the future, if we handle square and round caps
101 if (SkPaint::kButt_Cap != rec->getCap()) {
102 return false;
103 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000104
reed@google.com3ec68f02012-05-29 20:48:50 +0000105 fTangent = fPts[1] - fPts[0];
106 if (fTangent.isZero()) {
107 return false;
108 }
109
110 fPathLength = pathLength;
111 fTangent.scale(SkScalarInvert(pathLength));
112 fTangent.rotateCCW(&fNormal);
113 fNormal.scale(SkScalarHalf(rec->getWidth()));
114
115 // now estimate how many quads will be added to the path
116 // resulting segments = pathLen * intervalCount / intervalLen
117 // resulting points = 4 * segments
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000118
reed@google.com3ec68f02012-05-29 20:48:50 +0000119 SkScalar ptCount = SkScalarMulDiv(pathLength,
120 SkIntToScalar(intervalCount),
121 intervalLength);
122 int n = SkScalarCeilToInt(ptCount) << 2;
123 dst->incReserve(n);
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000124
reed@google.com3ec68f02012-05-29 20:48:50 +0000125 // we will take care of the stroking
126 rec->setFillStyle();
127 return true;
128 }
129
130 void addSegment(SkScalar d0, SkScalar d1, SkPath* path) const {
131 SkASSERT(d0 < fPathLength);
132 // clamp the segment to our length
133 if (d1 > fPathLength) {
134 d1 = fPathLength;
135 }
136
137 SkScalar x0 = fPts[0].fX + SkScalarMul(fTangent.fX, d0);
138 SkScalar x1 = fPts[0].fX + SkScalarMul(fTangent.fX, d1);
139 SkScalar y0 = fPts[0].fY + SkScalarMul(fTangent.fY, d0);
140 SkScalar y1 = fPts[0].fY + SkScalarMul(fTangent.fY, d1);
141
142 SkPoint pts[4];
143 pts[0].set(x0 + fNormal.fX, y0 + fNormal.fY); // moveTo
144 pts[1].set(x1 + fNormal.fX, y1 + fNormal.fY); // lineTo
145 pts[2].set(x1 - fNormal.fX, y1 - fNormal.fY); // lineTo
146 pts[3].set(x0 - fNormal.fX, y0 - fNormal.fY); // lineTo
147
148 path->addPoly(pts, SK_ARRAY_COUNT(pts), false);
149 }
150
151private:
152 SkPoint fPts[2];
153 SkVector fTangent;
154 SkVector fNormal;
155 SkScalar fPathLength;
156};
157
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000158bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
reed@google.comfd4be262012-05-25 01:04:12 +0000159 SkStrokeRec* rec) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000160 // we do nothing if the src wants to be filled, or if our dashlength is 0
reed@google.comfd4be262012-05-25 01:04:12 +0000161 if (rec->isFillStyle() || fInitialDashLength < 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 return false;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000163 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000164
165 SkPathMeasure meas(src, false);
166 const SkScalar* intervals = fIntervals;
167
reed@google.com3ec68f02012-05-29 20:48:50 +0000168 SpecialLineRec lineRec;
169 const bool specialLine = lineRec.init(src, dst, rec, meas.getLength(),
170 fCount >> 1, fIntervalLength);
171
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 do {
173 bool skipFirstSegment = meas.isClosed();
174 bool addedSegment = false;
175 SkScalar length = meas.getLength();
176 int index = fInitialDashIndex;
177 SkScalar scale = SK_Scalar1;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000178
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000179 if (fScaleToFit) {
180 if (fIntervalLength >= length) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000181 scale = SkScalarDiv(length, fIntervalLength);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000182 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000183 SkScalar div = SkScalarDiv(length, fIntervalLength);
184 int n = SkScalarFloor(div);
185 scale = SkScalarDiv(length, n * fIntervalLength);
186 }
187 }
188
189 SkScalar distance = 0;
190 SkScalar dlen = SkScalarMul(fInitialDashLength, scale);
191
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000192 while (distance < length) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 SkASSERT(dlen >= 0);
194 addedSegment = false;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000195 if (is_even(index) && dlen > 0 && !skipFirstSegment) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000196 addedSegment = true;
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000197
reed@google.com3ec68f02012-05-29 20:48:50 +0000198 if (specialLine) {
199 lineRec.addSegment(distance, distance + dlen, dst);
200 } else {
201 meas.getSegment(distance, distance + dlen, dst, true);
202 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000203 }
204 distance += dlen;
205
206 // clear this so we only respect it the first time around
207 skipFirstSegment = false;
208
209 // wrap around our intervals array if necessary
210 index += 1;
211 SkASSERT(index <= fCount);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000212 if (index == fCount) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000213 index = 0;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000214 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000215
216 // fetch our next dlen
217 dlen = SkScalarMul(intervals[index], scale);
218 }
219
220 // 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 +0000221 if (meas.isClosed() && is_even(fInitialDashIndex) &&
222 fInitialDashLength > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000223 meas.getSegment(0, SkScalarMul(fInitialDashLength, scale), dst, !addedSegment);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000224 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000225 } while (meas.nextContour());
reed@google.com3ec68f02012-05-29 20:48:50 +0000226
reed@android.com8a1c16f2008-12-17 15:59:43 +0000227 return true;
228}
229
robertphillips@google.com629ab542012-11-28 17:18:11 +0000230// Currently asPoints is more restrictive then it needs to be. In the future
231// we need to:
232// allow kRound_Cap capping (could allow rotations in the matrix with this)
233// loosen restriction on initial dash length
234// allow cases where (stroke width == interval[0]) and return size
235// allow partial first and last pixels
236bool SkDashPathEffect::asPoints(PointData* results,
237 const SkPath& src,
238 const SkStrokeRec& rec,
239 const SkMatrix& matrix) const {
240 if (rec.isFillStyle() || fInitialDashLength < 0 || SK_Scalar1 != rec.getWidth()) {
241 return false;
242 }
243
244 if (fIntervalLength != 2 || SK_Scalar1 != fIntervals[0] || SK_Scalar1 != fIntervals[1]) {
245 return false;
246 }
247
248 if (fScaleToFit || 0 != fInitialDashLength) {
249 return false;
250 }
251
252 SkPoint pts[2];
253
254 if (rec.isHairlineStyle() || !src.isLine(pts)) {
255 return false;
256 }
257
258 if (SkPaint::kButt_Cap != rec.getCap()) {
259 return false;
260 }
261
262 if (!matrix.rectStaysRect()) {
263 return false;
264 }
265
266 SkPathMeasure meas(src, false);
267 SkScalar length = meas.getLength();
268
269 if (!SkScalarIsInt(length)) {
270 return false;
271 }
272
273 if (NULL != results) {
274 results->fFlags = 0; // don't use clip rect & draw rects
275 results->fSize.set(SK_Scalar1, SK_Scalar1);
276
277 SkVector tangent = pts[1] - pts[0];
278 if (tangent.isZero()) {
279 return false;
280 }
281
282 tangent.scale(SkScalarInvert(length));
283
robertphillips@google.com935ad022012-12-05 19:07:21 +0000284 SkScalar ptCount = SkScalarDiv(length-1, SkIntToScalar(2));
285 results->fNumPoints = SkScalarCeilToInt(ptCount);
286 results->fPoints = new SkPoint[results->fNumPoints];
robertphillips@google.com629ab542012-11-28 17:18:11 +0000287
288 // +1 b.c. fInitialDashLength is zero so the initial segment will be skipped
289 int index = fInitialDashIndex+1;
robertphillips@google.com935ad022012-12-05 19:07:21 +0000290 int iCurPt = 0;
robertphillips@google.com629ab542012-11-28 17:18:11 +0000291
292 for (SkScalar distance = SK_ScalarHalf; distance < length; distance += SK_Scalar1) {
293 SkASSERT(index <= fCount);
294
295 if (0 == index) {
296 SkScalar x0 = pts[0].fX + SkScalarMul(tangent.fX, distance);
297 SkScalar y0 = pts[0].fY + SkScalarMul(tangent.fY, distance);
robertphillips@google.com935ad022012-12-05 19:07:21 +0000298 SkASSERT(iCurPt < results->fNumPoints);
299 results->fPoints[iCurPt].set(x0, y0);
300 ++iCurPt;
robertphillips@google.com629ab542012-11-28 17:18:11 +0000301 }
302
303 index ^= 1; // 0 -> 1 -> 0 ...
304 }
robertphillips@google.com935ad022012-12-05 19:07:21 +0000305
306 SkASSERT(iCurPt == results->fNumPoints);
robertphillips@google.com629ab542012-11-28 17:18:11 +0000307 }
308
309 return true;
310}
311
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000312SkFlattenable::Factory SkDashPathEffect::getFactory() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000313 return fInitialDashLength < 0 ? NULL : CreateProc;
314}
315
djsollen@google.com54924242012-03-29 15:18:04 +0000316void SkDashPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000317 SkASSERT(fInitialDashLength >= 0);
318
djsollen@google.com54924242012-03-29 15:18:04 +0000319 this->INHERITED::flatten(buffer);
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000320 buffer.writeInt(fInitialDashIndex);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000321 buffer.writeScalar(fInitialDashLength);
322 buffer.writeScalar(fIntervalLength);
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000323 buffer.writeBool(fScaleToFit);
324 buffer.writeScalarArray(fIntervals, fCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000325}
326
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000327SkFlattenable* SkDashPathEffect::CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000328 return SkNEW_ARGS(SkDashPathEffect, (buffer));
329}
330
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000331SkDashPathEffect::SkDashPathEffect(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
332 fInitialDashIndex = buffer.readInt();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000333 fInitialDashLength = buffer.readScalar();
334 fIntervalLength = buffer.readScalar();
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000335 fScaleToFit = buffer.readBool();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000336
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000337 fCount = buffer.getArrayCount();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000338 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * fCount);
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000339 buffer.readScalarArray(fIntervals);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000340}