1ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com/*
2ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * Copyright 2006 The Android Open Source Project
3ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com *
4ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * Use of this source code is governed by a BSD-style license that can be
5ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com * found in the LICENSE file.
6ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com */
7ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com
88a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com#include "SkDashPathEffect.h"
9a22ea1882391cc5c84136060636d5c952c1f34b3egdaniel
10a22ea1882391cc5c84136060636d5c952c1f34b3egdaniel#include "SkDashPathPriv.h"
118b0e8ac5f582de80356019406e2975079bf0829dcommit-bot@chromium.org#include "SkReadBuffer.h"
128b0e8ac5f582de80356019406e2975079bf0829dcommit-bot@chromium.org#include "SkWriteBuffer.h"
138a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
141c577cd3ee331944b9061ee0eec147b211ee563cmtkleinSkDashPathEffect::SkDashPathEffect(const SkScalar intervals[], int count, SkScalar phase)
151c577cd3ee331944b9061ee0eec147b211ee563cmtklein        : fPhase(0)
161c577cd3ee331944b9061ee0eec147b211ee563cmtklein        , fInitialDashLength(0)
171c577cd3ee331944b9061ee0eec147b211ee563cmtklein        , fInitialDashIndex(0)
181c577cd3ee331944b9061ee0eec147b211ee563cmtklein        , fIntervalLength(0) {
19aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    SkASSERT(intervals);
20aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    SkASSERT(count > 1 && SkAlign2(count) == count);
21aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org
22aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    fIntervals = (SkScalar*)sk_malloc_throw(sizeof(SkScalar) * count);
23aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    fCount = count;
24aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    for (int i = 0; i < count; i++) {
25aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        SkASSERT(intervals[i] >= 0);
26aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        fIntervals[i] = intervals[i];
27aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    }
28aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org
29a22ea1882391cc5c84136060636d5c952c1f34b3egdaniel    // set the internal data members
301c577cd3ee331944b9061ee0eec147b211ee563cmtklein    SkDashPath::CalcDashParameters(phase, fIntervals, fCount,
311c577cd3ee331944b9061ee0eec147b211ee563cmtklein            &fInitialDashLength, &fInitialDashIndex, &fIntervalLength, &fPhase);
32aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org}
33aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org
343334c3a1fa05b5ee0cab0f2f1ec7b19939737337mike@reedtribe.orgSkDashPathEffect::~SkDashPathEffect() {
358a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com    sk_free(fIntervals);
368a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com}
378a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
383334c3a1fa05b5ee0cab0f2f1ec7b19939737337mike@reedtribe.orgbool SkDashPathEffect::filterPath(SkPath* dst, const SkPath& src,
394bbdeac58cc928dc66296bde3bd06e78070d96b7reed@google.com                              SkStrokeRec* rec, const SkRect* cullRect) const {
40a22ea1882391cc5c84136060636d5c952c1f34b3egdaniel    return SkDashPath::FilterDashPath(dst, src, rec, cullRect, fIntervals, fCount,
41a22ea1882391cc5c84136060636d5c952c1f34b3egdaniel                                      fInitialDashLength, fInitialDashIndex, fIntervalLength);
42629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com}
43629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
44e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillipsstatic void outset_for_stroke(SkRect* rect, const SkStrokeRec& rec) {
45e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    SkScalar radius = SkScalarHalf(rec.getWidth());
46e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    if (0 == radius) {
47e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        radius = SK_Scalar1;    // hairlines
48e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    }
49e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    if (SkPaint::kMiter_Join == rec.getJoin()) {
50e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        radius = SkScalarMul(radius, rec.getMiter());
51e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    }
52e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    rect->outset(radius, radius);
53e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips}
54e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
55e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips// Attempt to trim the line to minimally cover the cull rect (currently
56e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips// only works for horizontal and vertical lines).
57e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips// Return true if processing should continue; false otherwise.
58e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillipsstatic bool cull_line(SkPoint* pts, const SkStrokeRec& rec,
59e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips                      const SkMatrix& ctm, const SkRect* cullRect,
60e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips                      const SkScalar intervalLength) {
61e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    if (NULL == cullRect) {
62e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkASSERT(false); // Shouldn't ever occur in practice
63e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        return false;
64e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    }
65e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
66e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    SkScalar dx = pts[1].x() - pts[0].x();
67e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    SkScalar dy = pts[1].y() - pts[0].y();
68e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
69e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    if ((dx && dy) || (!dx && !dy)) {
70e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        return false;
71e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    }
72e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
73e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    SkRect bounds = *cullRect;
74e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    outset_for_stroke(&bounds, rec);
75e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
76e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    // cullRect is in device space while pts are in the local coordinate system
77e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    // defined by the ctm. We want our answer in the local coordinate system.
78e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
79e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    SkASSERT(ctm.rectStaysRect());
80e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    SkMatrix inv;
81e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    if (!ctm.invert(&inv)) {
82e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        return false;
83e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    }
84e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
85e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    inv.mapRect(&bounds);
86e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
87e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    if (dx) {
88e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkASSERT(dx && !dy);
89e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkScalar minX = pts[0].fX;
90e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkScalar maxX = pts[1].fX;
91e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
92e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        if (dx < 0) {
93e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips            SkTSwap(minX, maxX);
94e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        }
95e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
96e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkASSERT(minX < maxX);
97e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        if (maxX < bounds.fLeft || minX > bounds.fRight) {
98e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips            return false;
99e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        }
100e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
101e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        // Now we actually perform the chop, removing the excess to the left and
102e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        // right of the bounds (keeping our new line "in phase" with the dash,
103e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        // hence the (mod intervalLength).
104e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
105e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        if (minX < bounds.fLeft) {
106e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips            minX = bounds.fLeft - SkScalarMod(bounds.fLeft - minX, intervalLength);
107e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        }
108e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        if (maxX > bounds.fRight) {
109e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips            maxX = bounds.fRight + SkScalarMod(maxX - bounds.fRight, intervalLength);
110e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        }
111e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
112e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkASSERT(maxX > minX);
113e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        if (dx < 0) {
114e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips            SkTSwap(minX, maxX);
115e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        }
116e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        pts[0].fX = minX;
117e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        pts[1].fX = maxX;
118e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    } else {
119e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkASSERT(dy && !dx);
120e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkScalar minY = pts[0].fY;
121e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkScalar maxY = pts[1].fY;
122e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
123e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        if (dy < 0) {
124e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips            SkTSwap(minY, maxY);
125e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        }
126e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
127e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkASSERT(minY < maxY);
128e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        if (maxY < bounds.fTop || minY > bounds.fBottom) {
129e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips            return false;
130e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        }
131e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
132e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        // Now we actually perform the chop, removing the excess to the top and
133e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        // bottom of the bounds (keeping our new line "in phase" with the dash,
134e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        // hence the (mod intervalLength).
135e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
136e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        if (minY < bounds.fTop) {
137e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips            minY = bounds.fTop - SkScalarMod(bounds.fTop - minY, intervalLength);
138e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        }
139e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        if (maxY > bounds.fBottom) {
140e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips            maxY = bounds.fBottom + SkScalarMod(maxY - bounds.fBottom, intervalLength);
141e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        }
142e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
143e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkASSERT(maxY > minY);
144e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        if (dy < 0) {
145e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips            SkTSwap(minY, maxY);
146e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        }
147e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        pts[0].fY = minY;
148e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        pts[1].fY = maxY;
149e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    }
150e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
151e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    return true;
152e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips}
153e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
154629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com// Currently asPoints is more restrictive then it needs to be. In the future
155629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com// we need to:
156629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com//      allow kRound_Cap capping (could allow rotations in the matrix with this)
1576d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com//      allow paths to be returned
158629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.combool SkDashPathEffect::asPoints(PointData* results,
159629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com                                const SkPath& src,
160629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com                                const SkStrokeRec& rec,
1614bbdeac58cc928dc66296bde3bd06e78070d96b7reed@google.com                                const SkMatrix& matrix,
1624bbdeac58cc928dc66296bde3bd06e78070d96b7reed@google.com                                const SkRect* cullRect) const {
1636d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    // width < 0 -> fill && width == 0 -> hairline so requiring width > 0 rules both out
1646d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    if (fInitialDashLength < 0 || 0 >= rec.getWidth()) {
165629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com        return false;
166629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com    }
167629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
1686d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    // TODO: this next test could be eased up. We could allow any number of
1696d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    // intervals as long as all the ons match and all the offs match.
1706d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    // Additionally, they do not necessarily need to be integers.
1716d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    // We cannot allow arbitrary intervals since we want the returned points
1726d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    // to be uniformly sized.
1737a03d86a3d9adcb13432fbd82039725149487c97skia.committer@gmail.com    if (fCount != 2 ||
1746d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        !SkScalarNearlyEqual(fIntervals[0], fIntervals[1]) ||
1756d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        !SkScalarIsInt(fIntervals[0]) ||
1766d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        !SkScalarIsInt(fIntervals[1])) {
177629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com        return false;
178629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com    }
179629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
180629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com    SkPoint pts[2];
181629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
1826d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    if (!src.isLine(pts)) {
183629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com        return false;
184629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com    }
185629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
1866d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    // TODO: this test could be eased up to allow circles
187629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com    if (SkPaint::kButt_Cap != rec.getCap()) {
188629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com        return false;
189629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com    }
190629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
1916d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    // TODO: this test could be eased up for circles. Rotations could be allowed.
192629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com    if (!matrix.rectStaysRect()) {
193629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com        return false;
194629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com    }
195629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
196e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    // See if the line can be limited to something plausible.
197e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    if (!cull_line(pts, rec, matrix, cullRect, fIntervalLength)) {
198e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        return false;
199e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    }
200e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips
201e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    SkScalar length = SkPoint::Distance(pts[1], pts[0]);
2026d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com
2036d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    SkVector tangent = pts[1] - pts[0];
2046d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    if (tangent.isZero()) {
2056d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        return false;
2066d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    }
207629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
2086d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    tangent.scale(SkScalarInvert(length));
2096d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com
2106d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    // TODO: make this test for horizontal & vertical lines more robust
2116d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    bool isXAxis = true;
212e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    if (SkScalarNearlyEqual(SK_Scalar1, tangent.fX) ||
213e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips        SkScalarNearlyEqual(-SK_Scalar1, tangent.fX)) {
2146d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        results->fSize.set(SkScalarHalf(fIntervals[0]), SkScalarHalf(rec.getWidth()));
215e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips    } else if (SkScalarNearlyEqual(SK_Scalar1, tangent.fY) ||
216e12770148a7d170e4845ebfae75ac38ae9cf4f32Robert Phillips               SkScalarNearlyEqual(-SK_Scalar1, tangent.fY)) {
2176d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        results->fSize.set(SkScalarHalf(rec.getWidth()), SkScalarHalf(fIntervals[0]));
2186d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        isXAxis = false;
2196d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com    } else if (SkPaint::kRound_Cap != rec.getCap()) {
2206d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        // Angled lines don't have axis-aligned boxes.
221629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com        return false;
222629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com    }
223629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
22449f085dddff10473b6ebf832a974288300224e60bsalomon    if (results) {
2257a03d86a3d9adcb13432fbd82039725149487c97skia.committer@gmail.com        results->fFlags = 0;
2265c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com        SkScalar clampedInitialDashLength = SkMinScalar(length, fInitialDashLength);
227629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
2286d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        if (SkPaint::kRound_Cap == rec.getCap()) {
2296d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            results->fFlags |= PointData::kCircles_PointFlag;
230629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com        }
231629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
2326d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        results->fNumPoints = 0;
2336d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        SkScalar len2 = length;
2345c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com        if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
2355c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com            SkASSERT(len2 >= clampedInitialDashLength);
2366d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            if (0 == fInitialDashIndex) {
2375c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                if (clampedInitialDashLength > 0) {
2385c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                    if (clampedInitialDashLength >= fIntervals[0]) {
2396d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                        ++results->fNumPoints;  // partial first dash
2406d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                    }
2415c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                    len2 -= clampedInitialDashLength;
2426d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                }
2436d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                len2 -= fIntervals[1];  // also skip first space
2446d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                if (len2 < 0) {
2456d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                    len2 = 0;
2466d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                }
2476d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            } else {
2485c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                len2 -= clampedInitialDashLength; // skip initial partial empty
2496d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            }
2506d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        }
2516d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        int numMidPoints = SkScalarFloorToInt(SkScalarDiv(len2, fIntervalLength));
2526d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        results->fNumPoints += numMidPoints;
2536d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        len2 -= numMidPoints * fIntervalLength;
2546d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        bool partialLast = false;
2556d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        if (len2 > 0) {
2566d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            if (len2 < fIntervals[0]) {
2577a03d86a3d9adcb13432fbd82039725149487c97skia.committer@gmail.com                partialLast = true;
2586d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            } else {
2596d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                ++numMidPoints;
2606d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                ++results->fNumPoints;
2616d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            }
2626d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        }
263629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
264935ad026826fb7d31d562ff7326b84ec3a827456robertphillips@google.com        results->fPoints = new SkPoint[results->fNumPoints];
265629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
2666d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        SkScalar    distance = 0;
2676d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        int         curPt = 0;
2686d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com
2695c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com        if (clampedInitialDashLength > 0 || 0 == fInitialDashIndex) {
2705c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com            SkASSERT(clampedInitialDashLength <= length);
2716d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com
2726d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            if (0 == fInitialDashIndex) {
2735c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                if (clampedInitialDashLength > 0) {
2746d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                    // partial first block
2756d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                    SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
2765c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                    SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, SkScalarHalf(clampedInitialDashLength));
2775c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                    SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, SkScalarHalf(clampedInitialDashLength));
2786d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                    SkScalar halfWidth, halfHeight;
2796d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                    if (isXAxis) {
2805c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                        halfWidth = SkScalarHalf(clampedInitialDashLength);
2816d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                        halfHeight = SkScalarHalf(rec.getWidth());
2826d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                    } else {
2836d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                        halfWidth = SkScalarHalf(rec.getWidth());
2845c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                        halfHeight = SkScalarHalf(clampedInitialDashLength);
2856d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                    }
2865c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                    if (clampedInitialDashLength < fIntervals[0]) {
2876d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                        // This one will not be like the others
2886d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                        results->fFirst.addRect(x - halfWidth, y - halfHeight,
2896d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                                                x + halfWidth, y + halfHeight);
2906d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                    } else {
2916d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                        SkASSERT(curPt < results->fNumPoints);
2926d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                        results->fPoints[curPt].set(x, y);
2936d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                        ++curPt;
2946d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                    }
2956d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com
2965c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                    distance += clampedInitialDashLength;
2976d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                }
298629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
2996d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                distance += fIntervals[1];  // skip over the next blank block too
3006d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            } else {
3015c4d5582c9d47ea47c7699fe69b9f95d0117dbd5robertphillips@google.com                distance += clampedInitialDashLength;
3026d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            }
3036d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        }
3046d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com
3056d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        if (0 != numMidPoints) {
3066d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            distance += SkScalarHalf(fIntervals[0]);
307629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
3086d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            for (int i = 0; i < numMidPoints; ++i) {
3096d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance);
3106d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance);
3116d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com
3126d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                SkASSERT(curPt < results->fNumPoints);
3136d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                results->fPoints[curPt].set(x, y);
3146d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                ++curPt;
3156d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com
3166d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                distance += fIntervalLength;
317629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com            }
318629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
3196d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            distance -= SkScalarHalf(fIntervals[0]);
3206d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        }
3216d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com
3226d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        if (partialLast) {
3236d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            // partial final block
3246d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            SkASSERT(SkPaint::kRound_Cap != rec.getCap()); // can't handle partial circles
3256d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            SkScalar temp = length - distance;
3266d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            SkASSERT(temp < fIntervals[0]);
3276d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            SkScalar x = pts[0].fX + SkScalarMul(tangent.fX, distance + SkScalarHalf(temp));
3286d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            SkScalar y = pts[0].fY + SkScalarMul(tangent.fY, distance + SkScalarHalf(temp));
3296d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            SkScalar halfWidth, halfHeight;
3306d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            if (isXAxis) {
3316d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                halfWidth = SkScalarHalf(temp);
3326d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                halfHeight = SkScalarHalf(rec.getWidth());
3336d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            } else {
3346d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                halfWidth = SkScalarHalf(rec.getWidth());
3356d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                halfHeight = SkScalarHalf(temp);
3366d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            }
3376d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com            results->fLast.addRect(x - halfWidth, y - halfHeight,
3386d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com                                   x + halfWidth, y + halfHeight);
339629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com        }
340935ad026826fb7d31d562ff7326b84ec3a827456robertphillips@google.com
3416d87557278052c131957e5d6e093d3a675162d22robertphillips@google.com        SkASSERT(curPt == results->fNumPoints);
342629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com    }
343629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com
344629ab540667422d3edcb97c51e9628b7051e1ba4robertphillips@google.com    return true;
3458a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com}
3468a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
347aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.orgSkPathEffect::DashType SkDashPathEffect::asADash(DashInfo* info) const {
348aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    if (info) {
34949f085dddff10473b6ebf832a974288300224e60bsalomon        if (info->fCount >= fCount && info->fIntervals) {
350aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org            memcpy(info->fIntervals, fIntervals, fCount * sizeof(SkScalar));
351aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        }
352aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        info->fCount = fCount;
353aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        info->fPhase = fPhase;
354aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    }
355aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    return kDash_DashType;
356aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org}
357aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org
3588b0e8ac5f582de80356019406e2975079bf0829dcommit-bot@chromium.orgvoid SkDashPathEffect::flatten(SkWriteBuffer& buffer) const {
359aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    buffer.writeScalar(fPhase);
360c73dd5c6880739f26216f198c757028fd28df1a4djsollen@google.com    buffer.writeScalarArray(fIntervals, fCount);
3618a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com}
3628a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
3638b0e8ac5f582de80356019406e2975079bf0829dcommit-bot@chromium.orgSkFlattenable* SkDashPathEffect::CreateProc(SkReadBuffer& buffer) {
3649fa60daad4d5f54c0dbe3dbcc7608a8f6d721187reed    const SkScalar phase = buffer.readScalar();
3659fa60daad4d5f54c0dbe3dbcc7608a8f6d721187reed    uint32_t count = buffer.getArrayCount();
3669fa60daad4d5f54c0dbe3dbcc7608a8f6d721187reed    SkAutoSTArray<32, SkScalar> intervals(count);
3679fa60daad4d5f54c0dbe3dbcc7608a8f6d721187reed    if (buffer.readScalarArray(intervals.get(), count)) {
3689fa60daad4d5f54c0dbe3dbcc7608a8f6d721187reed        return Create(intervals.get(), SkToInt(count), phase);
3699fa60daad4d5f54c0dbe3dbcc7608a8f6d721187reed    }
3709fa60daad4d5f54c0dbe3dbcc7608a8f6d721187reed    return NULL;
3718a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com}
3728a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
3739fa60daad4d5f54c0dbe3dbcc7608a8f6d721187reed#ifdef SK_SUPPORT_LEGACY_DEEPFLATTENING
3741c577cd3ee331944b9061ee0eec147b211ee563cmtkleinSkDashPathEffect::SkDashPathEffect(SkReadBuffer& buffer)
3751c577cd3ee331944b9061ee0eec147b211ee563cmtklein        : INHERITED(buffer)
3761c577cd3ee331944b9061ee0eec147b211ee563cmtklein        , fPhase(0)
3771c577cd3ee331944b9061ee0eec147b211ee563cmtklein        , fInitialDashLength(0)
3781c577cd3ee331944b9061ee0eec147b211ee563cmtklein        , fInitialDashIndex(0)
3791c577cd3ee331944b9061ee0eec147b211ee563cmtklein        , fIntervalLength(0) {
3807ed173b1ebac84671fb0dc1b9bd323a5e6e63771commit-bot@chromium.org    bool useOldPic = buffer.isVersionLT(SkReadBuffer::kDashWritesPhaseIntervals_Version);
381aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    if (useOldPic) {
382aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        fInitialDashIndex = buffer.readInt();
383aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        fInitialDashLength = buffer.readScalar();
384aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        fIntervalLength = buffer.readScalar();
385aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        buffer.readBool(); // Dummy for old ScalarToFit field
386aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    } else {
387aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        fPhase = buffer.readScalar();
388aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    }
389fbfcd5602128ec010c82cb733c9cdc0a3254f9f3rmistry@google.com
390c73dd5c6880739f26216f198c757028fd28df1a4djsollen@google.com    fCount = buffer.getArrayCount();
391ef74fa189b738e13295d6a96f86a6e10223505a8commit-bot@chromium.org    size_t allocSize = sizeof(SkScalar) * fCount;
392ef74fa189b738e13295d6a96f86a6e10223505a8commit-bot@chromium.org    if (buffer.validateAvailable(allocSize)) {
393ef74fa189b738e13295d6a96f86a6e10223505a8commit-bot@chromium.org        fIntervals = (SkScalar*)sk_malloc_throw(allocSize);
394ef74fa189b738e13295d6a96f86a6e10223505a8commit-bot@chromium.org        buffer.readScalarArray(fIntervals, fCount);
395ef74fa189b738e13295d6a96f86a6e10223505a8commit-bot@chromium.org    } else {
396ef74fa189b738e13295d6a96f86a6e10223505a8commit-bot@chromium.org        fIntervals = NULL;
397ef74fa189b738e13295d6a96f86a6e10223505a8commit-bot@chromium.org    }
398aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org
399aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    if (useOldPic) {
400aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        fPhase = 0;
4016b3eebce43dcd0e409ce953d2a2ea93df2b97733commit-bot@chromium.org        if (fInitialDashLength != -1) { // Signal for bad dash interval
4026b3eebce43dcd0e409ce953d2a2ea93df2b97733commit-bot@chromium.org            for (int i = 0; i < fInitialDashIndex; ++i) {
4036b3eebce43dcd0e409ce953d2a2ea93df2b97733commit-bot@chromium.org                fPhase += fIntervals[i];
4046b3eebce43dcd0e409ce953d2a2ea93df2b97733commit-bot@chromium.org            }
4056b3eebce43dcd0e409ce953d2a2ea93df2b97733commit-bot@chromium.org            fPhase += fIntervals[fInitialDashIndex] - fInitialDashLength;
406aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org        }
407aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    } else {
408a22ea1882391cc5c84136060636d5c952c1f34b3egdaniel        // set the internal data members, fPhase should have been between 0 and intervalLength
409a22ea1882391cc5c84136060636d5c952c1f34b3egdaniel        // when written to buffer so no need to adjust it
4101c577cd3ee331944b9061ee0eec147b211ee563cmtklein        SkDashPath::CalcDashParameters(fPhase, fIntervals, fCount,
4111c577cd3ee331944b9061ee0eec147b211ee563cmtklein                &fInitialDashLength, &fInitialDashIndex, &fIntervalLength);
412aec143824c9be4e4af6e2cb7cce3d2d2268c0b15commit-bot@chromium.org    }
4138a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com}
4149fa60daad4d5f54c0dbe3dbcc7608a8f6d721187reed#endif
4159fa60daad4d5f54c0dbe3dbcc7608a8f6d721187reed
416