SkDashPathEffect.cpp revision 3ec68f047a1f698bec12e1a270fdf4f62aed9cdb
1
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
9
10#include "SkDashPathEffect.h"
11#include "SkBuffer.h"
12#include "SkPathMeasure.h"
13
14static inline int is_even(int x) {
15    return (~x) << 31;
16}
17
18static SkScalar FindFirstInterval(const SkScalar intervals[], SkScalar phase,
19                                  int32_t* index) {
20    int i;
21
22    for (i = 0; phase > intervals[i]; i++) {
23        phase -= intervals[i];
24    }
25    *index = i;
26    return intervals[i] - phase;
27}
28
29SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count,
30                                   SkScalar phase, bool scaleToFit)
31        : fScaleToFit(scaleToFit) {
32    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;
39    for (int i = 0; i < count; i++) {
40        SkASSERT(intervals[i] >= 0);
41        fIntervals[i] = intervals[i];
42        len += intervals[i];
43    }
44    fIntervalLength = len;
45
46    // 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
51        if (phase < 0) {
52            phase = -phase;
53            if (phase > len) {
54                phase = SkScalarMod(phase, len);
55            }
56            phase = len - phase;
57
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 .
61            SkASSERT(phase <= len);
62            if (phase == len) {
63                phase = 0;
64            }
65        } else if (phase >= len) {
66            phase = SkScalarMod(phase, len);
67        }
68        SkASSERT(phase >= 0 && phase < len);
69
70        fInitialDashLength = FindFirstInterval(intervals, phase, &fInitialDashIndex);
71
72        SkASSERT(fInitialDashLength >= 0);
73        SkASSERT(fInitialDashIndex >= 0 && fInitialDashIndex < fCount);
74    } else {
75        fInitialDashLength = -1;    // signal bad dash intervals
76    }
77}
78
79SkDashPathEffect::~SkDashPathEffect() {
80    sk_free(fIntervals);
81}
82
83class 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
150bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
151                                  SkStrokeRec* rec) {
152    // we do nothing if the src wants to be filled, or if our dashlength is 0
153    if (rec->isFillStyle() || fInitialDashLength < 0) {
154        return false;
155    }
156
157    SkPathMeasure   meas(src, false);
158    const SkScalar* intervals = fIntervals;
159
160    SpecialLineRec lineRec;
161    const bool specialLine = lineRec.init(src, dst, rec, meas.getLength(),
162                                          fCount >> 1, fIntervalLength);
163
164    do {
165        bool        skipFirstSegment = meas.isClosed();
166        bool        addedSegment = false;
167        SkScalar    length = meas.getLength();
168        int         index = fInitialDashIndex;
169        SkScalar    scale = SK_Scalar1;
170
171        if (fScaleToFit) {
172            if (fIntervalLength >= length) {
173                scale = SkScalarDiv(length, fIntervalLength);
174            } else {
175                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
184        while (distance < length) {
185            SkASSERT(dlen >= 0);
186            addedSegment = false;
187            if (is_even(index) && dlen > 0 && !skipFirstSegment) {
188                addedSegment = true;
189
190                if (specialLine) {
191                    lineRec.addSegment(distance, distance + dlen, dst);
192                } else {
193                    meas.getSegment(distance, distance + dlen, dst, true);
194                }
195            }
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);
204            if (index == fCount) {
205                index = 0;
206            }
207
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
213        if (meas.isClosed() && is_even(fInitialDashIndex) &&
214                fInitialDashLength > 0) {
215            meas.getSegment(0, SkScalarMul(fInitialDashLength, scale), dst, !addedSegment);
216        }
217    } while (meas.nextContour());
218
219    return true;
220}
221
222SkFlattenable::Factory SkDashPathEffect::getFactory() {
223    return fInitialDashLength < 0 ? NULL : CreateProc;
224}
225
226void SkDashPathEffect::flatten(SkFlattenableWriteBuffer& buffer) const {
227    SkASSERT(fInitialDashLength >= 0);
228
229    this->INHERITED::flatten(buffer);
230    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
238SkFlattenable* SkDashPathEffect::CreateProc(SkFlattenableReadBuffer& buffer) {
239    return SkNEW_ARGS(SkDashPathEffect, (buffer));
240}
241
242SkDashPathEffect::SkDashPathEffect(SkFlattenableReadBuffer& buffer) {
243    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
253///////////////////////////////////////////////////////////////////////////////
254
255SK_DEFINE_FLATTENABLE_REGISTRAR(SkDashPathEffect)
256