blob: d1e4d79179003a66c02cbbac0c5d49363f2d4194 [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
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000018SK_DEFINE_INST_COUNT(SkPath);
19
reed@google.com744faba2012-05-29 19:54:52 +000020// This value is just made-up for now. When count is 4, calling memset was much
21// slower than just writing the loop. This seems odd, and hopefully in the
22// future this we appear to have been a fluke...
23#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
24
reed@android.com8a1c16f2008-12-17 15:59:43 +000025////////////////////////////////////////////////////////////////////////////
26
reed@google.com3563c9e2011-11-14 19:34:57 +000027/**
28 * Path.bounds is defined to be the bounds of all the control points.
29 * If we called bounds.join(r) we would skip r if r was empty, which breaks
30 * our promise. Hence we have a custom joiner that doesn't look at emptiness
31 */
32static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
33 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
34 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
35 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
36 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
37}
38
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000039static bool is_degenerate(const SkPath& path) {
40 SkPath::Iter iter(path, false);
41 SkPoint pts[4];
42 return SkPath::kDone_Verb == iter.next(pts);
43}
44
bsalomon@google.com6aa29652012-04-18 13:29:52 +000045class SkAutoDisableOvalCheck {
46public:
47 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
48 fSaved = fPath->fIsOval;
49 }
50
51 ~SkAutoDisableOvalCheck() {
52 fPath->fIsOval = fSaved;
53 }
54
55private:
56 SkPath* fPath;
57 bool fSaved;
58};
59
bsalomon@google.com30c174b2012-11-13 14:36:42 +000060class SkAutoDisableDirectionCheck {
61public:
62 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
63 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
64 }
65
66 ~SkAutoDisableDirectionCheck() {
67 fPath->fDirection = fSaved;
68 }
69
70private:
71 SkPath* fPath;
72 SkPath::Direction fSaved;
73};
74
reed@android.com8a1c16f2008-12-17 15:59:43 +000075/* This guy's constructor/destructor bracket a path editing operation. It is
76 used when we know the bounds of the amount we are going to add to the path
77 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000078
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 It captures some state about the path up front (i.e. if it already has a
80 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000081 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000082
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000083 It also notes if the path was originally degenerate, and if so, sets
84 isConvex to true. Thus it can only be used if the contour being added is
85 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +000086 */
87class SkAutoPathBoundsUpdate {
88public:
89 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
90 this->init(path);
91 }
92
93 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
94 SkScalar right, SkScalar bottom) {
95 fRect.set(left, top, right, bottom);
96 this->init(path);
97 }
reed@google.comabf15c12011-01-18 20:35:51 +000098
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000100 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000101 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +0000102 fPath->fBounds = fRect;
103 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000104 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000105 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +0000106 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +0000107 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000108 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000109 }
110 }
reed@google.comabf15c12011-01-18 20:35:51 +0000111
reed@android.com8a1c16f2008-12-17 15:59:43 +0000112private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000113 SkPath* fPath;
114 SkRect fRect;
115 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000116 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000117 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000118
reed@android.com8a1c16f2008-12-17 15:59:43 +0000119 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000120 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000122 // Mark the path's bounds as dirty if (1) they are, or (2) the path
123 // is non-finite, and therefore its bounds are not meaningful
124 fDirty = SkToBool(path->fBoundsIsDirty) || !path->fIsFinite;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000125 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000127 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000128 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129 }
130};
131
reed@google.com0bb18bb2012-07-26 15:20:36 +0000132// Return true if the computed bounds are finite.
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000133static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
134 int count = ref.countPoints();
reed@google.com0bb18bb2012-07-26 15:20:36 +0000135 if (count <= 1) { // we ignore just 1 point (moveto)
136 bounds->setEmpty();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000137 return count ? ref.points()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000138 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000139 return bounds->setBoundsCheck(ref.points(), count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140 }
141}
142
143////////////////////////////////////////////////////////////////////////////
144
145/*
146 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000147 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000148 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
149
150 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000151 1. If we encounter degenerate segments, remove them
152 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
153 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
154 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155*/
156
157////////////////////////////////////////////////////////////////////////////
158
reed@google.comd335d1d2012-01-12 18:17:11 +0000159// flag to require a moveTo if we begin with something else, like lineTo etc.
160#define INITIAL_LASTMOVETOINDEX_VALUE ~0
161
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000162SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000163 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000164#ifdef SK_BUILD_FOR_ANDROID
165 , fGenerationID(0)
166#endif
167{
168 this->resetFields();
169}
170
171void SkPath::resetFields() {
172 //fPathRef is assumed to have been emptied by the caller.
173 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
174 fFillType = kWinding_FillType;
175 fSegmentMask = 0;
176 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000177 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000178 fDirection = kUnknown_Direction;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000179 fIsFinite = false;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000180 fIsOval = false;
djsollen@google.com56c69772011-11-08 19:00:26 +0000181#ifdef SK_BUILD_FOR_ANDROID
bungeman@google.coma5809a32013-06-21 15:13:34 +0000182 GEN_ID_INC;
djsollen@google.come63793a2012-03-21 15:39:03 +0000183 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000184#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000185}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000186
bungeman@google.coma5809a32013-06-21 15:13:34 +0000187SkPath::SkPath(const SkPath& that)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000188 : fPathRef(SkRef(that.fPathRef.get()))
bungeman@google.coma5809a32013-06-21 15:13:34 +0000189#ifdef SK_BUILD_FOR_ANDROID
190 , fGenerationID(0)
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000191#endif
192{
bungeman@google.coma5809a32013-06-21 15:13:34 +0000193 this->copyFields(that);
194 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000195}
196
197SkPath::~SkPath() {
198 SkDEBUGCODE(this->validate();)
199}
200
bungeman@google.coma5809a32013-06-21 15:13:34 +0000201SkPath& SkPath::operator=(const SkPath& that) {
202 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000203
bungeman@google.coma5809a32013-06-21 15:13:34 +0000204 if (this != &that) {
205 fPathRef.reset(SkRef(that.fPathRef.get()));
206 this->copyFields(that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000207 }
208 SkDEBUGCODE(this->validate();)
209 return *this;
210}
211
bungeman@google.coma5809a32013-06-21 15:13:34 +0000212void SkPath::copyFields(const SkPath& that) {
213 //fPathRef is assumed to have been set by the caller.
214 fBounds = that.fBounds;
215 fLastMoveToIndex = that.fLastMoveToIndex;
216 fFillType = that.fFillType;
217 fSegmentMask = that.fSegmentMask;
218 fBoundsIsDirty = that.fBoundsIsDirty;
219 fConvexity = that.fConvexity;
220 fDirection = that.fDirection;
221 fIsFinite = that.fIsFinite;
222 fIsOval = that.fIsOval;
223#ifdef SK_BUILD_FOR_ANDROID
224 GEN_ID_INC;
225 fSourcePath = NULL;
226#endif
227}
228
wjmaclean@chromium.org22023be2012-09-06 18:42:03 +0000229SK_API bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000230 // note: don't need to look at isConvex or bounds, since just comparing the
231 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000232
233 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
234 // since it is only a cache of info in the fVerbs, but its a fast way to
235 // notice a difference
236
reed@android.com3abec1d2009-03-02 05:36:20 +0000237 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000238 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000239 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000240}
241
bungeman@google.coma5809a32013-06-21 15:13:34 +0000242void SkPath::swap(SkPath& that) {
243 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000244
bungeman@google.coma5809a32013-06-21 15:13:34 +0000245 if (this != &that) {
246 fPathRef.swap(&that.fPathRef);
247 SkTSwap<SkRect>(fBounds, that.fBounds);
248 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
249 SkTSwap<uint8_t>(fFillType, that.fFillType);
250 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
251 SkTSwap<uint8_t>(fBoundsIsDirty, that.fBoundsIsDirty);
252 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
253 SkTSwap<uint8_t>(fDirection, that.fDirection);
254 SkTSwap<SkBool8>(fIsFinite, that.fIsFinite);
255 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000256 GEN_ID_INC;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000257 GEN_ID_PTR_INC(&that);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000258 }
259}
260
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000261static inline bool check_edge_against_rect(const SkPoint& p0,
262 const SkPoint& p1,
263 const SkRect& rect,
264 SkPath::Direction dir) {
265 const SkPoint* edgeBegin;
266 SkVector v;
267 if (SkPath::kCW_Direction == dir) {
268 v = p1 - p0;
269 edgeBegin = &p0;
270 } else {
271 v = p0 - p1;
272 edgeBegin = &p1;
273 }
274 if (v.fX || v.fY) {
275 // check the cross product of v with the vec from edgeBegin to each rect corner
276 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
277 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
278 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
279 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
280 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
281 return false;
282 }
283 }
284 return true;
285}
286
287bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
288 // This only handles non-degenerate convex paths currently.
289 if (kConvex_Convexity != this->getConvexity()) {
290 return false;
291 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000292
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000293 Direction direction;
294 if (!this->cheapComputeDirection(&direction)) {
295 return false;
296 }
297
298 SkPoint firstPt;
299 SkPoint prevPt;
300 RawIter iter(*this);
301 SkPath::Verb verb;
302 SkPoint pts[4];
303 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000304 SkDEBUGCODE(int segmentCount = 0;)
305 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000306
307 while ((verb = iter.next(pts)) != kDone_Verb) {
308 int nextPt = -1;
309 switch (verb) {
310 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000311 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000312 SkDEBUGCODE(++moveCnt);
313 firstPt = prevPt = pts[0];
314 break;
315 case kLine_Verb:
316 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000317 SkASSERT(moveCnt && !closeCount);
318 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000319 break;
320 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000321 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000322 SkASSERT(moveCnt && !closeCount);
323 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000324 nextPt = 2;
325 break;
326 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000327 SkASSERT(moveCnt && !closeCount);
328 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000329 nextPt = 3;
330 break;
331 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000332 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000333 break;
334 default:
335 SkDEBUGFAIL("unknown verb");
336 }
337 if (-1 != nextPt) {
338 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
339 return false;
340 }
341 prevPt = pts[nextPt];
342 }
343 }
344
345 return check_edge_against_rect(prevPt, firstPt, rect, direction);
346}
347
djsollen@google.com56c69772011-11-08 19:00:26 +0000348#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000349uint32_t SkPath::getGenerationID() const {
350 return fGenerationID;
351}
djsollen@google.come63793a2012-03-21 15:39:03 +0000352
353const SkPath* SkPath::getSourcePath() const {
354 return fSourcePath;
355}
356
357void SkPath::setSourcePath(const SkPath* path) {
358 fSourcePath = path;
359}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000360#endif
361
reed@android.com8a1c16f2008-12-17 15:59:43 +0000362void SkPath::reset() {
363 SkDEBUGCODE(this->validate();)
364
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000365 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000366 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000367}
368
369void SkPath::rewind() {
370 SkDEBUGCODE(this->validate();)
371
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000372 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000373 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000374}
375
376bool SkPath::isEmpty() const {
377 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000378 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000379}
380
reed@google.com7e6c4d12012-05-10 14:05:43 +0000381bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000382 int verbCount = fPathRef->countVerbs();
383 int ptCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000384
reed@google.com7e6c4d12012-05-10 14:05:43 +0000385 if (2 == verbCount && 2 == ptCount) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000386 if (kMove_Verb == fPathRef->atVerb(0) &&
387 kLine_Verb == fPathRef->atVerb(1)) {
reed@google.com7e6c4d12012-05-10 14:05:43 +0000388 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000389 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000390 line[0] = pts[0];
391 line[1] = pts[1];
392 }
393 return true;
394 }
395 }
396 return false;
397}
398
caryclark@google.comf1316942011-07-26 19:54:45 +0000399/*
400 Determines if path is a rect by keeping track of changes in direction
401 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000402
caryclark@google.comf1316942011-07-26 19:54:45 +0000403 The direction is computed such that:
404 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000405 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000406 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000407 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000408
caryclark@google.comf1316942011-07-26 19:54:45 +0000409A rectangle cycles up/right/down/left or up/left/down/right.
410
411The test fails if:
412 The path is closed, and followed by a line.
413 A second move creates a new endpoint.
414 A diagonal line is parsed.
415 There's more than four changes of direction.
416 There's a discontinuity on the line (e.g., a move in the middle)
417 The line reverses direction.
418 The rectangle doesn't complete a cycle.
419 The path contains a quadratic or cubic.
420 The path contains fewer than four points.
421 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000422
caryclark@google.comf1316942011-07-26 19:54:45 +0000423It's OK if the path has:
424 Several colinear line segments composing a rectangle side.
425 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000426
caryclark@google.comf1316942011-07-26 19:54:45 +0000427The direction takes advantage of the corners found since opposite sides
428must travel in opposite directions.
429
430FIXME: Allow colinear quads and cubics to be treated like lines.
431FIXME: If the API passes fill-only, return true if the filled stroke
432 is a rectangle, though the caller failed to close the path.
433 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000434bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
435 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000436 int corners = 0;
437 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000438 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000439 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000440 first.set(0, 0);
441 last.set(0, 0);
442 int firstDirection = 0;
443 int lastDirection = 0;
444 int nextDirection = 0;
445 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000446 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000447 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000448 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
449 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000450 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000451 savePts = pts;
452 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000453 autoClose = true;
454 case kLine_Verb: {
455 SkScalar left = last.fX;
456 SkScalar top = last.fY;
457 SkScalar right = pts->fX;
458 SkScalar bottom = pts->fY;
459 ++pts;
460 if (left != right && top != bottom) {
461 return false; // diagonal
462 }
463 if (left == right && top == bottom) {
464 break; // single point on side OK
465 }
466 nextDirection = (left != right) << 0 |
467 (left < right || top < bottom) << 1;
468 if (0 == corners) {
469 firstDirection = nextDirection;
470 first = last;
471 last = pts[-1];
472 corners = 1;
473 closedOrMoved = false;
474 break;
475 }
476 if (closedOrMoved) {
477 return false; // closed followed by a line
478 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000479 if (autoClose && nextDirection == firstDirection) {
480 break; // colinear with first
481 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000482 closedOrMoved = autoClose;
483 if (lastDirection != nextDirection) {
484 if (++corners > 4) {
485 return false; // too many direction changes
486 }
487 }
488 last = pts[-1];
489 if (lastDirection == nextDirection) {
490 break; // colinear segment
491 }
492 // Possible values for corners are 2, 3, and 4.
493 // When corners == 3, nextDirection opposes firstDirection.
494 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000495 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000496 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
497 if ((directionCycle ^ turn) != nextDirection) {
498 return false; // direction didn't follow cycle
499 }
500 break;
501 }
502 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000503 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000504 case kCubic_Verb:
505 return false; // quadratic, cubic not allowed
506 case kMove_Verb:
507 last = *pts++;
508 closedOrMoved = true;
509 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000510 default:
511 SkASSERT(!"unexpected verb");
512 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000513 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000514 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000515 lastDirection = nextDirection;
516 }
517 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000518 bool result = 4 == corners && (first == last || autoClose);
519 if (savePts) {
520 *ptsPtr = savePts;
521 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000522 if (result && isClosed) {
523 *isClosed = autoClose;
524 }
525 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000526 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000527 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000528 return result;
529}
530
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000531bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000532 SkDEBUGCODE(this->validate();)
533 int currVerb = 0;
534 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000535 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
536 if (result && rect) {
537 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000538 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000539 return result;
540}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000541
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000542bool SkPath::isRect(bool* isClosed, Direction* direction) const {
543 SkDEBUGCODE(this->validate();)
544 int currVerb = 0;
545 const SkPoint* pts = fPathRef->points();
546 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000547}
548
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000549bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000550 SkDEBUGCODE(this->validate();)
551 int currVerb = 0;
552 const SkPoint* pts = fPathRef->points();
553 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000554 Direction testDirs[2];
555 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000556 return false;
557 }
558 const SkPoint* last = pts;
559 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000560 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000561 testRects[0].set(first, SkToS32(last - first));
562 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000563 if (testRects[0].contains(testRects[1])) {
564 if (rects) {
565 rects[0] = testRects[0];
566 rects[1] = testRects[1];
567 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000568 if (dirs) {
569 dirs[0] = testDirs[0];
570 dirs[1] = testDirs[1];
571 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000572 return true;
573 }
574 if (testRects[1].contains(testRects[0])) {
575 if (rects) {
576 rects[0] = testRects[1];
577 rects[1] = testRects[0];
578 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000579 if (dirs) {
580 dirs[0] = testDirs[1];
581 dirs[1] = testDirs[0];
582 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000583 return true;
584 }
585 }
586 return false;
587}
588
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000589int SkPath::countPoints() const {
590 return fPathRef->countPoints();
591}
592
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000593int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000594 SkDEBUGCODE(this->validate();)
595
596 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000597 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000598 int count = SkMin32(max, fPathRef->countPoints());
599 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
600 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000601}
602
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000603SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000604 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
605 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000606 }
607 return SkPoint::Make(0, 0);
608}
609
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000610int SkPath::countVerbs() const {
611 return fPathRef->countVerbs();
612}
613
614static inline void copy_verbs_reverse(uint8_t* inorderDst,
615 const uint8_t* reversedSrc,
616 int count) {
617 for (int i = 0; i < count; ++i) {
618 inorderDst[i] = reversedSrc[~i];
619 }
620}
621
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000622int SkPath::getVerbs(uint8_t dst[], int max) const {
623 SkDEBUGCODE(this->validate();)
624
625 SkASSERT(max >= 0);
626 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000627 int count = SkMin32(max, fPathRef->countVerbs());
628 copy_verbs_reverse(dst, fPathRef->verbs(), count);
629 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000630}
631
reed@google.com294dd7b2011-10-11 11:58:32 +0000632bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000633 SkDEBUGCODE(this->validate();)
634
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000635 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000636 if (count > 0) {
637 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000638 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000639 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000640 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000641 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000642 if (lastPt) {
643 lastPt->set(0, 0);
644 }
645 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646}
647
648void SkPath::setLastPt(SkScalar x, SkScalar y) {
649 SkDEBUGCODE(this->validate();)
650
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000651 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000652 if (count == 0) {
653 this->moveTo(x, y);
654 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000655 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000656 SkPathRef::Editor ed(&fPathRef);
657 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000658 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000659 }
660}
661
reed@android.comd252db02009-04-01 18:31:44 +0000662void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000664 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000665
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000666 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000667 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668}
669
reed@google.com04863fa2011-05-15 04:08:24 +0000670void SkPath::setConvexity(Convexity c) {
671 if (fConvexity != c) {
672 fConvexity = c;
673 GEN_ID_INC;
674 }
675}
676
reed@android.com8a1c16f2008-12-17 15:59:43 +0000677//////////////////////////////////////////////////////////////////////////////
678// Construction methods
679
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000680#define DIRTY_AFTER_EDIT \
681 do { \
682 fBoundsIsDirty = true; \
683 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000684 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000685 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000686 } while (0)
687
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000688#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
689 do { \
690 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000691 } while (0)
692
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693void SkPath::incReserve(U16CPU inc) {
694 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000695 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 SkDEBUGCODE(this->validate();)
697}
698
699void SkPath::moveTo(SkScalar x, SkScalar y) {
700 SkDEBUGCODE(this->validate();)
701
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000702 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703
reed@google.comd335d1d2012-01-12 18:17:11 +0000704 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000705 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000706
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000707 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000709 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000710 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000711}
712
713void SkPath::rMoveTo(SkScalar x, SkScalar y) {
714 SkPoint pt;
715 this->getLastPt(&pt);
716 this->moveTo(pt.fX + x, pt.fY + y);
717}
718
reed@google.comd335d1d2012-01-12 18:17:11 +0000719void SkPath::injectMoveToIfNeeded() {
720 if (fLastMoveToIndex < 0) {
721 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000722 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000723 x = y = 0;
724 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000725 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000726 x = pt.fX;
727 y = pt.fY;
728 }
729 this->moveTo(x, y);
730 }
731}
732
reed@android.com8a1c16f2008-12-17 15:59:43 +0000733void SkPath::lineTo(SkScalar x, SkScalar y) {
734 SkDEBUGCODE(this->validate();)
735
reed@google.comd335d1d2012-01-12 18:17:11 +0000736 this->injectMoveToIfNeeded();
737
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000738 SkPathRef::Editor ed(&fPathRef);
739 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000740 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000741
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000742 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000743 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000744}
745
746void SkPath::rLineTo(SkScalar x, SkScalar y) {
747 SkPoint pt;
748 this->getLastPt(&pt);
749 this->lineTo(pt.fX + x, pt.fY + y);
750}
751
752void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
753 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000754
reed@google.comd335d1d2012-01-12 18:17:11 +0000755 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000756
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000757 SkPathRef::Editor ed(&fPathRef);
758 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000759 pts[0].set(x1, y1);
760 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000761 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000762
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000763 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000764 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000765}
766
767void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
768 SkPoint pt;
769 this->getLastPt(&pt);
770 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
771}
772
reed@google.com277c3f82013-05-31 15:17:50 +0000773void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
774 SkScalar w) {
775 // check for <= 0 or NaN with this test
776 if (!(w > 0)) {
777 this->lineTo(x2, y2);
778 } else if (!SkScalarIsFinite(w)) {
779 this->lineTo(x1, y1);
780 this->lineTo(x2, y2);
781 } else if (SK_Scalar1 == w) {
782 this->quadTo(x1, y1, x2, y2);
783 } else {
784 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000785
reed@google.com277c3f82013-05-31 15:17:50 +0000786 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000787
reed@google.com277c3f82013-05-31 15:17:50 +0000788 SkPathRef::Editor ed(&fPathRef);
789 SkPoint* pts = ed.growForConic(w);
790 pts[0].set(x1, y1);
791 pts[1].set(x2, y2);
792 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000793
reed@google.com277c3f82013-05-31 15:17:50 +0000794 GEN_ID_INC;
795 DIRTY_AFTER_EDIT;
796 }
797}
798
799void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
800 SkScalar w) {
801 SkPoint pt;
802 this->getLastPt(&pt);
803 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
804}
805
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
807 SkScalar x3, SkScalar y3) {
808 SkDEBUGCODE(this->validate();)
809
reed@google.comd335d1d2012-01-12 18:17:11 +0000810 this->injectMoveToIfNeeded();
811
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000812 SkPathRef::Editor ed(&fPathRef);
813 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000814 pts[0].set(x1, y1);
815 pts[1].set(x2, y2);
816 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000817 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000818
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000819 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000820 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000821}
822
823void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
824 SkScalar x3, SkScalar y3) {
825 SkPoint pt;
826 this->getLastPt(&pt);
827 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
828 pt.fX + x3, pt.fY + y3);
829}
830
831void SkPath::close() {
832 SkDEBUGCODE(this->validate();)
833
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000834 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000836 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000837 case kLine_Verb:
838 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000839 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000840 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000841 case kMove_Verb: {
842 SkPathRef::Editor ed(&fPathRef);
843 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000844 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000845 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000846 }
reed@google.com277c3f82013-05-31 15:17:50 +0000847 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000848 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000849 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000850 default:
851 SkASSERT(!"unexpected verb");
852 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000853 }
854 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000855
856 // signal that we need a moveTo to follow us (unless we're done)
857#if 0
858 if (fLastMoveToIndex >= 0) {
859 fLastMoveToIndex = ~fLastMoveToIndex;
860 }
861#else
862 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
863#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000864}
865
866///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000867
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000868static void assert_known_direction(int dir) {
869 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
870}
871
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872void SkPath::addRect(const SkRect& rect, Direction dir) {
873 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
874}
875
876void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
877 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000878 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000879 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
880 SkAutoDisableDirectionCheck addc(this);
881
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
883
884 this->incReserve(5);
885
886 this->moveTo(left, top);
887 if (dir == kCCW_Direction) {
888 this->lineTo(left, bottom);
889 this->lineTo(right, bottom);
890 this->lineTo(right, top);
891 } else {
892 this->lineTo(right, top);
893 this->lineTo(right, bottom);
894 this->lineTo(left, bottom);
895 }
896 this->close();
897}
898
reed@google.com744faba2012-05-29 19:54:52 +0000899void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
900 SkDEBUGCODE(this->validate();)
901 if (count <= 0) {
902 return;
903 }
904
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000905 SkPathRef::Editor ed(&fPathRef);
906 fLastMoveToIndex = ed.pathRef()->countPoints();
907 uint8_t* vb;
908 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000909 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000910 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000911
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000912 memcpy(p, pts, count * sizeof(SkPoint));
913 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000914 if (count > 1) {
915 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
916 // be 0, the compiler will remove the test/branch entirely.
917 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000918 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000919 } else {
920 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000921 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000922 }
923 }
924 fSegmentMask |= kLine_SegmentMask;
925 }
926 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000927 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000928 }
929
930 GEN_ID_INC;
931 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000932 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000933}
934
reed@android.com8a1c16f2008-12-17 15:59:43 +0000935static void add_corner_arc(SkPath* path, const SkRect& rect,
936 SkScalar rx, SkScalar ry, int startAngle,
937 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000938 // These two asserts are not sufficient, since really we want to know
939 // that the pair of radii (e.g. left and right, or top and bottom) sum
940 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000941 // these conservative asserts.
942 SkASSERT(0 <= rx && rx <= rect.width());
943 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000944
reed@android.com8a1c16f2008-12-17 15:59:43 +0000945 SkRect r;
946 r.set(-rx, -ry, rx, ry);
947
948 switch (startAngle) {
949 case 0:
950 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
951 break;
952 case 90:
953 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
954 break;
955 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
956 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000957 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000958 }
reed@google.comabf15c12011-01-18 20:35:51 +0000959
reed@android.com8a1c16f2008-12-17 15:59:43 +0000960 SkScalar start = SkIntToScalar(startAngle);
961 SkScalar sweep = SkIntToScalar(90);
962 if (SkPath::kCCW_Direction == dir) {
963 start += sweep;
964 sweep = -sweep;
965 }
reed@google.comabf15c12011-01-18 20:35:51 +0000966
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967 path->arcTo(r, start, sweep, forceMoveTo);
968}
969
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000970void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000971 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000972 SkRRect rrect;
973 rrect.setRectRadii(rect, (const SkVector*) radii);
974 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975}
976
reed@google.com4ed0fb72012-12-12 20:48:18 +0000977void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000978 assert_known_direction(dir);
979
980 if (rrect.isEmpty()) {
981 return;
982 }
983
reed@google.com4ed0fb72012-12-12 20:48:18 +0000984 const SkRect& bounds = rrect.getBounds();
985
986 if (rrect.isRect()) {
987 this->addRect(bounds, dir);
988 } else if (rrect.isOval()) {
989 this->addOval(bounds, dir);
990 } else if (rrect.isSimple()) {
991 const SkVector& rad = rrect.getSimpleRadii();
992 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
993 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000994 SkAutoPathBoundsUpdate apbu(this, bounds);
995
996 if (kCW_Direction == dir) {
997 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
998 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
999 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1000 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1001 } else {
1002 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1003 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1004 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1005 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1006 }
1007 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001008 }
1009}
1010
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001011bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001012 int count = fPathRef->countVerbs();
1013 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1014 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001015 if (*verbs == kLine_Verb ||
1016 *verbs == kQuad_Verb ||
1017 *verbs == kCubic_Verb) {
1018 return false;
1019 }
1020 ++verbs;
1021 }
1022 return true;
1023}
1024
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001025#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
1026
1027void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1028 Direction dir) {
1029 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001030
humper@google.com75e3ca12013-04-08 21:44:11 +00001031 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001032 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001033 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001034 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001035 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1036 return;
1037 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001038
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001039 SkScalar w = rect.width();
1040 SkScalar halfW = SkScalarHalf(w);
1041 SkScalar h = rect.height();
1042 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001043
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001044 if (halfW <= 0 || halfH <= 0) {
1045 return;
1046 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001047
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001048 bool skip_hori = rx >= halfW;
1049 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001050
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001051 if (skip_hori && skip_vert) {
1052 this->addOval(rect, dir);
1053 return;
1054 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001055
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001056 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001057
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001058 SkAutoPathBoundsUpdate apbu(this, rect);
1059 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001060
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001061 if (skip_hori) {
1062 rx = halfW;
1063 } else if (skip_vert) {
1064 ry = halfH;
1065 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001066
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001067 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1068 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001069
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001070 this->incReserve(17);
1071 this->moveTo(rect.fRight - rx, rect.fTop);
1072 if (dir == kCCW_Direction) {
1073 if (!skip_hori) {
1074 this->lineTo(rect.fLeft + rx, rect.fTop); // top
1075 }
1076 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1077 rect.fLeft, rect.fTop + ry - sy,
1078 rect.fLeft, rect.fTop + ry); // top-left
1079 if (!skip_vert) {
1080 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1081 }
1082 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1083 rect.fLeft + rx - sx, rect.fBottom,
1084 rect.fLeft + rx, rect.fBottom); // bot-left
1085 if (!skip_hori) {
1086 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
1087 }
1088 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1089 rect.fRight, rect.fBottom - ry + sy,
1090 rect.fRight, rect.fBottom - ry); // bot-right
1091 if (!skip_vert) {
1092 this->lineTo(rect.fRight, rect.fTop + ry);
1093 }
1094 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1095 rect.fRight - rx + sx, rect.fTop,
1096 rect.fRight - rx, rect.fTop); // top-right
1097 } else {
1098 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1099 rect.fRight, rect.fTop + ry - sy,
1100 rect.fRight, rect.fTop + ry); // top-right
1101 if (!skip_vert) {
1102 this->lineTo(rect.fRight, rect.fBottom - ry);
1103 }
1104 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1105 rect.fRight - rx + sx, rect.fBottom,
1106 rect.fRight - rx, rect.fBottom); // bot-right
1107 if (!skip_hori) {
1108 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1109 }
1110 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1111 rect.fLeft, rect.fBottom - ry + sy,
1112 rect.fLeft, rect.fBottom - ry); // bot-left
1113 if (!skip_vert) {
1114 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1115 }
1116 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1117 rect.fLeft + rx - sx, rect.fTop,
1118 rect.fLeft + rx, rect.fTop); // top-left
1119 if (!skip_hori) {
1120 this->lineTo(rect.fRight - rx, rect.fTop); // top
1121 }
1122 }
1123 this->close();
1124}
1125
reed@android.com8a1c16f2008-12-17 15:59:43 +00001126void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001127 assert_known_direction(dir);
1128
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001129 /* If addOval() is called after previous moveTo(),
1130 this path is still marked as an oval. This is used to
1131 fit into WebKit's calling sequences.
1132 We can't simply check isEmpty() in this case, as additional
1133 moveTo() would mark the path non empty.
1134 */
1135 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001136 if (fIsOval) {
1137 fDirection = dir;
1138 } else {
1139 fDirection = kUnknown_Direction;
1140 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001141
1142 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001143 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001144
reed@android.com8a1c16f2008-12-17 15:59:43 +00001145 SkAutoPathBoundsUpdate apbu(this, oval);
1146
1147 SkScalar cx = oval.centerX();
1148 SkScalar cy = oval.centerY();
1149 SkScalar rx = SkScalarHalf(oval.width());
1150 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001151
reed@android.com8a1c16f2008-12-17 15:59:43 +00001152 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1153 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1154 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1155 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1156
1157 /*
1158 To handle imprecision in computing the center and radii, we revert to
1159 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1160 to ensure that we don't exceed the oval's bounds *ever*, since we want
1161 to use oval for our fast-bounds, rather than have to recompute it.
1162 */
1163 const SkScalar L = oval.fLeft; // cx - rx
1164 const SkScalar T = oval.fTop; // cy - ry
1165 const SkScalar R = oval.fRight; // cx + rx
1166 const SkScalar B = oval.fBottom; // cy + ry
1167
1168 this->incReserve(17); // 8 quads + close
1169 this->moveTo(R, cy);
1170 if (dir == kCCW_Direction) {
1171 this->quadTo( R, cy - sy, cx + mx, cy - my);
1172 this->quadTo(cx + sx, T, cx , T);
1173 this->quadTo(cx - sx, T, cx - mx, cy - my);
1174 this->quadTo( L, cy - sy, L, cy );
1175 this->quadTo( L, cy + sy, cx - mx, cy + my);
1176 this->quadTo(cx - sx, B, cx , B);
1177 this->quadTo(cx + sx, B, cx + mx, cy + my);
1178 this->quadTo( R, cy + sy, R, cy );
1179 } else {
1180 this->quadTo( R, cy + sy, cx + mx, cy + my);
1181 this->quadTo(cx + sx, B, cx , B);
1182 this->quadTo(cx - sx, B, cx - mx, cy + my);
1183 this->quadTo( L, cy + sy, L, cy );
1184 this->quadTo( L, cy - sy, cx - mx, cy - my);
1185 this->quadTo(cx - sx, T, cx , T);
1186 this->quadTo(cx + sx, T, cx + mx, cy - my);
1187 this->quadTo( R, cy - sy, R, cy );
1188 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001189 this->close();
1190}
1191
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001192bool SkPath::isOval(SkRect* rect) const {
1193 if (fIsOval && rect) {
1194 *rect = getBounds();
1195 }
1196
1197 return fIsOval;
1198}
1199
reed@android.com8a1c16f2008-12-17 15:59:43 +00001200void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1201 if (r > 0) {
1202 SkRect rect;
1203 rect.set(x - r, y - r, x + r, y + r);
1204 this->addOval(rect, dir);
1205 }
1206}
1207
1208#include "SkGeometry.h"
1209
1210static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1211 SkScalar sweepAngle,
1212 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001213
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001214 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001215 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1216 // Chrome uses this path to move into and out of ovals. If not
1217 // treated as a special case the moves can distort the oval's
1218 // bounding box (and break the circle special case).
1219 pts[0].set(oval.fRight, oval.centerY());
1220 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001221 } else if (0 == oval.width() && 0 == oval.height()) {
1222 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001223 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001224 // a rect.
1225 // TODO: optimizing the case where only one of width or height is zero
1226 // should also be considered. This case, however, doesn't seem to be
1227 // as common as the single point case.
1228 pts[0].set(oval.fRight, oval.fTop);
1229 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001230 }
1231
reed@android.com8a1c16f2008-12-17 15:59:43 +00001232 SkVector start, stop;
1233
1234 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1235 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1236 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001237
1238 /* If the sweep angle is nearly (but less than) 360, then due to precision
1239 loss in radians-conversion and/or sin/cos, we may end up with coincident
1240 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1241 of drawing a nearly complete circle (good).
1242 e.g. canvas.drawArc(0, 359.99, ...)
1243 -vs- canvas.drawArc(0, 359.9, ...)
1244 We try to detect this edge case, and tweak the stop vector
1245 */
1246 if (start == stop) {
1247 SkScalar sw = SkScalarAbs(sweepAngle);
1248 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1249 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1250 // make a guess at a tiny angle (in radians) to tweak by
1251 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1252 // not sure how much will be enough, so we use a loop
1253 do {
1254 stopRad -= deltaRad;
1255 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1256 } while (start == stop);
1257 }
1258 }
1259
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001261
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1263 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001264
reed@android.com8a1c16f2008-12-17 15:59:43 +00001265 return SkBuildQuadArc(start, stop,
1266 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1267 &matrix, pts);
1268}
1269
1270void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1271 bool forceMoveTo) {
1272 if (oval.width() < 0 || oval.height() < 0) {
1273 return;
1274 }
1275
1276 SkPoint pts[kSkBuildQuadArcStorage];
1277 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1278 SkASSERT((count & 1) == 1);
1279
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001280 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001281 forceMoveTo = true;
1282 }
1283 this->incReserve(count);
1284 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1285 for (int i = 1; i < count; i += 2) {
1286 this->quadTo(pts[i], pts[i+1]);
1287 }
1288}
1289
1290void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1291 SkScalar sweepAngle) {
1292 if (oval.isEmpty() || 0 == sweepAngle) {
1293 return;
1294 }
1295
1296 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1297
1298 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1299 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1300 return;
1301 }
1302
1303 SkPoint pts[kSkBuildQuadArcStorage];
1304 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1305
1306 this->incReserve(count);
1307 this->moveTo(pts[0]);
1308 for (int i = 1; i < count; i += 2) {
1309 this->quadTo(pts[i], pts[i+1]);
1310 }
1311}
1312
1313/*
1314 Need to handle the case when the angle is sharp, and our computed end-points
1315 for the arc go behind pt1 and/or p2...
1316*/
1317void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1318 SkScalar radius) {
1319 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001320
reed@android.com8a1c16f2008-12-17 15:59:43 +00001321 // need to know our prev pt so we can construct tangent vectors
1322 {
1323 SkPoint start;
1324 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001325 // Handle degenerate cases by adding a line to the first point and
1326 // bailing out.
1327 if ((x1 == start.fX && y1 == start.fY) ||
1328 (x1 == x2 && y1 == y2) ||
1329 radius == 0) {
1330 this->lineTo(x1, y1);
1331 return;
1332 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001333 before.setNormalize(x1 - start.fX, y1 - start.fY);
1334 after.setNormalize(x2 - x1, y2 - y1);
1335 }
reed@google.comabf15c12011-01-18 20:35:51 +00001336
reed@android.com8a1c16f2008-12-17 15:59:43 +00001337 SkScalar cosh = SkPoint::DotProduct(before, after);
1338 SkScalar sinh = SkPoint::CrossProduct(before, after);
1339
1340 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001341 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 return;
1343 }
reed@google.comabf15c12011-01-18 20:35:51 +00001344
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1346 if (dist < 0) {
1347 dist = -dist;
1348 }
1349
1350 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1351 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1352 SkRotationDirection arcDir;
1353
1354 // now turn before/after into normals
1355 if (sinh > 0) {
1356 before.rotateCCW();
1357 after.rotateCCW();
1358 arcDir = kCW_SkRotationDirection;
1359 } else {
1360 before.rotateCW();
1361 after.rotateCW();
1362 arcDir = kCCW_SkRotationDirection;
1363 }
1364
1365 SkMatrix matrix;
1366 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001367
reed@android.com8a1c16f2008-12-17 15:59:43 +00001368 matrix.setScale(radius, radius);
1369 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1370 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001371
reed@android.com8a1c16f2008-12-17 15:59:43 +00001372 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001373
reed@android.com8a1c16f2008-12-17 15:59:43 +00001374 this->incReserve(count);
1375 // [xx,yy] == pts[0]
1376 this->lineTo(xx, yy);
1377 for (int i = 1; i < count; i += 2) {
1378 this->quadTo(pts[i], pts[i+1]);
1379 }
1380}
1381
1382///////////////////////////////////////////////////////////////////////////////
1383
1384void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1385 SkMatrix matrix;
1386
1387 matrix.setTranslate(dx, dy);
1388 this->addPath(path, matrix);
1389}
1390
1391void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001392 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001393
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001394 fIsOval = false;
1395
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001396 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001397 SkPoint pts[4];
1398 Verb verb;
1399
1400 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1401
1402 while ((verb = iter.next(pts)) != kDone_Verb) {
1403 switch (verb) {
1404 case kMove_Verb:
1405 proc(matrix, &pts[0], &pts[0], 1);
1406 this->moveTo(pts[0]);
1407 break;
1408 case kLine_Verb:
1409 proc(matrix, &pts[1], &pts[1], 1);
1410 this->lineTo(pts[1]);
1411 break;
1412 case kQuad_Verb:
1413 proc(matrix, &pts[1], &pts[1], 2);
1414 this->quadTo(pts[1], pts[2]);
1415 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001416 case kConic_Verb:
1417 proc(matrix, &pts[1], &pts[1], 2);
1418 this->conicTo(pts[1], pts[2], iter.conicWeight());
1419 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001420 case kCubic_Verb:
1421 proc(matrix, &pts[1], &pts[1], 3);
1422 this->cubicTo(pts[1], pts[2], pts[3]);
1423 break;
1424 case kClose_Verb:
1425 this->close();
1426 break;
1427 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001428 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001429 }
1430 }
1431}
1432
1433///////////////////////////////////////////////////////////////////////////////
1434
reed@google.com277c3f82013-05-31 15:17:50 +00001435static int pts_in_verb(unsigned verb) {
1436 static const uint8_t gPtsInVerb[] = {
1437 1, // kMove
1438 1, // kLine
1439 2, // kQuad
1440 2, // kConic
1441 3, // kCubic
1442 0, // kClose
1443 0 // kDone
1444 };
1445
1446 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1447 return gPtsInVerb[verb];
1448}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001449
1450// ignore the initial moveto, and stop when the 1st contour ends
1451void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001452 int i, vcount = path.fPathRef->countVerbs();
1453 // exit early if the path is empty, or just has a moveTo.
1454 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455 return;
1456 }
1457
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001458 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001459
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001460 fIsOval = false;
1461
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001462 const uint8_t* verbs = path.fPathRef->verbs();
1463 // skip the initial moveTo
1464 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001465 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001467 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001468 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001469 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001470 case kLine_Verb:
1471 this->lineTo(pts[0].fX, pts[0].fY);
1472 break;
1473 case kQuad_Verb:
1474 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1475 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001476 case kConic_Verb:
1477 this->conicTo(pts[0], pts[1], *conicWeight++);
1478 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001479 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001480 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481 break;
1482 case kClose_Verb:
1483 return;
1484 }
reed@google.com277c3f82013-05-31 15:17:50 +00001485 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486 }
1487}
1488
1489// ignore the last point of the 1st contour
1490void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001491 int i, vcount = path.fPathRef->countVerbs();
1492 // exit early if the path is empty, or just has a moveTo.
1493 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001494 return;
1495 }
1496
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001497 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001498
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001499 fIsOval = false;
1500
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001501 const uint8_t* verbs = path.fPathRef->verbs();
1502 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001503 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001504
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001505 SkASSERT(verbs[~0] == kMove_Verb);
1506 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001507 unsigned v = verbs[~i];
1508 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001509 if (n == 0) {
1510 break;
1511 }
1512 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001513 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001514 }
1515
1516 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001517 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 case kLine_Verb:
1519 this->lineTo(pts[-1].fX, pts[-1].fY);
1520 break;
1521 case kQuad_Verb:
1522 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1523 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001524 case kConic_Verb:
1525 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1526 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001527 case kCubic_Verb:
1528 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1529 pts[-3].fX, pts[-3].fY);
1530 break;
1531 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001532 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 break;
1534 }
reed@google.com277c3f82013-05-31 15:17:50 +00001535 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001536 }
1537}
1538
reed@google.com63d73742012-01-10 15:33:12 +00001539void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001540 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001541
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001542 const SkPoint* pts = src.fPathRef->pointsEnd();
1543 // we will iterator through src's verbs backwards
1544 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1545 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001546 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001547
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001548 fIsOval = false;
1549
reed@google.com63d73742012-01-10 15:33:12 +00001550 bool needMove = true;
1551 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001552 while (verbs < verbsEnd) {
1553 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001554 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001555
1556 if (needMove) {
1557 --pts;
1558 this->moveTo(pts->fX, pts->fY);
1559 needMove = false;
1560 }
1561 pts -= n;
1562 switch (v) {
1563 case kMove_Verb:
1564 if (needClose) {
1565 this->close();
1566 needClose = false;
1567 }
1568 needMove = true;
1569 pts += 1; // so we see the point in "if (needMove)" above
1570 break;
1571 case kLine_Verb:
1572 this->lineTo(pts[0]);
1573 break;
1574 case kQuad_Verb:
1575 this->quadTo(pts[1], pts[0]);
1576 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001577 case kConic_Verb:
1578 this->conicTo(pts[1], pts[0], *--conicWeights);
1579 break;
reed@google.com63d73742012-01-10 15:33:12 +00001580 case kCubic_Verb:
1581 this->cubicTo(pts[2], pts[1], pts[0]);
1582 break;
1583 case kClose_Verb:
1584 needClose = true;
1585 break;
1586 default:
1587 SkASSERT(!"unexpected verb");
1588 }
1589 }
1590}
1591
reed@android.com8a1c16f2008-12-17 15:59:43 +00001592///////////////////////////////////////////////////////////////////////////////
1593
1594void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1595 SkMatrix matrix;
1596
1597 matrix.setTranslate(dx, dy);
1598 this->transform(matrix, dst);
1599}
1600
1601#include "SkGeometry.h"
1602
1603static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1604 int level = 2) {
1605 if (--level >= 0) {
1606 SkPoint tmp[5];
1607
1608 SkChopQuadAtHalf(pts, tmp);
1609 subdivide_quad_to(path, &tmp[0], level);
1610 subdivide_quad_to(path, &tmp[2], level);
1611 } else {
1612 path->quadTo(pts[1], pts[2]);
1613 }
1614}
1615
1616static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1617 int level = 2) {
1618 if (--level >= 0) {
1619 SkPoint tmp[7];
1620
1621 SkChopCubicAtHalf(pts, tmp);
1622 subdivide_cubic_to(path, &tmp[0], level);
1623 subdivide_cubic_to(path, &tmp[3], level);
1624 } else {
1625 path->cubicTo(pts[1], pts[2], pts[3]);
1626 }
1627}
1628
1629void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1630 SkDEBUGCODE(this->validate();)
1631 if (dst == NULL) {
1632 dst = (SkPath*)this;
1633 }
1634
tomhudson@google.com8d430182011-06-06 19:11:19 +00001635 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001636 SkPath tmp;
1637 tmp.fFillType = fFillType;
1638
1639 SkPath::Iter iter(*this, false);
1640 SkPoint pts[4];
1641 SkPath::Verb verb;
1642
reed@google.com4a3b7142012-05-16 17:16:46 +00001643 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001644 switch (verb) {
1645 case kMove_Verb:
1646 tmp.moveTo(pts[0]);
1647 break;
1648 case kLine_Verb:
1649 tmp.lineTo(pts[1]);
1650 break;
1651 case kQuad_Verb:
1652 subdivide_quad_to(&tmp, pts);
1653 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001654 case kConic_Verb:
1655 SkASSERT(!"TODO: compute new weight");
1656 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1657 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 case kCubic_Verb:
1659 subdivide_cubic_to(&tmp, pts);
1660 break;
1661 case kClose_Verb:
1662 tmp.close();
1663 break;
1664 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001665 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001666 break;
1667 }
1668 }
1669
1670 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001671 SkPathRef::Editor ed(&dst->fPathRef);
1672 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001673 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001674 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001675 /*
1676 * If we're not in perspective, we can transform all of the points at
1677 * once.
1678 *
1679 * Here we also want to optimize bounds, by noting if the bounds are
1680 * already known, and if so, we just transform those as well and mark
1681 * them as "known", rather than force the transformed path to have to
1682 * recompute them.
1683 *
1684 * Special gotchas if the path is effectively empty (<= 1 point) or
1685 * if it is non-finite. In those cases bounds need to stay empty,
1686 * regardless of the matrix.
1687 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001688 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001689 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001690 if (fIsFinite) {
1691 matrix.mapRect(&dst->fBounds, fBounds);
1692 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1693 dst->fBounds.setEmpty();
1694 }
1695 } else {
1696 dst->fIsFinite = false;
1697 dst->fBounds.setEmpty();
1698 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001699 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001700 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001701 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702 }
1703
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001704 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1705
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001707 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001708 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001709 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001710 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001711
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001712#ifdef SK_BUILD_FOR_ANDROID
1713 if (!matrix.isIdentity()) {
1714 GEN_ID_PTR_INC(dst);
1715 }
1716#endif
1717
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001718 if (kUnknown_Direction == fDirection) {
1719 dst->fDirection = kUnknown_Direction;
1720 } else {
1721 SkScalar det2x2 =
1722 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1723 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1724 if (det2x2 < 0) {
1725 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1726 } else if (det2x2 > 0) {
1727 dst->fDirection = fDirection;
1728 } else {
1729 dst->fDirection = kUnknown_Direction;
1730 }
1731 }
1732
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001733 // It's an oval only if it stays a rect.
1734 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001735
reed@android.com8a1c16f2008-12-17 15:59:43 +00001736 SkDEBUGCODE(dst->validate();)
1737 }
1738}
1739
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740///////////////////////////////////////////////////////////////////////////////
1741///////////////////////////////////////////////////////////////////////////////
1742
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001743enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001744 kEmptyContour_SegmentState, // The current contour is empty. We may be
1745 // starting processing or we may have just
1746 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001747 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1748 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1749 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750};
1751
1752SkPath::Iter::Iter() {
1753#ifdef SK_DEBUG
1754 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001755 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001757 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001758 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001759#endif
1760 // need to init enough to make next() harmlessly return kDone_Verb
1761 fVerbs = NULL;
1762 fVerbStop = NULL;
1763 fNeedClose = false;
1764}
1765
1766SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1767 this->setPath(path, forceClose);
1768}
1769
1770void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001771 fPts = path.fPathRef->points();
1772 fVerbs = path.fPathRef->verbs();
1773 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001774 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001775 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001776 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777 fForceClose = SkToU8(forceClose);
1778 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001779 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780}
1781
1782bool SkPath::Iter::isClosedContour() const {
1783 if (fVerbs == NULL || fVerbs == fVerbStop) {
1784 return false;
1785 }
1786 if (fForceClose) {
1787 return true;
1788 }
1789
1790 const uint8_t* verbs = fVerbs;
1791 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001792
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001793 if (kMove_Verb == *(verbs - 1)) {
1794 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001795 }
1796
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001797 while (verbs > stop) {
1798 // verbs points one beyond the current verb, decrement first.
1799 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800 if (kMove_Verb == v) {
1801 break;
1802 }
1803 if (kClose_Verb == v) {
1804 return true;
1805 }
1806 }
1807 return false;
1808}
1809
1810SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001811 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001812 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001813 // A special case: if both points are NaN, SkPoint::operation== returns
1814 // false, but the iterator expects that they are treated as the same.
1815 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001816 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1817 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001818 return kClose_Verb;
1819 }
1820
reed@google.com9e25dbf2012-05-15 17:05:38 +00001821 pts[0] = fLastPt;
1822 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823 fLastPt = fMoveTo;
1824 fCloseLine = true;
1825 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001826 } else {
1827 pts[0] = fMoveTo;
1828 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001829 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001830}
1831
reed@google.com9e25dbf2012-05-15 17:05:38 +00001832const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001833 if (fSegmentState == kAfterMove_SegmentState) {
1834 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001835 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001836 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001837 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001838 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1839 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001840 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001841 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001842}
1843
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844void SkPath::Iter::consumeDegenerateSegments() {
1845 // We need to step over anything that will not move the current draw point
1846 // forward before the next move is seen
1847 const uint8_t* lastMoveVerb = 0;
1848 const SkPoint* lastMovePt = 0;
1849 SkPoint lastPt = fLastPt;
1850 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001851 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001852 switch (verb) {
1853 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001854 // Keep a record of this most recent move
1855 lastMoveVerb = fVerbs;
1856 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001857 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001858 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001859 fPts++;
1860 break;
1861
1862 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001863 // A close when we are in a segment is always valid except when it
1864 // follows a move which follows a segment.
1865 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001866 return;
1867 }
1868 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001869 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001870 break;
1871
1872 case kLine_Verb:
1873 if (!IsLineDegenerate(lastPt, fPts[0])) {
1874 if (lastMoveVerb) {
1875 fVerbs = lastMoveVerb;
1876 fPts = lastMovePt;
1877 return;
1878 }
1879 return;
1880 }
1881 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001882 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883 fPts++;
1884 break;
1885
reed@google.com277c3f82013-05-31 15:17:50 +00001886 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001887 case kQuad_Verb:
1888 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1889 if (lastMoveVerb) {
1890 fVerbs = lastMoveVerb;
1891 fPts = lastMovePt;
1892 return;
1893 }
1894 return;
1895 }
1896 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001897 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001898 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001899 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001900 break;
1901
1902 case kCubic_Verb:
1903 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1904 if (lastMoveVerb) {
1905 fVerbs = lastMoveVerb;
1906 fPts = lastMovePt;
1907 return;
1908 }
1909 return;
1910 }
1911 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001912 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001913 fPts += 3;
1914 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001915
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001916 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001917 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 }
1919 }
1920}
1921
reed@google.com4a3b7142012-05-16 17:16:46 +00001922SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001923 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001924
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001926 // Close the curve if requested and if there is some curve to close
1927 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001928 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001929 return kLine_Verb;
1930 }
1931 fNeedClose = false;
1932 return kClose_Verb;
1933 }
1934 return kDone_Verb;
1935 }
1936
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001937 // fVerbs is one beyond the current verb, decrement first
1938 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001939 const SkPoint* SK_RESTRICT srcPts = fPts;
1940 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001941
1942 switch (verb) {
1943 case kMove_Verb:
1944 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001945 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 verb = this->autoClose(pts);
1947 if (verb == kClose_Verb) {
1948 fNeedClose = false;
1949 }
1950 return (Verb)verb;
1951 }
1952 if (fVerbs == fVerbStop) { // might be a trailing moveto
1953 return kDone_Verb;
1954 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001955 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001956 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001958 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001959 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 fNeedClose = fForceClose;
1961 break;
1962 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001963 pts[0] = this->cons_moveTo();
1964 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001965 fLastPt = srcPts[0];
1966 fCloseLine = false;
1967 srcPts += 1;
1968 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001969 case kConic_Verb:
1970 fConicWeights += 1;
1971 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001972 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001973 pts[0] = this->cons_moveTo();
1974 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001975 fLastPt = srcPts[1];
1976 srcPts += 2;
1977 break;
1978 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001979 pts[0] = this->cons_moveTo();
1980 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001981 fLastPt = srcPts[2];
1982 srcPts += 3;
1983 break;
1984 case kClose_Verb:
1985 verb = this->autoClose(pts);
1986 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001987 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001988 } else {
1989 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001990 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001991 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001992 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001993 break;
1994 }
1995 fPts = srcPts;
1996 return (Verb)verb;
1997}
1998
1999///////////////////////////////////////////////////////////////////////////////
2000
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002001SkPath::RawIter::RawIter() {
2002#ifdef SK_DEBUG
2003 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00002004 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002005 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
2006#endif
2007 // need to init enough to make next() harmlessly return kDone_Verb
2008 fVerbs = NULL;
2009 fVerbStop = NULL;
2010}
2011
2012SkPath::RawIter::RawIter(const SkPath& path) {
2013 this->setPath(path);
2014}
2015
2016void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002017 fPts = path.fPathRef->points();
2018 fVerbs = path.fPathRef->verbs();
2019 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002020 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002021 fMoveTo.fX = fMoveTo.fY = 0;
2022 fLastPt.fX = fLastPt.fY = 0;
2023}
2024
2025SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002026 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002027 if (fVerbs == fVerbStop) {
2028 return kDone_Verb;
2029 }
2030
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002031 // fVerbs points one beyond next verb so decrement first.
2032 unsigned verb = *(--fVerbs);
2033 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002034
2035 switch (verb) {
2036 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002037 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002038 fMoveTo = srcPts[0];
2039 fLastPt = fMoveTo;
2040 srcPts += 1;
2041 break;
2042 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002043 pts[0] = fLastPt;
2044 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002045 fLastPt = srcPts[0];
2046 srcPts += 1;
2047 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002048 case kConic_Verb:
2049 fConicWeights += 1;
2050 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002051 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002052 pts[0] = fLastPt;
2053 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002054 fLastPt = srcPts[1];
2055 srcPts += 2;
2056 break;
2057 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002058 pts[0] = fLastPt;
2059 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002060 fLastPt = srcPts[2];
2061 srcPts += 3;
2062 break;
2063 case kClose_Verb:
2064 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002065 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002066 break;
2067 }
2068 fPts = srcPts;
2069 return (Verb)verb;
2070}
2071
2072///////////////////////////////////////////////////////////////////////////////
2073
reed@android.com8a1c16f2008-12-17 15:59:43 +00002074/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002075 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002076*/
2077
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002078uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002079 SkDEBUGCODE(this->validate();)
2080
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002081 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002082 const int byteCount = sizeof(int32_t)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002083 + fPathRef->writeSize()
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002084 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002085 return SkAlign4(byteCount);
2086 }
2087
2088 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002089
2090 // Call getBounds() to ensure (as a side-effect) that fBounds
2091 // and fIsFinite are computed.
2092 const SkRect& bounds = this->getBounds();
2093 SkASSERT(!fBoundsIsDirty);
2094
2095 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2096 ((fIsOval & 1) << kIsOval_SerializationShift) |
2097 (fConvexity << kConvexity_SerializationShift) |
2098 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002099 (fSegmentMask << kSegmentMask_SerializationShift) |
2100 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002101
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002102 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002103
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002104 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002105
2106 buffer.write(&bounds, sizeof(bounds));
2107
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002108 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002109 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002110}
2111
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002112uint32_t SkPath::readFromMemory(const void* storage) {
2113 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002114
reed@google.com98b11f12011-09-21 18:40:27 +00002115 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002116 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2117 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2118 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2119 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002120 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002121 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002122
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002123 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002124
2125 buffer.read(&fBounds, sizeof(fBounds));
2126 fBoundsIsDirty = false;
2127
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002128 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002129
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002130 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131
2132 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002133 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002134}
2135
2136///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137
reed@google.com51bbe752013-01-17 22:07:50 +00002138#include "SkString.h"
2139
2140static void append_scalar(SkString* str, SkScalar value) {
2141 SkString tmp;
2142 tmp.printf("%g", value);
2143 if (tmp.contains('.')) {
2144 tmp.appendUnichar('f');
2145 }
2146 str->append(tmp);
2147}
2148
2149static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002150 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002151 str->append(label);
2152 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002153
reed@google.com51bbe752013-01-17 22:07:50 +00002154 const SkScalar* values = &pts[0].fX;
2155 count *= 2;
2156
2157 for (int i = 0; i < count; ++i) {
2158 append_scalar(str, values[i]);
2159 if (i < count - 1) {
2160 str->append(", ");
2161 }
2162 }
reed@google.com277c3f82013-05-31 15:17:50 +00002163 if (conicWeight >= 0) {
2164 str->append(", ");
2165 append_scalar(str, conicWeight);
2166 }
reed@google.com51bbe752013-01-17 22:07:50 +00002167 str->append(");\n");
2168}
2169
reed@android.com8a1c16f2008-12-17 15:59:43 +00002170void SkPath::dump(bool forceClose, const char title[]) const {
2171 Iter iter(*this, forceClose);
2172 SkPoint pts[4];
2173 Verb verb;
2174
2175 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2176 title ? title : "");
2177
reed@google.com51bbe752013-01-17 22:07:50 +00002178 SkString builder;
2179
reed@google.com4a3b7142012-05-16 17:16:46 +00002180 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002181 switch (verb) {
2182 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002183 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002184 break;
2185 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002186 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002187 break;
2188 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002189 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002190 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002191 case kConic_Verb:
2192 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2193 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002194 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002195 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002196 break;
2197 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002198 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002199 break;
2200 default:
2201 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2202 verb = kDone_Verb; // stop the loop
2203 break;
2204 }
2205 }
reed@google.com51bbe752013-01-17 22:07:50 +00002206 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002207}
2208
reed@android.come522ca52009-11-23 20:10:41 +00002209void SkPath::dump() const {
2210 this->dump(false);
2211}
2212
2213#ifdef SK_DEBUG
2214void SkPath::validate() const {
2215 SkASSERT(this != NULL);
2216 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002217
djsollen@google.com077348c2012-10-22 20:23:32 +00002218#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002219 if (!fBoundsIsDirty) {
2220 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002221
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002222 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002223 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002224
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002225 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002226 // if we're empty, fBounds may be empty but translated, so we can't
2227 // necessarily compare to bounds directly
2228 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2229 // be [2, 2, 2, 2]
2230 SkASSERT(bounds.isEmpty());
2231 SkASSERT(fBounds.isEmpty());
2232 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002233 if (bounds.isEmpty()) {
2234 SkASSERT(fBounds.isEmpty());
2235 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002236 if (!fBounds.isEmpty()) {
2237 SkASSERT(fBounds.contains(bounds));
2238 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002239 }
reed@android.come522ca52009-11-23 20:10:41 +00002240 }
2241 }
reed@google.com10296cc2011-09-21 12:29:05 +00002242
2243 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002244 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2245 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2246 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002247 case kLine_Verb:
2248 mask |= kLine_SegmentMask;
2249 break;
2250 case kQuad_Verb:
2251 mask |= kQuad_SegmentMask;
2252 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002253 case kConic_Verb:
2254 mask |= kConic_SegmentMask;
2255 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002256 case kCubic_Verb:
2257 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002258 case kMove_Verb: // these verbs aren't included in the segment mask.
2259 case kClose_Verb:
2260 break;
2261 case kDone_Verb:
2262 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2263 break;
2264 default:
2265 SkDEBUGFAIL("Unknown Verb");
2266 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002267 }
2268 }
2269 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002270#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002271}
djsollen@google.com077348c2012-10-22 20:23:32 +00002272#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002273
reed@google.com04863fa2011-05-15 04:08:24 +00002274///////////////////////////////////////////////////////////////////////////////
2275
reed@google.com0b7b9822011-05-16 12:29:27 +00002276static int sign(SkScalar x) { return x < 0; }
2277#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002278
reed@google.com04863fa2011-05-15 04:08:24 +00002279static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002280 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002281}
2282
2283// only valid for a single contour
2284struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002285 Convexicator()
2286 : fPtCount(0)
2287 , fConvexity(SkPath::kConvex_Convexity)
2288 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002289 fSign = 0;
2290 // warnings
2291 fCurrPt.set(0, 0);
2292 fVec0.set(0, 0);
2293 fVec1.set(0, 0);
2294 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002295
2296 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002297 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002298 }
2299
2300 SkPath::Convexity getConvexity() const { return fConvexity; }
2301
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002302 /** The direction returned is only valid if the path is determined convex */
2303 SkPath::Direction getDirection() const { return fDirection; }
2304
reed@google.com04863fa2011-05-15 04:08:24 +00002305 void addPt(const SkPoint& pt) {
2306 if (SkPath::kConcave_Convexity == fConvexity) {
2307 return;
2308 }
2309
2310 if (0 == fPtCount) {
2311 fCurrPt = pt;
2312 ++fPtCount;
2313 } else {
2314 SkVector vec = pt - fCurrPt;
2315 if (vec.fX || vec.fY) {
2316 fCurrPt = pt;
2317 if (++fPtCount == 2) {
2318 fFirstVec = fVec1 = vec;
2319 } else {
2320 SkASSERT(fPtCount > 2);
2321 this->addVec(vec);
2322 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002323
reed@google.com85b6e392011-05-15 20:25:17 +00002324 int sx = sign(vec.fX);
2325 int sy = sign(vec.fY);
2326 fDx += (sx != fSx);
2327 fDy += (sy != fSy);
2328 fSx = sx;
2329 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002330
reed@google.com85b6e392011-05-15 20:25:17 +00002331 if (fDx > 3 || fDy > 3) {
2332 fConvexity = SkPath::kConcave_Convexity;
2333 }
reed@google.com04863fa2011-05-15 04:08:24 +00002334 }
2335 }
2336 }
2337
2338 void close() {
2339 if (fPtCount > 2) {
2340 this->addVec(fFirstVec);
2341 }
2342 }
2343
2344private:
2345 void addVec(const SkVector& vec) {
2346 SkASSERT(vec.fX || vec.fY);
2347 fVec0 = fVec1;
2348 fVec1 = vec;
2349 int sign = CrossProductSign(fVec0, fVec1);
2350 if (0 == fSign) {
2351 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002352 if (1 == sign) {
2353 fDirection = SkPath::kCW_Direction;
2354 } else if (-1 == sign) {
2355 fDirection = SkPath::kCCW_Direction;
2356 }
reed@google.com04863fa2011-05-15 04:08:24 +00002357 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002358 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002359 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002360 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002361 }
2362 }
2363 }
2364
2365 SkPoint fCurrPt;
2366 SkVector fVec0, fVec1, fFirstVec;
2367 int fPtCount; // non-degenerate points
2368 int fSign;
2369 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002370 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002371 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002372};
2373
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002374SkPath::Convexity SkPath::internalGetConvexity() const {
2375 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002376 SkPoint pts[4];
2377 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002378 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002379
2380 int contourCount = 0;
2381 int count;
2382 Convexicator state;
2383
2384 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2385 switch (verb) {
2386 case kMove_Verb:
2387 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002388 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002389 return kConcave_Convexity;
2390 }
2391 pts[1] = pts[0];
2392 count = 1;
2393 break;
2394 case kLine_Verb: count = 1; break;
2395 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002396 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002397 case kCubic_Verb: count = 3; break;
2398 case kClose_Verb:
2399 state.close();
2400 count = 0;
2401 break;
2402 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002403 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002404 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002405 return kConcave_Convexity;
2406 }
2407
2408 for (int i = 1; i <= count; i++) {
2409 state.addPt(pts[i]);
2410 }
2411 // early exit
2412 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002413 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002414 return kConcave_Convexity;
2415 }
2416 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002417 fConvexity = state.getConvexity();
2418 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2419 fDirection = state.getDirection();
2420 }
2421 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002422}
reed@google.com69a99432012-01-10 18:00:10 +00002423
2424///////////////////////////////////////////////////////////////////////////////
2425
2426class ContourIter {
2427public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002428 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002429
2430 bool done() const { return fDone; }
2431 // if !done() then these may be called
2432 int count() const { return fCurrPtCount; }
2433 const SkPoint* pts() const { return fCurrPt; }
2434 void next();
2435
2436private:
2437 int fCurrPtCount;
2438 const SkPoint* fCurrPt;
2439 const uint8_t* fCurrVerb;
2440 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002441 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002442 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002443 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002444};
2445
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002446ContourIter::ContourIter(const SkPathRef& pathRef) {
2447 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002448 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002449 fCurrPt = pathRef.points();
2450 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002451 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002452 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002453 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002454 this->next();
2455}
2456
2457void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002458 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002459 fDone = true;
2460 }
2461 if (fDone) {
2462 return;
2463 }
2464
2465 // skip pts of prev contour
2466 fCurrPt += fCurrPtCount;
2467
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002468 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002469 int ptCount = 1; // moveTo
2470 const uint8_t* verbs = fCurrVerb;
2471
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002472 for (--verbs; verbs > fStopVerbs; --verbs) {
2473 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002474 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002475 goto CONTOUR_END;
2476 case SkPath::kLine_Verb:
2477 ptCount += 1;
2478 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002479 case SkPath::kConic_Verb:
2480 fCurrConicWeight += 1;
2481 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002482 case SkPath::kQuad_Verb:
2483 ptCount += 2;
2484 break;
2485 case SkPath::kCubic_Verb:
2486 ptCount += 3;
2487 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002488 case SkPath::kClose_Verb:
2489 break;
2490 default:
2491 SkASSERT(!"unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002492 break;
2493 }
2494 }
2495CONTOUR_END:
2496 fCurrPtCount = ptCount;
2497 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002498 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002499}
2500
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002501// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002502static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002503 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2504 // We may get 0 when the above subtracts underflow. We expect this to be
2505 // very rare and lazily promote to double.
2506 if (0 == cross) {
2507 double p0x = SkScalarToDouble(p0.fX);
2508 double p0y = SkScalarToDouble(p0.fY);
2509
2510 double p1x = SkScalarToDouble(p1.fX);
2511 double p1y = SkScalarToDouble(p1.fY);
2512
2513 double p2x = SkScalarToDouble(p2.fX);
2514 double p2y = SkScalarToDouble(p2.fY);
2515
2516 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2517 (p1y - p0y) * (p2x - p0x));
2518
2519 }
2520 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002521}
2522
reed@google.comc1ea60a2012-01-31 15:15:36 +00002523// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002524static int find_max_y(const SkPoint pts[], int count) {
2525 SkASSERT(count > 0);
2526 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002527 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002528 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002529 SkScalar y = pts[i].fY;
2530 if (y > max) {
2531 max = y;
2532 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002533 }
2534 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002535 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002536}
2537
reed@google.comcabaf1d2012-01-11 21:03:05 +00002538static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2539 int i = index;
2540 for (;;) {
2541 i = (i + inc) % n;
2542 if (i == index) { // we wrapped around, so abort
2543 break;
2544 }
2545 if (pts[index] != pts[i]) { // found a different point, success!
2546 break;
2547 }
2548 }
2549 return i;
2550}
2551
reed@google.comc1ea60a2012-01-31 15:15:36 +00002552/**
2553 * Starting at index, and moving forward (incrementing), find the xmin and
2554 * xmax of the contiguous points that have the same Y.
2555 */
2556static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2557 int* maxIndexPtr) {
2558 const SkScalar y = pts[index].fY;
2559 SkScalar min = pts[index].fX;
2560 SkScalar max = min;
2561 int minIndex = index;
2562 int maxIndex = index;
2563 for (int i = index + 1; i < n; ++i) {
2564 if (pts[i].fY != y) {
2565 break;
2566 }
2567 SkScalar x = pts[i].fX;
2568 if (x < min) {
2569 min = x;
2570 minIndex = i;
2571 } else if (x > max) {
2572 max = x;
2573 maxIndex = i;
2574 }
2575 }
2576 *maxIndexPtr = maxIndex;
2577 return minIndex;
2578}
2579
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002580static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002581 if (dir) {
2582 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2583 }
reed@google.comac8543f2012-01-30 20:51:25 +00002584}
2585
reed@google.comc1ea60a2012-01-31 15:15:36 +00002586#if 0
2587#include "SkString.h"
2588#include "../utils/SkParsePath.h"
2589static void dumpPath(const SkPath& path) {
2590 SkString str;
2591 SkParsePath::ToSVGString(path, &str);
2592 SkDebugf("%s\n", str.c_str());
2593}
2594#endif
2595
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002596namespace {
2597// for use with convex_dir_test
2598double mul(double a, double b) { return a * b; }
2599SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2600double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2601SkScalar toScalar(SkScalar a) { return a; }
2602
2603// determines the winding direction of a convex polygon with the precision
2604// of T. CAST_SCALAR casts an SkScalar to T.
2605template <typename T, T (CAST_SCALAR)(SkScalar)>
2606bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2607 // we find the first three points that form a non-degenerate
2608 // triangle. If there are no such points then the path is
2609 // degenerate. The first is always point 0. Now we find the second
2610 // point.
2611 int i = 0;
2612 enum { kX = 0, kY = 1 };
2613 T v0[2];
2614 while (1) {
2615 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2616 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2617 if (v0[kX] || v0[kY]) {
2618 break;
2619 }
2620 if (++i == n - 1) {
2621 return false;
2622 }
2623 }
2624 // now find a third point that is not colinear with the first two
2625 // points and check the orientation of the triangle (which will be
2626 // the same as the orientation of the path).
2627 for (++i; i < n; ++i) {
2628 T v1[2];
2629 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2630 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2631 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2632 if (0 != cross) {
2633 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2634 return true;
2635 }
2636 }
2637 return false;
2638}
2639}
2640
reed@google.comac8543f2012-01-30 20:51:25 +00002641/*
2642 * We loop through all contours, and keep the computed cross-product of the
2643 * contour that contained the global y-max. If we just look at the first
2644 * contour, we may find one that is wound the opposite way (correctly) since
2645 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2646 * that is outer most (or at least has the global y-max) before we can consider
2647 * its cross product.
2648 */
reed@google.com69a99432012-01-10 18:00:10 +00002649bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002650// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002651 // don't want to pay the cost for computing this if it
2652 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002653
2654 if (kUnknown_Direction != fDirection) {
2655 *dir = static_cast<Direction>(fDirection);
2656 return true;
2657 }
reed@google.com69a99432012-01-10 18:00:10 +00002658 const Convexity conv = this->getConvexityOrUnknown();
2659
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002660 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002661
reed@google.comac8543f2012-01-30 20:51:25 +00002662 // initialize with our logical y-min
2663 SkScalar ymax = this->getBounds().fTop;
2664 SkScalar ymaxCross = 0;
2665
reed@google.com69a99432012-01-10 18:00:10 +00002666 for (; !iter.done(); iter.next()) {
2667 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002668 if (n < 3) {
2669 continue;
2670 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002671
reed@google.comcabaf1d2012-01-11 21:03:05 +00002672 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002673 SkScalar cross = 0;
2674 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002675 // We try first at scalar precision, and then again at double
2676 // precision. This is because the vectors computed between distant
2677 // points may lose too much precision.
2678 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002679 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002680 return true;
2681 }
2682 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002683 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002684 return true;
2685 } else {
2686 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002687 }
2688 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002689 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002690 if (pts[index].fY < ymax) {
2691 continue;
2692 }
2693
reed@google.comc1ea60a2012-01-31 15:15:36 +00002694 // If there is more than 1 distinct point at the y-max, we take the
2695 // x-min and x-max of them and just subtract to compute the dir.
2696 if (pts[(index + 1) % n].fY == pts[index].fY) {
2697 int maxIndex;
2698 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002699 if (minIndex == maxIndex) {
2700 goto TRY_CROSSPROD;
2701 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002702 SkASSERT(pts[minIndex].fY == pts[index].fY);
2703 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2704 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2705 // we just subtract the indices, and let that auto-convert to
2706 // SkScalar, since we just want - or + to signal the direction.
2707 cross = minIndex - maxIndex;
2708 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002709 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002710 // Find a next and prev index to use for the cross-product test,
2711 // but we try to find pts that form non-zero vectors from pts[index]
2712 //
2713 // Its possible that we can't find two non-degenerate vectors, so
2714 // we have to guard our search (e.g. all the pts could be in the
2715 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002716
reed@google.comc1ea60a2012-01-31 15:15:36 +00002717 // we pass n - 1 instead of -1 so we don't foul up % operator by
2718 // passing it a negative LH argument.
2719 int prev = find_diff_pt(pts, index, n, n - 1);
2720 if (prev == index) {
2721 // completely degenerate, skip to next contour
2722 continue;
2723 }
2724 int next = find_diff_pt(pts, index, n, 1);
2725 SkASSERT(next != index);
2726 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002727 // if we get a zero and the points are horizontal, then we look at the spread in
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002728 // x-direction. We really should continue to walk away from the degeneracy until
2729 // there is a divergence.
2730 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002731 // construct the subtract so we get the correct Direction below
2732 cross = pts[index].fX - pts[next].fX;
2733 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002734 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002735
reed@google.comac8543f2012-01-30 20:51:25 +00002736 if (cross) {
2737 // record our best guess so far
2738 ymax = pts[index].fY;
2739 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002740 }
reed@google.com69a99432012-01-10 18:00:10 +00002741 }
2742 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002743 if (ymaxCross) {
2744 crossToDir(ymaxCross, dir);
2745 fDirection = *dir;
2746 return true;
2747 } else {
2748 return false;
2749 }
reed@google.comac8543f2012-01-30 20:51:25 +00002750}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002751
2752///////////////////////////////////////////////////////////////////////////////
2753
2754static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2755 SkScalar D, SkScalar t) {
2756 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2757}
2758
2759static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2760 SkScalar t) {
2761 SkScalar A = c3 + 3*(c1 - c2) - c0;
2762 SkScalar B = 3*(c2 - c1 - c1 + c0);
2763 SkScalar C = 3*(c1 - c0);
2764 SkScalar D = c0;
2765 return eval_cubic_coeff(A, B, C, D, t);
2766}
2767
2768/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2769 t value such that cubic(t) = target
2770 */
2771static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2772 SkScalar target, SkScalar* t) {
2773 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2774 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002775
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002776 SkScalar D = c0 - target;
2777 SkScalar A = c3 + 3*(c1 - c2) - c0;
2778 SkScalar B = 3*(c2 - c1 - c1 + c0);
2779 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002780
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002781 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2782 SkScalar minT = 0;
2783 SkScalar maxT = SK_Scalar1;
2784 SkScalar mid;
2785 int i;
2786 for (i = 0; i < 16; i++) {
2787 mid = SkScalarAve(minT, maxT);
2788 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2789 if (delta < 0) {
2790 minT = mid;
2791 delta = -delta;
2792 } else {
2793 maxT = mid;
2794 }
2795 if (delta < TOLERANCE) {
2796 break;
2797 }
2798 }
2799 *t = mid;
2800 return true;
2801}
2802
2803template <size_t N> static void find_minmax(const SkPoint pts[],
2804 SkScalar* minPtr, SkScalar* maxPtr) {
2805 SkScalar min, max;
2806 min = max = pts[0].fX;
2807 for (size_t i = 1; i < N; ++i) {
2808 min = SkMinScalar(min, pts[i].fX);
2809 max = SkMaxScalar(max, pts[i].fX);
2810 }
2811 *minPtr = min;
2812 *maxPtr = max;
2813}
2814
2815static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2816 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002817
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002818 int dir = 1;
2819 if (pts[0].fY > pts[3].fY) {
2820 storage[0] = pts[3];
2821 storage[1] = pts[2];
2822 storage[2] = pts[1];
2823 storage[3] = pts[0];
2824 pts = storage;
2825 dir = -1;
2826 }
2827 if (y < pts[0].fY || y >= pts[3].fY) {
2828 return 0;
2829 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002830
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002831 // quickreject or quickaccept
2832 SkScalar min, max;
2833 find_minmax<4>(pts, &min, &max);
2834 if (x < min) {
2835 return 0;
2836 }
2837 if (x > max) {
2838 return dir;
2839 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002840
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002841 // compute the actual x(t) value
2842 SkScalar t, xt;
2843 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2844 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2845 } else {
2846 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2847 xt = y < mid ? pts[0].fX : pts[3].fX;
2848 }
2849 return xt < x ? dir : 0;
2850}
2851
2852static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2853 SkPoint dst[10];
2854 int n = SkChopCubicAtYExtrema(pts, dst);
2855 int w = 0;
2856 for (int i = 0; i <= n; ++i) {
2857 w += winding_mono_cubic(&dst[i * 3], x, y);
2858 }
2859 return w;
2860}
2861
2862static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2863 SkScalar y0 = pts[0].fY;
2864 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002865
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002866 int dir = 1;
2867 if (y0 > y2) {
2868 SkTSwap(y0, y2);
2869 dir = -1;
2870 }
2871 if (y < y0 || y >= y2) {
2872 return 0;
2873 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002874
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875 // bounds check on X (not required. is it faster?)
2876#if 0
2877 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2878 return 0;
2879 }
2880#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002881
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002882 SkScalar roots[2];
2883 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2884 2 * (pts[1].fY - pts[0].fY),
2885 pts[0].fY - y,
2886 roots);
2887 SkASSERT(n <= 1);
2888 SkScalar xt;
2889 if (0 == n) {
2890 SkScalar mid = SkScalarAve(y0, y2);
2891 // Need [0] and [2] if dir == 1
2892 // and [2] and [0] if dir == -1
2893 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2894 } else {
2895 SkScalar t = roots[0];
2896 SkScalar C = pts[0].fX;
2897 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2898 SkScalar B = 2 * (pts[1].fX - C);
2899 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2900 }
2901 return xt < x ? dir : 0;
2902}
2903
2904static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2905 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2906 if (y0 == y1) {
2907 return true;
2908 }
2909 if (y0 < y1) {
2910 return y1 <= y2;
2911 } else {
2912 return y1 >= y2;
2913 }
2914}
2915
2916static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2917 SkPoint dst[5];
2918 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002919
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002920 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2921 n = SkChopQuadAtYExtrema(pts, dst);
2922 pts = dst;
2923 }
2924 int w = winding_mono_quad(pts, x, y);
2925 if (n > 0) {
2926 w += winding_mono_quad(&pts[2], x, y);
2927 }
2928 return w;
2929}
2930
2931static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2932 SkScalar x0 = pts[0].fX;
2933 SkScalar y0 = pts[0].fY;
2934 SkScalar x1 = pts[1].fX;
2935 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002936
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002937 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002938
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002939 int dir = 1;
2940 if (y0 > y1) {
2941 SkTSwap(y0, y1);
2942 dir = -1;
2943 }
2944 if (y < y0 || y >= y1) {
2945 return 0;
2946 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002947
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002948 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2949 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002950
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002951 if (SkScalarSignAsInt(cross) == dir) {
2952 dir = 0;
2953 }
2954 return dir;
2955}
2956
2957bool SkPath::contains(SkScalar x, SkScalar y) const {
2958 bool isInverse = this->isInverseFillType();
2959 if (this->isEmpty()) {
2960 return isInverse;
2961 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002962
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002963 const SkRect& bounds = this->getBounds();
2964 if (!bounds.contains(x, y)) {
2965 return isInverse;
2966 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002967
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002968 SkPath::Iter iter(*this, true);
2969 bool done = false;
2970 int w = 0;
2971 do {
2972 SkPoint pts[4];
2973 switch (iter.next(pts, false)) {
2974 case SkPath::kMove_Verb:
2975 case SkPath::kClose_Verb:
2976 break;
2977 case SkPath::kLine_Verb:
2978 w += winding_line(pts, x, y);
2979 break;
2980 case SkPath::kQuad_Verb:
2981 w += winding_quad(pts, x, y);
2982 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002983 case SkPath::kConic_Verb:
2984 SkASSERT(0);
2985 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002986 case SkPath::kCubic_Verb:
2987 w += winding_cubic(pts, x, y);
2988 break;
2989 case SkPath::kDone_Verb:
2990 done = true;
2991 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002992 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002993 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002994
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002995 switch (this->getFillType()) {
2996 case SkPath::kEvenOdd_FillType:
2997 case SkPath::kInverseEvenOdd_FillType:
2998 w &= 1;
2999 break;
reed@google.come9bb6232012-07-11 18:56:10 +00003000 default:
3001 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003002 }
3003 return SkToBool(w);
3004}