blob: 3299c635812fd667fea4981f3f4a65f863db1612 [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
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000230SkFlattenable::Factory SkDashPathEffect::getFactory() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000231 return fInitialDashLength < 0 ? NULL : CreateProc;
232}
233
djsollen@google.com54924242012-03-29 15:18:04 +0000234void SkDashPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000235 SkASSERT(fInitialDashLength >= 0);
236
djsollen@google.com54924242012-03-29 15:18:04 +0000237 this->INHERITED::flatten(buffer);
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000238 buffer.writeInt(fInitialDashIndex);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000239 buffer.writeScalar(fInitialDashLength);
240 buffer.writeScalar(fIntervalLength);
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000241 buffer.writeBool(fScaleToFit);
242 buffer.writeScalarArray(fIntervals, fCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243}
244
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000245SkFlattenable* SkDashPathEffect::CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000246 return SkNEW_ARGS(SkDashPathEffect, (buffer));
247}
248
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000249SkDashPathEffect::SkDashPathEffect(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
250 fInitialDashIndex = buffer.readInt();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000251 fInitialDashLength = buffer.readScalar();
252 fIntervalLength = buffer.readScalar();
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000253 fScaleToFit = buffer.readBool();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000254
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000255 fCount = buffer.getArrayCount();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000256 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * fCount);
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000257 buffer.readScalarArray(fIntervals);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258}
259
reed@google.com6bac9472011-06-21 19:24:00 +0000260///////////////////////////////////////////////////////////////////////////////
261
caryclark@google.comd26147a2011-12-15 14:16:43 +0000262SK_DEFINE_FLATTENABLE_REGISTRAR(SkDashPathEffect)