blob: e6f6afaf077d8adae7d3eecd70a925adb06f9023 [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"
12#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,
19 int32_t* index) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000020 int i;
21
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000022 for (i = 0; phase > intervals[i]; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000023 phase -= intervals[i];
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000024 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000025 *index = i;
26 return intervals[i] - phase;
27}
28
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000029SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count,
30 SkScalar phase, bool scaleToFit)
31 : fScaleToFit(scaleToFit) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000032 SkASSERT(intervals);
33 SkASSERT(count > 1 && SkAlign2(count) == count);
34
35 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count);
36 fCount = count;
37
38 SkScalar len = 0;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000039 for (int i = 0; i < count; i++) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000040 SkASSERT(intervals[i] >= 0);
41 fIntervals[i] = intervals[i];
42 len += intervals[i];
43 }
44 fIntervalLength = len;
45
epoger@google.com20bf4ca2012-04-27 13:34:52 +000046 // watch out for values that might make us go out of bounds
47 if ((len > 0) && SkScalarIsFinite(phase) && SkScalarIsFinite(len)) {
48
49 // Adjust phase to be between 0 and len, "flipping" phase if negative.
50 // e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000051 if (phase < 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000052 phase = -phase;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000053 if (phase > len) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000054 phase = SkScalarMod(phase, len);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000055 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000056 phase = len - phase;
epoger@google.com20bf4ca2012-04-27 13:34:52 +000057
58 // Due to finite precision, it's possible that phase == len,
59 // even after the subtract (if len >>> phase), so fix that here.
60 // This fixes http://crbug.com/124652 .
reed@google.com1df888b2012-04-24 22:47:21 +000061 SkASSERT(phase <= len);
62 if (phase == len) {
63 phase = 0;
64 }
epoger@google.com20bf4ca2012-04-27 13:34:52 +000065 } else if (phase >= len) {
reed@android.com8a1c16f2008-12-17 15:59:43 +000066 phase = SkScalarMod(phase, len);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000067 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000068 SkASSERT(phase >= 0 && phase < len);
epoger@google.com20bf4ca2012-04-27 13:34:52 +000069
reed@android.com8a1c16f2008-12-17 15:59:43 +000070 fInitialDashLength = FindFirstInterval(intervals, phase, &fInitialDashIndex);
71
72 SkASSERT(fInitialDashLength >= 0);
73 SkASSERT(fInitialDashIndex >= 0 && fInitialDashIndex < fCount);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000074 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +000075 fInitialDashLength = -1; // signal bad dash intervals
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000076 }
reed@android.com8a1c16f2008-12-17 15:59:43 +000077}
78
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +000079SkDashPathEffect::~SkDashPathEffect() {
reed@android.com8a1c16f2008-12-17 15:59:43 +000080 sk_free(fIntervals);
81}
82
reed@google.com3ec68f02012-05-29 20:48:50 +000083class SpecialLineRec {
84public:
85 bool init(const SkPath& src, SkPath* dst, SkStrokeRec* rec,
86 SkScalar pathLength,
87 int intervalCount, SkScalar intervalLength) {
88 if (rec->isHairlineStyle() || !src.isLine(fPts)) {
89 return false;
90 }
91
92 // can relax this in the future, if we handle square and round caps
93 if (SkPaint::kButt_Cap != rec->getCap()) {
94 return false;
95 }
96
97 fTangent = fPts[1] - fPts[0];
98 if (fTangent.isZero()) {
99 return false;
100 }
101
102 fPathLength = pathLength;
103 fTangent.scale(SkScalarInvert(pathLength));
104 fTangent.rotateCCW(&fNormal);
105 fNormal.scale(SkScalarHalf(rec->getWidth()));
106
107 // now estimate how many quads will be added to the path
108 // resulting segments = pathLen * intervalCount / intervalLen
109 // resulting points = 4 * segments
110
111 SkScalar ptCount = SkScalarMulDiv(pathLength,
112 SkIntToScalar(intervalCount),
113 intervalLength);
114 int n = SkScalarCeilToInt(ptCount) << 2;
115 dst->incReserve(n);
116
117 // we will take care of the stroking
118 rec->setFillStyle();
119 return true;
120 }
121
122 void addSegment(SkScalar d0, SkScalar d1, SkPath* path) const {
123 SkASSERT(d0 < fPathLength);
124 // clamp the segment to our length
125 if (d1 > fPathLength) {
126 d1 = fPathLength;
127 }
128
129 SkScalar x0 = fPts[0].fX + SkScalarMul(fTangent.fX, d0);
130 SkScalar x1 = fPts[0].fX + SkScalarMul(fTangent.fX, d1);
131 SkScalar y0 = fPts[0].fY + SkScalarMul(fTangent.fY, d0);
132 SkScalar y1 = fPts[0].fY + SkScalarMul(fTangent.fY, d1);
133
134 SkPoint pts[4];
135 pts[0].set(x0 + fNormal.fX, y0 + fNormal.fY); // moveTo
136 pts[1].set(x1 + fNormal.fX, y1 + fNormal.fY); // lineTo
137 pts[2].set(x1 - fNormal.fX, y1 - fNormal.fY); // lineTo
138 pts[3].set(x0 - fNormal.fX, y0 - fNormal.fY); // lineTo
139
140 path->addPoly(pts, SK_ARRAY_COUNT(pts), false);
141 }
142
143private:
144 SkPoint fPts[2];
145 SkVector fTangent;
146 SkVector fNormal;
147 SkScalar fPathLength;
148};
149
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000150bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
reed@google.comfd4be262012-05-25 01:04:12 +0000151 SkStrokeRec* rec) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152 // we do nothing if the src wants to be filled, or if our dashlength is 0
reed@google.comfd4be262012-05-25 01:04:12 +0000153 if (rec->isFillStyle() || fInitialDashLength < 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154 return false;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000155 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000156
157 SkPathMeasure meas(src, false);
158 const SkScalar* intervals = fIntervals;
159
reed@google.com3ec68f02012-05-29 20:48:50 +0000160 SpecialLineRec lineRec;
161 const bool specialLine = lineRec.init(src, dst, rec, meas.getLength(),
162 fCount >> 1, fIntervalLength);
163
reed@android.com8a1c16f2008-12-17 15:59:43 +0000164 do {
165 bool skipFirstSegment = meas.isClosed();
166 bool addedSegment = false;
167 SkScalar length = meas.getLength();
168 int index = fInitialDashIndex;
169 SkScalar scale = SK_Scalar1;
reed@google.com3ec68f02012-05-29 20:48:50 +0000170
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000171 if (fScaleToFit) {
172 if (fIntervalLength >= length) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 scale = SkScalarDiv(length, fIntervalLength);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000174 } else {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175 SkScalar div = SkScalarDiv(length, fIntervalLength);
176 int n = SkScalarFloor(div);
177 scale = SkScalarDiv(length, n * fIntervalLength);
178 }
179 }
180
181 SkScalar distance = 0;
182 SkScalar dlen = SkScalarMul(fInitialDashLength, scale);
183
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000184 while (distance < length) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185 SkASSERT(dlen >= 0);
186 addedSegment = false;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000187 if (is_even(index) && dlen > 0 && !skipFirstSegment) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000188 addedSegment = true;
reed@google.com3ec68f02012-05-29 20:48:50 +0000189
190 if (specialLine) {
191 lineRec.addSegment(distance, distance + dlen, dst);
192 } else {
193 meas.getSegment(distance, distance + dlen, dst, true);
194 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195 }
196 distance += dlen;
197
198 // clear this so we only respect it the first time around
199 skipFirstSegment = false;
200
201 // wrap around our intervals array if necessary
202 index += 1;
203 SkASSERT(index <= fCount);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000204 if (index == fCount) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000205 index = 0;
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000206 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000207
208 // fetch our next dlen
209 dlen = SkScalarMul(intervals[index], scale);
210 }
211
212 // 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 +0000213 if (meas.isClosed() && is_even(fInitialDashIndex) &&
214 fInitialDashLength > 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000215 meas.getSegment(0, SkScalarMul(fInitialDashLength, scale), dst, !addedSegment);
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000216 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000217 } while (meas.nextContour());
reed@google.com3ec68f02012-05-29 20:48:50 +0000218
reed@android.com8a1c16f2008-12-17 15:59:43 +0000219 return true;
220}
221
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000222SkFlattenable::Factory SkDashPathEffect::getFactory() {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000223 return fInitialDashLength < 0 ? NULL : CreateProc;
224}
225
djsollen@google.com54924242012-03-29 15:18:04 +0000226void SkDashPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000227 SkASSERT(fInitialDashLength >= 0);
228
djsollen@google.com54924242012-03-29 15:18:04 +0000229 this->INHERITED::flatten(buffer);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000230 buffer.write32(fCount);
231 buffer.write32(fInitialDashIndex);
232 buffer.writeScalar(fInitialDashLength);
233 buffer.writeScalar(fIntervalLength);
234 buffer.write32(fScaleToFit);
235 buffer.writeMul4(fIntervals, fCount * sizeof(fIntervals[0]));
236}
237
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000238SkFlattenable* SkDashPathEffect::CreateProc(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000239 return SkNEW_ARGS(SkDashPathEffect, (buffer));
240}
241
mike@reedtribe.org3334c3a2011-04-20 11:39:28 +0000242SkDashPathEffect::SkDashPathEffect(SkFlattenableReadBuffer& buffer) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000243 fCount = buffer.readS32();
244 fInitialDashIndex = buffer.readS32();
245 fInitialDashLength = buffer.readScalar();
246 fIntervalLength = buffer.readScalar();
247 fScaleToFit = (buffer.readS32() != 0);
248
249 fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * fCount);
250 buffer.read(fIntervals, fCount * sizeof(fIntervals[0]));
251}
252
reed@google.com6bac9472011-06-21 19:24:00 +0000253///////////////////////////////////////////////////////////////////////////////
254
caryclark@google.comd26147a2011-12-15 14:16:43 +0000255SK_DEFINE_FLATTENABLE_REGISTRAR(SkDashPathEffect)