SkDashPathEffect.cpp revision 9fa60daad4d5f54c0dbe3dbcc7608a8f6d721187
1/*
2 * Copyright 2006 The Android Open Source Project
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkDashPathEffect.h"
9
10#include "SkDashPathPriv.h"
11#include "SkReadBuffer.h"
12#include "SkWriteBuffer.h"
13
14SkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count, SkScalar phase)
15        : fPhase(0)
16        , fInitialDashLength(0)
17        , fInitialDashIndex(0)
18        , fIntervalLength(0) {
19    SkASSERT(intervals);
20    SkASSERT(count > 1 && SkAlign2(count) == count);
21
22    fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count);
23    fCount = count;
24    for (int i = 0; i < count; i++) {
25        SkASSERT(intervals[i] >= 0);
26        fIntervals[i] = intervals[i];
27    }
28
29    // set the internal data members
30    SkDashPath::CalcDashParameters(phase, fIntervals, fCount,
31            &fInitialDashLength, &fInitialDashIndex, &fIntervalLength, &fPhase);
32}
33
34SkDashPathEffect::~SkDashPathEffect() {
35    sk_free(fIntervals);
36}
37
38bool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
39                              SkStrokeRec* rec, const SkRect* cullRect) const {
40    return SkDashPath::FilterDashPath(dst, src, rec, cullRect, fIntervals, fCount,
41                                      fInitialDashLength, fInitialDashIndex, fIntervalLength);
42}
43
44// Currently asPoints is more restrictive then it needs to be. In the future
45// we need to:
46//      allow kRound_Cap capping (could allow rotations in the matrix with this)
47//      allow paths to be returned
48bool SkDashPathEffect::asPoints(PointData* results,
49                                const SkPath& src,
50                                const SkStrokeRec& rec,
51                                const SkMatrix& matrix,
52                                const SkRect* cullRect) const {
53    // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out
54    if (fInitialDashLength < 0 || 0 >= rec.getWidth()) {
55        return false;
56    }
57
58    // TODO: this next test could be eased up. We could allow any number of
59    // intervals as long as all the ons match and all the offs match.
60    // Additionally, they do not necessarily need to be integers.
61    // We cannot allow arbitrary intervals since we want the returned points
62    // to be uniformly sized.
63    if (fCount != 2 ||
64        !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) ||
65        !SkScalarIsInt(fIntervals[0]) ||
66        !SkScalarIsInt(fIntervals[1])) {
67        return false;
68    }
69
70    SkPoint pts[2];
71
72    if (!src.isLine(pts)) {
73        return false;
74    }
75
76    // TODO: this test could be eased up to allow circles
77    if (SkPaint::kButt_Cap != rec.getCap()) {
78        return false;
79    }
80
81    // TODO: this test could be eased up for circles. Rotations could be allowed.
82    if (!matrix.rectStaysRect()) {
83        return false;
84    }
85
86    SkScalar        length = SkPoint::Distance(pts[1], pts[0]);
87
88    SkVector tangent = pts[1] - pts[0];
89    if (tangent.isZero()) {
90        return false;
91    }
92
93    tangent.scale(SkScalarInvert(length));
94
95    // TODO: make this test for horizontal & vertical lines more robust
96    bool isXAxis = true;
97    if (SK_Scalar1 == tangent.fX || -SK_Scalar1 == tangent.fX) {
98        results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth()));
99    } else if (SK_Scalar1 == tangent.fY || -SK_Scalar1 == tangent.fY) {
100        results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0]));
101        isXAxis = false;
102    } else if (SkPaint::kRound_Cap != rec.getCap()) {
103        // Angled lines don't have axis-aligned boxes.
104        return false;
105    }
106
107    if (NULL != results) {
108        results->fFlags = 0;
109        SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength);
110
111        if (SkPaint::kRound_Cap == rec.getCap()) {
112            results->fFlags |= PointData::kCircles_PointFlag;
113        }
114
115        results->fNumPoints = 0;
116        SkScalar len2 = length;
117        if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
118            SkASSERT(len2 >= clampedInitialDashLength);
119            if (0 == fInitialDashIndex) {
120                if (clampedInitialDashLength > 0) {
121                    if (clampedInitialDashLength >= fIntervals[0]) {
122                        ++results->fNumPoints;  // partial first dash
123                    }
124                    len2 -= clampedInitialDashLength;
125                }
126                len2 -= fIntervals[1];  // also skip first space
127                if (len2 < 0) {
128                    len2 = 0;
129                }
130            } else {
131                len2 -= clampedInitialDashLength; // skip initial partial empty
132            }
133        }
134        int numMidPoints = SkScalarFloorToInt(SkScalarDiv(len2, fIntervalLength));
135        results->fNumPoints += numMidPoints;
136        len2 -= numMidPoints * fIntervalLength;
137        bool partialLast = false;
138        if (len2 > 0) {
139            if (len2 < fIntervals[0]) {
140                partialLast = true;
141            } else {
142                ++numMidPoints;
143                ++results->fNumPoints;
144            }
145        }
146
147        results->fPoints = new SkPoint[results->fNumPoints];
148
149        SkScalar    distance = 0;
150        int         curPt = 0;
151
152        if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
153            SkASSERT(clampedInitialDashLength <= length);
154
155            if (0 == fInitialDashIndex) {
156                if (clampedInitialDashLength > 0) {
157                    // partial first block
158                    SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
159                    SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, SkScalarHalf(clampedInitialDashLength));
160                    SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, SkScalarHalf(clampedInitialDashLength));
161                    SkScalar halfWidth, halfHeight;
162                    if (isXAxis) {
163                        halfWidth = SkScalarHalf(clampedInitialDashLength);
164                        halfHeight = SkScalarHalf(rec.getWidth());
165                    } else {
166                        halfWidth = SkScalarHalf(rec.getWidth());
167                        halfHeight = SkScalarHalf(clampedInitialDashLength);
168                    }
169                    if (clampedInitialDashLength < fIntervals[0]) {
170                        // This one will not be like the others
171                        results->fFirst.addRect(x - halfWidth, y - halfHeight,
172                                                x + halfWidth, y + halfHeight);
173                    } else {
174                        SkASSERT(curPt < results->fNumPoints);
175                        results->fPoints[curPt].set(x, y);
176                        ++curPt;
177                    }
178
179                    distance += clampedInitialDashLength;
180                }
181
182                distance += fIntervals[1];  // skip over the next blank block too
183            } else {
184                distance += clampedInitialDashLength;
185            }
186        }
187
188        if (0 != numMidPoints) {
189            distance += SkScalarHalf(fIntervals[0]);
190
191            for (int i = 0; i < numMidPoints; ++i) {
192                SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance);
193                SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance);
194
195                SkASSERT(curPt < results->fNumPoints);
196                results->fPoints[curPt].set(x, y);
197                ++curPt;
198
199                distance += fIntervalLength;
200            }
201
202            distance -= SkScalarHalf(fIntervals[0]);
203        }
204
205        if (partialLast) {
206            // partial final block
207            SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
208            SkScalar temp = length - distance;
209            SkASSERT(temp < fIntervals[0]);
210            SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance + SkScalarHalf(temp));
211            SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance + SkScalarHalf(temp));
212            SkScalar halfWidth, halfHeight;
213            if (isXAxis) {
214                halfWidth = SkScalarHalf(temp);
215                halfHeight = SkScalarHalf(rec.getWidth());
216            } else {
217                halfWidth = SkScalarHalf(rec.getWidth());
218                halfHeight = SkScalarHalf(temp);
219            }
220            results->fLast.addRect(x - halfWidth, y - halfHeight,
221                                   x + halfWidth, y + halfHeight);
222        }
223
224        SkASSERT(curPt == results->fNumPoints);
225    }
226
227    return true;
228}
229
230SkPathEffect::DashType SkDashPathEffect::asADash(DashInfo* info) const {
231    if (info) {
232        if (info->fCount >= fCount && NULL != info->fIntervals) {
233            memcpy(info->fIntervals, fIntervals, fCount * sizeof(SkScalar));
234        }
235        info->fCount = fCount;
236        info->fPhase = fPhase;
237    }
238    return kDash_DashType;
239}
240
241void SkDashPathEffect::flatten(SkWriteBuffer& buffer) const {
242    buffer.writeScalar(fPhase);
243    buffer.writeScalarArray(fIntervals, fCount);
244}
245
246SkFlattenable* SkDashPathEffect::CreateProc(SkReadBuffer& buffer) {
247    const SkScalar phase = buffer.readScalar();
248    uint32_t count = buffer.getArrayCount();
249    SkAutoSTArray<32, SkScalar> intervals(count);
250    if (buffer.readScalarArray(intervals.get(), count)) {
251        return Create(intervals.get(), SkToInt(count), phase);
252    }
253    return NULL;
254}
255
256#ifdef SK_SUPPORT_LEGACY_DEEPFLATTENING
257SkDashPathEffect::SkDashPathEffect(SkReadBuffer& buffer)
258        : INHERITED(buffer)
259        , fPhase(0)
260        , fInitialDashLength(0)
261        , fInitialDashIndex(0)
262        , fIntervalLength(0) {
263    bool useOldPic = buffer.isVersionLT(SkReadBuffer::kDashWritesPhaseIntervals_Version);
264    if (useOldPic) {
265        fInitialDashIndex = buffer.readInt();
266        fInitialDashLength = buffer.readScalar();
267        fIntervalLength = buffer.readScalar();
268        buffer.readBool(); // Dummy for old ScalarToFit field
269    } else {
270        fPhase = buffer.readScalar();
271    }
272
273    fCount = buffer.getArrayCount();
274    size_t allocSize = sizeof(SkScalar) * fCount;
275    if (buffer.validateAvailable(allocSize)) {
276        fIntervals = (SkScalar*)sk_malloc_throw(allocSize);
277        buffer.readScalarArray(fIntervals, fCount);
278    } else {
279        fIntervals = NULL;
280    }
281
282    if (useOldPic) {
283        fPhase = 0;
284        if (fInitialDashLength != -1) { // Signal for bad dash interval
285            for (int i = 0; i < fInitialDashIndex; ++i) {
286                fPhase += fIntervals[i];
287            }
288            fPhase += fIntervals[fInitialDashIndex] - fInitialDashLength;
289        }
290    } else {
291        // set the internal data members, fPhase should have been between 0 and intervalLength
292        // when written to buffer so no need to adjust it
293        SkDashPath::CalcDashParameters(fPhase, fIntervals, fCount,
294                &fInitialDashLength, &fInitialDashIndex, &fIntervalLength);
295    }
296}
297#endif
298
299