blob: 537601fa87ce1b006ee4c627b80cce75ed69f5a0 [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"
11#include "SkBuffer.h"
djsollen@google.comc73dd5c2012-08-07 15:54:32 +000012#include "SkFlattenableBuffers.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
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000037SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count,
38 SkScalar phase, bool scaleToFit)
39 : fScaleToFit(scaleToFit) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000040 SkASSERT(intervals);
41 SkASSERT(count > 1 && SkAlign2(count) == count);
42
43 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count);
44 fCount = count;
45
46 SkScalar len = 0;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000047 for (int i = 0; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000048 SkASSERT(intervals[i] >= 0);
49 fIntervals[i] = intervals[i];
50 len += intervals[i];
51 }
52 fIntervalLength = len;
53
epoger@google.com20bf4ca2012-04-27 13:34:52 +000054 // watch out for values that might make us go out of bounds
55 if ((len > 0) && SkScalarIsFinite(phase) && SkScalarIsFinite(len)) {
56
57 // Adjust phase to be between 0 and len, "flipping" phase if negative.
58 // e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000059 if (phase < 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000060 phase = -phase;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000061 if (phase > len) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000062 phase = SkScalarMod(phase, len);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000063 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000064 phase = len - phase;
epoger@google.com20bf4ca2012-04-27 13:34:52 +000065
66 // Due to finite precision, it's possible that phase == len,
67 // even after the subtract (if len >>> phase), so fix that here.
68 // This fixes http://crbug.com/124652 .
reed@google.com1df888b2012-04-24 22:47:21 +000069 SkASSERT(phase <= len);
70 if (phase == len) {
71 phase = 0;
72 }
epoger@google.com20bf4ca2012-04-27 13:34:52 +000073 } else if (phase >= len) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000074 phase = SkScalarMod(phase, len);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000075 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000076 SkASSERT(phase >= 0 && phase < len);
epoger@google.com20bf4ca2012-04-27 13:34:52 +000077
reed@google.comd9ee3482012-08-06 14:58:35 +000078 fInitialDashLength = FindFirstInterval(intervals, phase,
79 &fInitialDashIndex, count);
reed@android.com8a1c16f2008-12-17 15:59:43 +000080
81 SkASSERT(fInitialDashLength >= 0);
82 SkASSERT(fInitialDashIndex >= 0 && fInitialDashIndex < fCount);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000083 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +000084 fInitialDashLength = -1; // signal bad dash intervals
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000085 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000086}
87
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000088SkDashPathEffect::~SkDashPathEffect() {
reed@android.com8a1c16f2008-12-17 15:59:43 +000089 sk_free(fIntervals);
90}
91
reed@google.com3ec68f02012-05-29 20:48:50 +000092class SpecialLineRec {
93public:
94 bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec,
95 SkScalar pathLength,
96 int intervalCount, SkScalar intervalLength) {
97 if (rec->isHairlineStyle() || !src.isLine(fPts)) {
98 return false;
99 }
100
101 // can relax this in the future, if we handle square and round caps
102 if (SkPaint::kButt_Cap != rec->getCap()) {
103 return false;
104 }
105
106 fTangent = fPts[1] - fPts[0];
107 if (fTangent.isZero()) {
108 return false;
109 }
110
111 fPathLength = pathLength;
112 fTangent.scale(SkScalarInvert(pathLength));
113 fTangent.rotateCCW(&fNormal);
114 fNormal.scale(SkScalarHalf(rec->getWidth()));
115
116 // now estimate how many quads will be added to the path
117 // resulting segments = pathLen * intervalCount / intervalLen
118 // resulting points = 4 * segments
119
120 SkScalar ptCount = SkScalarMulDiv(pathLength,
121 SkIntToScalar(intervalCount),
122 intervalLength);
123 int n = SkScalarCeilToInt(ptCount) << 2;
124 dst->incReserve(n);
125
126 // we will take care of the stroking
127 rec->setFillStyle();
128 return true;
129 }
130
131 void addSegment(SkScalar d0, SkScalar d1, SkPath* path) const {
132 SkASSERT(d0 < fPathLength);
133 // clamp the segment to our length
134 if (d1 > fPathLength) {
135 d1 = fPathLength;
136 }
137
138 SkScalar x0 = fPts[0].fX + SkScalarMul(fTangent.fX, d0);
139 SkScalar x1 = fPts[0].fX + SkScalarMul(fTangent.fX, d1);
140 SkScalar y0 = fPts[0].fY + SkScalarMul(fTangent.fY, d0);
141 SkScalar y1 = fPts[0].fY + SkScalarMul(fTangent.fY, d1);
142
143 SkPoint pts[4];
144 pts[0].set(x0 + fNormal.fX, y0 + fNormal.fY); // moveTo
145 pts[1].set(x1 + fNormal.fX, y1 + fNormal.fY); // lineTo
146 pts[2].set(x1 - fNormal.fX, y1 - fNormal.fY); // lineTo
147 pts[3].set(x0 - fNormal.fX, y0 - fNormal.fY); // lineTo
148
149 path->addPoly(pts, SK_ARRAY_COUNT(pts), false);
150 }
151
152private:
153 SkPoint fPts[2];
154 SkVector fTangent;
155 SkVector fNormal;
156 SkScalar fPathLength;
157};
158
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000159bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
reed@google.comfd4be262012-05-25 01:04:12 +0000160 SkStrokeRec* rec) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000161 // we do nothing if the src wants to be filled, or if our dashlength is 0
reed@google.comfd4be262012-05-25 01:04:12 +0000162 if (rec->isFillStyle() || fInitialDashLength < 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163 return false;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000164 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165
166 SkPathMeasure meas(src, false);
167 const SkScalar* intervals = fIntervals;
168
reed@google.com3ec68f02012-05-29 20:48:50 +0000169 SpecialLineRec lineRec;
170 const bool specialLine = lineRec.init(src, dst, rec, meas.getLength(),
171 fCount >> 1, fIntervalLength);
172
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 do {
174 bool skipFirstSegment = meas.isClosed();
175 bool addedSegment = false;
176 SkScalar length = meas.getLength();
177 int index = fInitialDashIndex;
178 SkScalar scale = SK_Scalar1;
reed@google.com3ec68f02012-05-29 20:48:50 +0000179
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000180 if (fScaleToFit) {
181 if (fIntervalLength >= length) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182 scale = SkScalarDiv(length, fIntervalLength);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000183 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000184 SkScalar div = SkScalarDiv(length, fIntervalLength);
185 int n = SkScalarFloor(div);
186 scale = SkScalarDiv(length, n * fIntervalLength);
187 }
188 }
189
190 SkScalar distance = 0;
191 SkScalar dlen = SkScalarMul(fInitialDashLength, scale);
192
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000193 while (distance < length) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194 SkASSERT(dlen >= 0);
195 addedSegment = false;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000196 if (is_even(index) && dlen > 0 && !skipFirstSegment) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000197 addedSegment = true;
reed@google.com3ec68f02012-05-29 20:48:50 +0000198
199 if (specialLine) {
200 lineRec.addSegment(distance, distance + dlen, dst);
201 } else {
202 meas.getSegment(distance, distance + dlen, dst, true);
203 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000204 }
205 distance += dlen;
206
207 // clear this so we only respect it the first time around
208 skipFirstSegment = false;
209
210 // wrap around our intervals array if necessary
211 index += 1;
212 SkASSERT(index <= fCount);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000213 if (index == fCount) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000214 index = 0;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000215 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000216
217 // fetch our next dlen
218 dlen = SkScalarMul(intervals[index], scale);
219 }
220
221 // 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 +0000222 if (meas.isClosed() && is_even(fInitialDashIndex) &&
223 fInitialDashLength > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000224 meas.getSegment(0, SkScalarMul(fInitialDashLength, scale), dst, !addedSegment);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000225 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000226 } while (meas.nextContour());
reed@google.com3ec68f02012-05-29 20:48:50 +0000227
reed@android.com8a1c16f2008-12-17 15:59:43 +0000228 return true;
229}
230
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000231SkFlattenable::Factory SkDashPathEffect::getFactory() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000232 return fInitialDashLength < 0 ? NULL : CreateProc;
233}
234
djsollen@google.com54924242012-03-29 15:18:04 +0000235void SkDashPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000236 SkASSERT(fInitialDashLength >= 0);
237
djsollen@google.com54924242012-03-29 15:18:04 +0000238 this->INHERITED::flatten(buffer);
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000239 buffer.writeInt(fInitialDashIndex);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 buffer.writeScalar(fInitialDashLength);
241 buffer.writeScalar(fIntervalLength);
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000242 buffer.writeBool(fScaleToFit);
243 buffer.writeScalarArray(fIntervals, fCount);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244}
245
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000246SkFlattenable* SkDashPathEffect::CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000247 return SkNEW_ARGS(SkDashPathEffect, (buffer));
248}
249
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000250SkDashPathEffect::SkDashPathEffect(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {
251 fInitialDashIndex = buffer.readInt();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000252 fInitialDashLength = buffer.readScalar();
253 fIntervalLength = buffer.readScalar();
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000254 fScaleToFit = buffer.readBool();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000255
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000256 fCount = buffer.getArrayCount();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000257 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * fCount);
djsollen@google.comc73dd5c2012-08-07 15:54:32 +0000258 buffer.readScalarArray(fIntervals);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000259}
260
reed@google.com6bac9472011-06-21 19:24:00 +0000261///////////////////////////////////////////////////////////////////////////////
262
caryclark@google.comd26147a2011-12-15 14:16:43 +0000263SK_DEFINE_FLATTENABLE_REGISTRAR(SkDashPathEffect)