blob: 88d7c43d6bb7a506fddd0f0f8692b63ff6c96f37 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkPath.h"
djsollen@google.com94e75ee2012-06-08 18:30:46 +000011#include "SkBuffer.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000013#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000014#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000015#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000016
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000017////////////////////////////////////////////////////////////////////////////
18
19#if SK_DEBUG_PATH_REF
20
21SkPath::PathRefDebugRef::PathRefDebugRef(SkPath* owner) : fOwner(owner) {}
22
23SkPath::PathRefDebugRef::PathRefDebugRef(SkPathRef* pr, SkPath* owner)
24: fPathRef(pr)
25, fOwner(owner) {
26 pr->addOwner(owner);
27}
28
29SkPath::PathRefDebugRef::~PathRefDebugRef() {
30 fPathRef->removeOwner(fOwner);
31}
32
33void SkPath::PathRefDebugRef::reset(SkPathRef* ref) {
34 bool diff = (ref != fPathRef.get());
35 if (diff && NULL != fPathRef.get()) {
36 fPathRef.get()->removeOwner(fOwner);
37 }
38 fPathRef.reset(ref);
39 if (diff && NULL != fPathRef.get()) {
40 fPathRef.get()->addOwner(fOwner);
41 }
42}
43
44void SkPath::PathRefDebugRef::swap(SkPath::PathRefDebugRef* other) {
45 if (other->fPathRef.get() != fPathRef.get()) {
46 other->fPathRef->removeOwner(other->fOwner);
47 other->fPathRef->addOwner(fOwner);
48
49 fPathRef->removeOwner(fOwner);
50 fPathRef->addOwner(other->fOwner);
51 }
52
53 fPathRef.swap(&other->fPathRef);
54}
55
56SkPathRef* SkPath::PathRefDebugRef::get() const { return fPathRef.get(); }
57
58SkAutoTUnref<SkPathRef>::BlockRefType *SkPath::PathRefDebugRef::operator->() const {
59 return fPathRef.operator->();
60}
61
62SkPath::PathRefDebugRef::operator SkPathRef*() {
63 return fPathRef.operator SkPathRef *();
64}
65
66#endif
skia.committer@gmail.com7cc7f492012-10-04 02:01:34 +000067
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000068////////////////////////////////////////////////////////////////////////////
69
70
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000071SK_DEFINE_INST_COUNT(SkPath);
72
reed@google.com744faba2012-05-29 19:54:52 +000073// This value is just made-up for now. When count is 4, calling memset was much
74// slower than just writing the loop. This seems odd, and hopefully in the
75// future this we appear to have been a fluke...
76#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
77
reed@android.com8a1c16f2008-12-17 15:59:43 +000078////////////////////////////////////////////////////////////////////////////
79
reed@google.com3563c9e2011-11-14 19:34:57 +000080/**
81 * Path.bounds is defined to be the bounds of all the control points.
82 * If we called bounds.join(r) we would skip r if r was empty, which breaks
83 * our promise. Hence we have a custom joiner that doesn't look at emptiness
84 */
85static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
86 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
87 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
88 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
89 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
90}
91
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000092static bool is_degenerate(const SkPath& path) {
93 SkPath::Iter iter(path, false);
94 SkPoint pts[4];
95 return SkPath::kDone_Verb == iter.next(pts);
96}
97
bsalomon@google.com6aa29652012-04-18 13:29:52 +000098class SkAutoDisableOvalCheck {
99public:
100 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
101 fSaved = fPath->fIsOval;
102 }
103
104 ~SkAutoDisableOvalCheck() {
105 fPath->fIsOval = fSaved;
106 }
107
108private:
109 SkPath* fPath;
110 bool fSaved;
111};
112
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000113class SkAutoDisableDirectionCheck {
114public:
115 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
116 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
117 }
118
119 ~SkAutoDisableDirectionCheck() {
120 fPath->fDirection = fSaved;
121 }
122
123private:
124 SkPath* fPath;
125 SkPath::Direction fSaved;
126};
127
reed@android.com8a1c16f2008-12-17 15:59:43 +0000128/* This guy's constructor/destructor bracket a path editing operation. It is
129 used when we know the bounds of the amount we are going to add to the path
130 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +0000131
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132 It captures some state about the path up front (i.e. if it already has a
133 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +0000134 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +0000135
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000136 It also notes if the path was originally degenerate, and if so, sets
137 isConvex to true. Thus it can only be used if the contour being added is
138 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000139 */
140class SkAutoPathBoundsUpdate {
141public:
142 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
143 this->init(path);
144 }
145
146 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
147 SkScalar right, SkScalar bottom) {
148 fRect.set(left, top, right, bottom);
149 this->init(path);
150 }
reed@google.comabf15c12011-01-18 20:35:51 +0000151
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000153 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +0000155 fPath->fBounds = fRect;
156 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000157 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000158 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +0000159 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +0000160 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000161 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 }
163 }
reed@google.comabf15c12011-01-18 20:35:51 +0000164
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000166 SkPath* fPath;
167 SkRect fRect;
168 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000169 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000170 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000171
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000173 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000175 // Mark the path's bounds as dirty if (1) they are, or (2) the path
176 // is non-finite, and therefore its bounds are not meaningful
177 fDirty = SkToBool(path->fBoundsIsDirty) || !path->fIsFinite;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000178 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000179 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000180 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000181 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182 }
183};
184
reed@google.com0bb18bb2012-07-26 15:20:36 +0000185// Return true if the computed bounds are finite.
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000186static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
187 int count = ref.countPoints();
reed@google.com0bb18bb2012-07-26 15:20:36 +0000188 if (count <= 1) { // we ignore just 1 point (moveto)
189 bounds->setEmpty();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000190 return count ? ref.points()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000191 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000192 return bounds->setBoundsCheck(ref.points(), count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 }
194}
195
196////////////////////////////////////////////////////////////////////////////
197
198/*
199 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000200 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000201 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
202
203 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000204 1. If we encounter degenerate segments, remove them
205 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
206 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
207 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208*/
209
210////////////////////////////////////////////////////////////////////////////
211
reed@google.comd335d1d2012-01-12 18:17:11 +0000212// flag to require a moveTo if we begin with something else, like lineTo etc.
213#define INITIAL_LASTMOVETOINDEX_VALUE ~0
214
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000215SkPath::SkPath()
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000216#if SK_DEBUG_PATH_REF
217 : fPathRef(SkPathRef::CreateEmpty(), this)
218#else
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000219 : fPathRef(SkPathRef::CreateEmpty())
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000220#endif
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000221 , fFillType(kWinding_FillType)
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000222 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000223 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000224 fDirection = kUnknown_Direction;
reed@google.com10296cc2011-09-21 12:29:05 +0000225 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000226 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000227 fIsOval = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000228 fIsFinite = false; // gets computed when we know our bounds
djsollen@google.com56c69772011-11-08 19:00:26 +0000229#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000230 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000231 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000232#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000233}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000235SkPath::SkPath(const SkPath& src)
236#if SK_DEBUG_PATH_REF
237 : fPathRef(this)
238#endif
239{
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 SkDEBUGCODE(src.validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000241 src.fPathRef.get()->ref();
242 fPathRef.reset(src.fPathRef.get());
243 fBounds = src.fBounds;
244 fFillType = src.fFillType;
245 fBoundsIsDirty = src.fBoundsIsDirty;
246 fConvexity = src.fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000247 fDirection = src.fDirection;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000248 fIsFinite = src.fIsFinite;
249 fSegmentMask = src.fSegmentMask;
250 fLastMoveToIndex = src.fLastMoveToIndex;
251 fIsOval = src.fIsOval;
djsollen@google.com56c69772011-11-08 19:00:26 +0000252#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000253 fGenerationID = src.fGenerationID;
254 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000255#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000256}
257
258SkPath::~SkPath() {
259 SkDEBUGCODE(this->validate();)
260}
261
262SkPath& SkPath::operator=(const SkPath& src) {
263 SkDEBUGCODE(src.validate();)
264
265 if (this != &src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000266 src.fPathRef.get()->ref();
267 fPathRef.reset(src.fPathRef.get());
reed@android.comd252db02009-04-01 18:31:44 +0000268 fBounds = src.fBounds;
reed@android.comd252db02009-04-01 18:31:44 +0000269 fFillType = src.fFillType;
270 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000271 fConvexity = src.fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000272 fDirection = src.fDirection;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000273 fIsFinite = src.fIsFinite;
reed@google.com10296cc2011-09-21 12:29:05 +0000274 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000275 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000276 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000277 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000278 }
279 SkDEBUGCODE(this->validate();)
280 return *this;
281}
282
wjmaclean@chromium.org22023be2012-09-06 18:42:03 +0000283SK_API bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000284 // note: don't need to look at isConvex or bounds, since just comparing the
285 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000286
287 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
288 // since it is only a cache of info in the fVerbs, but its a fast way to
289 // notice a difference
290
reed@android.com3abec1d2009-03-02 05:36:20 +0000291 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000292 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000293 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000294}
295
reed@android.com8a1c16f2008-12-17 15:59:43 +0000296void SkPath::swap(SkPath& other) {
297 SkASSERT(&other != NULL);
298
299 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000300 SkTSwap<SkRect>(fBounds, other.fBounds);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000301 fPathRef.swap(&other.fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000302 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000303 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000304 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000305 SkTSwap<uint8_t>(fDirection, other.fDirection);
reed@google.com10296cc2011-09-21 12:29:05 +0000306 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000307 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000308 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
reed@google.com0bb18bb2012-07-26 15:20:36 +0000309 SkTSwap<SkBool8>(fIsFinite, other.fIsFinite);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000310 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311 }
312}
313
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000314static inline bool check_edge_against_rect(const SkPoint& p0,
315 const SkPoint& p1,
316 const SkRect& rect,
317 SkPath::Direction dir) {
318 const SkPoint* edgeBegin;
319 SkVector v;
320 if (SkPath::kCW_Direction == dir) {
321 v = p1 - p0;
322 edgeBegin = &p0;
323 } else {
324 v = p0 - p1;
325 edgeBegin = &p1;
326 }
327 if (v.fX || v.fY) {
328 // check the cross product of v with the vec from edgeBegin to each rect corner
329 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
330 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
331 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
332 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
333 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
334 return false;
335 }
336 }
337 return true;
338}
339
340bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
341 // This only handles non-degenerate convex paths currently.
342 if (kConvex_Convexity != this->getConvexity()) {
343 return false;
344 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000345
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000346 Direction direction;
347 if (!this->cheapComputeDirection(&direction)) {
348 return false;
349 }
350
351 SkPoint firstPt;
352 SkPoint prevPt;
353 RawIter iter(*this);
354 SkPath::Verb verb;
355 SkPoint pts[4];
356 SkDEBUGCODE(int moveCnt = 0;)
357
358 while ((verb = iter.next(pts)) != kDone_Verb) {
359 int nextPt = -1;
360 switch (verb) {
361 case kMove_Verb:
362 SkASSERT(!moveCnt);
363 SkDEBUGCODE(++moveCnt);
364 firstPt = prevPt = pts[0];
365 break;
366 case kLine_Verb:
367 nextPt = 1;
368 SkASSERT(moveCnt);
369 break;
370 case kQuad_Verb:
371 SkASSERT(moveCnt);
372 nextPt = 2;
373 break;
374 case kCubic_Verb:
375 SkASSERT(moveCnt);
376 nextPt = 3;
377 break;
378 case kClose_Verb:
379 break;
380 default:
381 SkDEBUGFAIL("unknown verb");
382 }
383 if (-1 != nextPt) {
384 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
385 return false;
386 }
387 prevPt = pts[nextPt];
388 }
389 }
390
391 return check_edge_against_rect(prevPt, firstPt, rect, direction);
392}
393
djsollen@google.com56c69772011-11-08 19:00:26 +0000394#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000395uint32_t SkPath::getGenerationID() const {
396 return fGenerationID;
397}
djsollen@google.come63793a2012-03-21 15:39:03 +0000398
399const SkPath* SkPath::getSourcePath() const {
400 return fSourcePath;
401}
402
403void SkPath::setSourcePath(const SkPath* path) {
404 fSourcePath = path;
405}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000406#endif
407
reed@android.com8a1c16f2008-12-17 15:59:43 +0000408void SkPath::reset() {
409 SkDEBUGCODE(this->validate();)
410
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000411 fPathRef.reset(SkPathRef::CreateEmpty());
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000412 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000413 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000414 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000415 fDirection = kUnknown_Direction;
reed@google.com10296cc2011-09-21 12:29:05 +0000416 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000417 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000418 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000419}
420
421void SkPath::rewind() {
422 SkDEBUGCODE(this->validate();)
423
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000424 SkPathRef::Rewind(&fPathRef);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000425 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000426 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000427 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000428 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000429 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000430 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431}
432
433bool SkPath::isEmpty() const {
434 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000435 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000436}
437
reed@google.com7e6c4d12012-05-10 14:05:43 +0000438bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000439 int verbCount = fPathRef->countVerbs();
440 int ptCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000441
reed@google.com7e6c4d12012-05-10 14:05:43 +0000442 if (2 == verbCount && 2 == ptCount) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000443 if (kMove_Verb == fPathRef->atVerb(0) &&
444 kLine_Verb == fPathRef->atVerb(1)) {
reed@google.com7e6c4d12012-05-10 14:05:43 +0000445 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000446 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000447 line[0] = pts[0];
448 line[1] = pts[1];
449 }
450 return true;
451 }
452 }
453 return false;
454}
455
caryclark@google.comf1316942011-07-26 19:54:45 +0000456/*
457 Determines if path is a rect by keeping track of changes in direction
458 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000459
caryclark@google.comf1316942011-07-26 19:54:45 +0000460 The direction is computed such that:
461 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000462 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000463 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000464 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000465
caryclark@google.comf1316942011-07-26 19:54:45 +0000466A rectangle cycles up/right/down/left or up/left/down/right.
467
468The test fails if:
469 The path is closed, and followed by a line.
470 A second move creates a new endpoint.
471 A diagonal line is parsed.
472 There's more than four changes of direction.
473 There's a discontinuity on the line (e.g., a move in the middle)
474 The line reverses direction.
475 The rectangle doesn't complete a cycle.
476 The path contains a quadratic or cubic.
477 The path contains fewer than four points.
478 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000479
caryclark@google.comf1316942011-07-26 19:54:45 +0000480It's OK if the path has:
481 Several colinear line segments composing a rectangle side.
482 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000483
caryclark@google.comf1316942011-07-26 19:54:45 +0000484The direction takes advantage of the corners found since opposite sides
485must travel in opposite directions.
486
487FIXME: Allow colinear quads and cubics to be treated like lines.
488FIXME: If the API passes fill-only, return true if the filled stroke
489 is a rectangle, though the caller failed to close the path.
490 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000491bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
492 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000493 int corners = 0;
494 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000495 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000496 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000497 first.set(0, 0);
498 last.set(0, 0);
499 int firstDirection = 0;
500 int lastDirection = 0;
501 int nextDirection = 0;
502 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000503 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000504 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000505 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
506 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000507 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000508 savePts = pts;
509 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000510 autoClose = true;
511 case kLine_Verb: {
512 SkScalar left = last.fX;
513 SkScalar top = last.fY;
514 SkScalar right = pts->fX;
515 SkScalar bottom = pts->fY;
516 ++pts;
517 if (left != right && top != bottom) {
518 return false; // diagonal
519 }
520 if (left == right && top == bottom) {
521 break; // single point on side OK
522 }
523 nextDirection = (left != right) << 0 |
524 (left < right || top < bottom) << 1;
525 if (0 == corners) {
526 firstDirection = nextDirection;
527 first = last;
528 last = pts[-1];
529 corners = 1;
530 closedOrMoved = false;
531 break;
532 }
533 if (closedOrMoved) {
534 return false; // closed followed by a line
535 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000536 if (autoClose && nextDirection == firstDirection) {
537 break; // colinear with first
538 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000539 closedOrMoved = autoClose;
540 if (lastDirection != nextDirection) {
541 if (++corners > 4) {
542 return false; // too many direction changes
543 }
544 }
545 last = pts[-1];
546 if (lastDirection == nextDirection) {
547 break; // colinear segment
548 }
549 // Possible values for corners are 2, 3, and 4.
550 // When corners == 3, nextDirection opposes firstDirection.
551 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000552 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000553 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
554 if ((directionCycle ^ turn) != nextDirection) {
555 return false; // direction didn't follow cycle
556 }
557 break;
558 }
559 case kQuad_Verb:
560 case kCubic_Verb:
561 return false; // quadratic, cubic not allowed
562 case kMove_Verb:
563 last = *pts++;
564 closedOrMoved = true;
565 break;
566 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000567 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000568 lastDirection = nextDirection;
569 }
570 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000571 bool result = 4 == corners && (first == last || autoClose);
572 if (savePts) {
573 *ptsPtr = savePts;
574 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000575 if (result && isClosed) {
576 *isClosed = autoClose;
577 }
578 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000579 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000580 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000581 return result;
582}
583
584bool SkPath::isRect(SkRect* rect) const {
585 SkDEBUGCODE(this->validate();)
586 int currVerb = 0;
587 const SkPoint* pts = fPathRef->points();
caryclark@google.comf68154a2012-11-21 15:18:06 +0000588 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
caryclark@google.comf1316942011-07-26 19:54:45 +0000589 if (result && rect) {
590 *rect = getBounds();
591 }
592 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000593}
594
caryclark@google.comf68154a2012-11-21 15:18:06 +0000595bool SkPath::isRect(bool* isClosed, Direction* direction) const {
596 SkDEBUGCODE(this->validate();)
597 int currVerb = 0;
598 const SkPoint* pts = fPathRef->points();
599 return isRectContour(false, &currVerb, &pts, isClosed, direction);
600}
601
caryclark@google.com56f233a2012-11-19 13:06:06 +0000602bool SkPath::isNestedRects(SkRect rects[2]) const {
603 SkDEBUGCODE(this->validate();)
604 int currVerb = 0;
605 const SkPoint* pts = fPathRef->points();
606 const SkPoint* first = pts;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000607 if (!isRectContour(true, &currVerb, &pts, NULL, NULL)) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000608 return false;
609 }
610 const SkPoint* last = pts;
611 SkRect testRects[2];
caryclark@google.comf68154a2012-11-21 15:18:06 +0000612 if (isRectContour(false, &currVerb, &pts, NULL, NULL)) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000613 testRects[0].set(first, last - first);
614 testRects[1].set(last, pts - last);
615 if (testRects[0].contains(testRects[1])) {
616 if (rects) {
617 rects[0] = testRects[0];
618 rects[1] = testRects[1];
619 }
620 return true;
621 }
622 if (testRects[1].contains(testRects[0])) {
623 if (rects) {
624 rects[0] = testRects[1];
625 rects[1] = testRects[0];
626 }
627 return true;
628 }
629 }
630 return false;
631}
632
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000633int SkPath::countPoints() const {
634 return fPathRef->countPoints();
635}
636
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000637int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 SkDEBUGCODE(this->validate();)
639
640 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000641 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000642 int count = SkMin32(max, fPathRef->countPoints());
643 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
644 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000645}
646
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000647SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000648 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
649 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000650 }
651 return SkPoint::Make(0, 0);
652}
653
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654int SkPath::countVerbs() const {
655 return fPathRef->countVerbs();
656}
657
658static inline void copy_verbs_reverse(uint8_t* inorderDst,
659 const uint8_t* reversedSrc,
660 int count) {
661 for (int i = 0; i < count; ++i) {
662 inorderDst[i] = reversedSrc[~i];
663 }
664}
665
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000666int SkPath::getVerbs(uint8_t dst[], int max) const {
667 SkDEBUGCODE(this->validate();)
668
669 SkASSERT(max >= 0);
670 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000671 int count = SkMin32(max, fPathRef->countVerbs());
672 copy_verbs_reverse(dst, fPathRef->verbs(), count);
673 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000674}
675
reed@google.com294dd7b2011-10-11 11:58:32 +0000676bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000677 SkDEBUGCODE(this->validate();)
678
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000679 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000680 if (count > 0) {
681 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000682 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000684 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000686 if (lastPt) {
687 lastPt->set(0, 0);
688 }
689 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690}
691
692void SkPath::setLastPt(SkScalar x, SkScalar y) {
693 SkDEBUGCODE(this->validate();)
694
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000695 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 if (count == 0) {
697 this->moveTo(x, y);
698 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000699 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000700 SkPathRef::Editor ed(&fPathRef);
701 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000702 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703 }
704}
705
reed@android.comd252db02009-04-01 18:31:44 +0000706void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000707 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000708 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000710 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000711 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000712}
713
reed@google.com04863fa2011-05-15 04:08:24 +0000714void SkPath::setConvexity(Convexity c) {
715 if (fConvexity != c) {
716 fConvexity = c;
717 GEN_ID_INC;
718 }
719}
720
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721//////////////////////////////////////////////////////////////////////////////
722// Construction methods
723
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000724#define DIRTY_AFTER_EDIT \
725 do { \
726 fBoundsIsDirty = true; \
727 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000728 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000729 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000730 } while (0)
731
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000732#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
733 do { \
734 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000735 } while (0)
736
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737void SkPath::incReserve(U16CPU inc) {
738 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000739 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000740 SkDEBUGCODE(this->validate();)
741}
742
743void SkPath::moveTo(SkScalar x, SkScalar y) {
744 SkDEBUGCODE(this->validate();)
745
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000746 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747
reed@google.comd335d1d2012-01-12 18:17:11 +0000748 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000749 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000750
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000751 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000752
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000753 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000754 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000755}
756
757void SkPath::rMoveTo(SkScalar x, SkScalar y) {
758 SkPoint pt;
759 this->getLastPt(&pt);
760 this->moveTo(pt.fX + x, pt.fY + y);
761}
762
reed@google.comd335d1d2012-01-12 18:17:11 +0000763void SkPath::injectMoveToIfNeeded() {
764 if (fLastMoveToIndex < 0) {
765 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000766 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000767 x = y = 0;
768 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000769 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000770 x = pt.fX;
771 y = pt.fY;
772 }
773 this->moveTo(x, y);
774 }
775}
776
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777void SkPath::lineTo(SkScalar x, SkScalar y) {
778 SkDEBUGCODE(this->validate();)
779
reed@google.comd335d1d2012-01-12 18:17:11 +0000780 this->injectMoveToIfNeeded();
781
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000782 SkPathRef::Editor ed(&fPathRef);
783 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000784 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000786 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000787 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788}
789
790void SkPath::rLineTo(SkScalar x, SkScalar y) {
791 SkPoint pt;
792 this->getLastPt(&pt);
793 this->lineTo(pt.fX + x, pt.fY + y);
794}
795
796void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
797 SkDEBUGCODE(this->validate();)
798
reed@google.comd335d1d2012-01-12 18:17:11 +0000799 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000801 SkPathRef::Editor ed(&fPathRef);
802 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803 pts[0].set(x1, y1);
804 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000805 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000807 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000808 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809}
810
811void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
812 SkPoint pt;
813 this->getLastPt(&pt);
814 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
815}
816
817void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
818 SkScalar x3, SkScalar y3) {
819 SkDEBUGCODE(this->validate();)
820
reed@google.comd335d1d2012-01-12 18:17:11 +0000821 this->injectMoveToIfNeeded();
822
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000823 SkPathRef::Editor ed(&fPathRef);
824 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825 pts[0].set(x1, y1);
826 pts[1].set(x2, y2);
827 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000828 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000830 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000831 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832}
833
834void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
835 SkScalar x3, SkScalar y3) {
836 SkPoint pt;
837 this->getLastPt(&pt);
838 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
839 pt.fX + x3, pt.fY + y3);
840}
841
842void SkPath::close() {
843 SkDEBUGCODE(this->validate();)
844
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000845 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000846 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000847 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848 case kLine_Verb:
849 case kQuad_Verb:
850 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000851 case kMove_Verb: {
852 SkPathRef::Editor ed(&fPathRef);
853 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000854 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000855 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000856 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000857 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000858 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859 break;
860 }
861 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000862
863 // signal that we need a moveTo to follow us (unless we're done)
864#if 0
865 if (fLastMoveToIndex >= 0) {
866 fLastMoveToIndex = ~fLastMoveToIndex;
867 }
868#else
869 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
870#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000871}
872
873///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000874
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000875static void assert_known_direction(int dir) {
876 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
877}
878
reed@android.com8a1c16f2008-12-17 15:59:43 +0000879void SkPath::addRect(const SkRect& rect, Direction dir) {
880 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
881}
882
883void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
884 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000885 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000886 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
887 SkAutoDisableDirectionCheck addc(this);
888
reed@android.com8a1c16f2008-12-17 15:59:43 +0000889 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
890
891 this->incReserve(5);
892
893 this->moveTo(left, top);
894 if (dir == kCCW_Direction) {
895 this->lineTo(left, bottom);
896 this->lineTo(right, bottom);
897 this->lineTo(right, top);
898 } else {
899 this->lineTo(right, top);
900 this->lineTo(right, bottom);
901 this->lineTo(left, bottom);
902 }
903 this->close();
904}
905
reed@google.com744faba2012-05-29 19:54:52 +0000906void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
907 SkDEBUGCODE(this->validate();)
908 if (count <= 0) {
909 return;
910 }
911
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000912 SkPathRef::Editor ed(&fPathRef);
913 fLastMoveToIndex = ed.pathRef()->countPoints();
914 uint8_t* vb;
915 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000916 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000917 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000918
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000919 memcpy(p, pts, count * sizeof(SkPoint));
920 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000921 if (count > 1) {
922 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
923 // be 0, the compiler will remove the test/branch entirely.
924 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000925 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000926 } else {
927 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000928 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000929 }
930 }
931 fSegmentMask |= kLine_SegmentMask;
932 }
933 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000934 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000935 }
936
937 GEN_ID_INC;
938 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000939 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000940}
941
reed@android.com8a1c16f2008-12-17 15:59:43 +0000942static void add_corner_arc(SkPath* path, const SkRect& rect,
943 SkScalar rx, SkScalar ry, int startAngle,
944 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000945 // These two asserts are not sufficient, since really we want to know
946 // that the pair of radii (e.g. left and right, or top and bottom) sum
947 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000948 // these conservative asserts.
949 SkASSERT(0 <= rx && rx <= rect.width());
950 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000951
reed@android.com8a1c16f2008-12-17 15:59:43 +0000952 SkRect r;
953 r.set(-rx, -ry, rx, ry);
954
955 switch (startAngle) {
956 case 0:
957 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
958 break;
959 case 90:
960 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
961 break;
962 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
963 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000964 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000965 }
reed@google.comabf15c12011-01-18 20:35:51 +0000966
reed@android.com8a1c16f2008-12-17 15:59:43 +0000967 SkScalar start = SkIntToScalar(startAngle);
968 SkScalar sweep = SkIntToScalar(90);
969 if (SkPath::kCCW_Direction == dir) {
970 start += sweep;
971 sweep = -sweep;
972 }
reed@google.comabf15c12011-01-18 20:35:51 +0000973
reed@android.com8a1c16f2008-12-17 15:59:43 +0000974 path->arcTo(r, start, sweep, forceMoveTo);
975}
976
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000977void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000978 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000979 SkRRect rrect;
980 rrect.setRectRadii(rect, (const SkVector*) radii);
981 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000982}
983
reed@google.com4ed0fb72012-12-12 20:48:18 +0000984void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000985 assert_known_direction(dir);
986
987 if (rrect.isEmpty()) {
988 return;
989 }
990
reed@google.com4ed0fb72012-12-12 20:48:18 +0000991 const SkRect& bounds = rrect.getBounds();
992
993 if (rrect.isRect()) {
994 this->addRect(bounds, dir);
995 } else if (rrect.isOval()) {
996 this->addOval(bounds, dir);
997 } else if (rrect.isSimple()) {
998 const SkVector& rad = rrect.getSimpleRadii();
999 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1000 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001001 SkAutoPathBoundsUpdate apbu(this, bounds);
1002
1003 if (kCW_Direction == dir) {
1004 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1005 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1006 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1007 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1008 } else {
1009 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1010 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1011 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1012 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1013 }
1014 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001015 }
1016}
1017
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001018bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001019 int count = fPathRef->countVerbs();
1020 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1021 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001022 if (*verbs == kLine_Verb ||
1023 *verbs == kQuad_Verb ||
1024 *verbs == kCubic_Verb) {
1025 return false;
1026 }
1027 ++verbs;
1028 }
1029 return true;
1030}
1031
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001032#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
1033
1034void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1035 Direction dir) {
1036 assert_known_direction(dir);
1037
1038 SkScalar w = rect.width();
1039 SkScalar halfW = SkScalarHalf(w);
1040 SkScalar h = rect.height();
1041 SkScalar halfH = SkScalarHalf(h);
1042
1043 if (halfW <= 0 || halfH <= 0) {
1044 return;
1045 }
1046
1047 bool skip_hori = rx >= halfW;
1048 bool skip_vert = ry >= halfH;
1049
1050 if (skip_hori && skip_vert) {
1051 this->addOval(rect, dir);
1052 return;
1053 }
1054
1055 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
1056
1057 SkAutoPathBoundsUpdate apbu(this, rect);
1058 SkAutoDisableDirectionCheck(this);
1059
1060 if (skip_hori) {
1061 rx = halfW;
1062 } else if (skip_vert) {
1063 ry = halfH;
1064 }
1065
1066 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1067 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
1068
1069 this->incReserve(17);
1070 this->moveTo(rect.fRight - rx, rect.fTop);
1071 if (dir == kCCW_Direction) {
1072 if (!skip_hori) {
1073 this->lineTo(rect.fLeft + rx, rect.fTop); // top
1074 }
1075 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1076 rect.fLeft, rect.fTop + ry - sy,
1077 rect.fLeft, rect.fTop + ry); // top-left
1078 if (!skip_vert) {
1079 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1080 }
1081 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1082 rect.fLeft + rx - sx, rect.fBottom,
1083 rect.fLeft + rx, rect.fBottom); // bot-left
1084 if (!skip_hori) {
1085 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
1086 }
1087 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1088 rect.fRight, rect.fBottom - ry + sy,
1089 rect.fRight, rect.fBottom - ry); // bot-right
1090 if (!skip_vert) {
1091 this->lineTo(rect.fRight, rect.fTop + ry);
1092 }
1093 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1094 rect.fRight - rx + sx, rect.fTop,
1095 rect.fRight - rx, rect.fTop); // top-right
1096 } else {
1097 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1098 rect.fRight, rect.fTop + ry - sy,
1099 rect.fRight, rect.fTop + ry); // top-right
1100 if (!skip_vert) {
1101 this->lineTo(rect.fRight, rect.fBottom - ry);
1102 }
1103 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1104 rect.fRight - rx + sx, rect.fBottom,
1105 rect.fRight - rx, rect.fBottom); // bot-right
1106 if (!skip_hori) {
1107 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1108 }
1109 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1110 rect.fLeft, rect.fBottom - ry + sy,
1111 rect.fLeft, rect.fBottom - ry); // bot-left
1112 if (!skip_vert) {
1113 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1114 }
1115 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1116 rect.fLeft + rx - sx, rect.fTop,
1117 rect.fLeft + rx, rect.fTop); // top-left
1118 if (!skip_hori) {
1119 this->lineTo(rect.fRight - rx, rect.fTop); // top
1120 }
1121 }
1122 this->close();
1123}
1124
reed@android.com8a1c16f2008-12-17 15:59:43 +00001125void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001126 assert_known_direction(dir);
1127
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001128 /* If addOval() is called after previous moveTo(),
1129 this path is still marked as an oval. This is used to
1130 fit into WebKit's calling sequences.
1131 We can't simply check isEmpty() in this case, as additional
1132 moveTo() would mark the path non empty.
1133 */
1134 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001135 if (fIsOval) {
1136 fDirection = dir;
1137 } else {
1138 fDirection = kUnknown_Direction;
1139 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001140
1141 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001142 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001143
reed@android.com8a1c16f2008-12-17 15:59:43 +00001144 SkAutoPathBoundsUpdate apbu(this, oval);
1145
1146 SkScalar cx = oval.centerX();
1147 SkScalar cy = oval.centerY();
1148 SkScalar rx = SkScalarHalf(oval.width());
1149 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001150
reed@android.com8a1c16f2008-12-17 15:59:43 +00001151 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1152 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1153 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1154 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1155
1156 /*
1157 To handle imprecision in computing the center and radii, we revert to
1158 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1159 to ensure that we don't exceed the oval's bounds *ever*, since we want
1160 to use oval for our fast-bounds, rather than have to recompute it.
1161 */
1162 const SkScalar L = oval.fLeft; // cx - rx
1163 const SkScalar T = oval.fTop; // cy - ry
1164 const SkScalar R = oval.fRight; // cx + rx
1165 const SkScalar B = oval.fBottom; // cy + ry
1166
1167 this->incReserve(17); // 8 quads + close
1168 this->moveTo(R, cy);
1169 if (dir == kCCW_Direction) {
1170 this->quadTo( R, cy - sy, cx + mx, cy - my);
1171 this->quadTo(cx + sx, T, cx , T);
1172 this->quadTo(cx - sx, T, cx - mx, cy - my);
1173 this->quadTo( L, cy - sy, L, cy );
1174 this->quadTo( L, cy + sy, cx - mx, cy + my);
1175 this->quadTo(cx - sx, B, cx , B);
1176 this->quadTo(cx + sx, B, cx + mx, cy + my);
1177 this->quadTo( R, cy + sy, R, cy );
1178 } else {
1179 this->quadTo( R, cy + sy, cx + mx, cy + my);
1180 this->quadTo(cx + sx, B, cx , B);
1181 this->quadTo(cx - sx, B, cx - mx, cy + my);
1182 this->quadTo( L, cy + sy, L, cy );
1183 this->quadTo( L, cy - sy, cx - mx, cy - my);
1184 this->quadTo(cx - sx, T, cx , T);
1185 this->quadTo(cx + sx, T, cx + mx, cy - my);
1186 this->quadTo( R, cy - sy, R, cy );
1187 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001188 this->close();
1189}
1190
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001191bool SkPath::isOval(SkRect* rect) const {
1192 if (fIsOval && rect) {
1193 *rect = getBounds();
1194 }
1195
1196 return fIsOval;
1197}
1198
reed@android.com8a1c16f2008-12-17 15:59:43 +00001199void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1200 if (r > 0) {
1201 SkRect rect;
1202 rect.set(x - r, y - r, x + r, y + r);
1203 this->addOval(rect, dir);
1204 }
1205}
1206
1207#include "SkGeometry.h"
1208
1209static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1210 SkScalar sweepAngle,
1211 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001212
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001213 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001214 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1215 // Chrome uses this path to move into and out of ovals. If not
1216 // treated as a special case the moves can distort the oval's
1217 // bounding box (and break the circle special case).
1218 pts[0].set(oval.fRight, oval.centerY());
1219 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001220 } else if (0 == oval.width() && 0 == oval.height()) {
1221 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001222 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001223 // a rect.
1224 // TODO: optimizing the case where only one of width or height is zero
1225 // should also be considered. This case, however, doesn't seem to be
1226 // as common as the single point case.
1227 pts[0].set(oval.fRight, oval.fTop);
1228 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001229 }
1230
reed@android.com8a1c16f2008-12-17 15:59:43 +00001231 SkVector start, stop;
1232
1233 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1234 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1235 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001236
1237 /* If the sweep angle is nearly (but less than) 360, then due to precision
1238 loss in radians-conversion and/or sin/cos, we may end up with coincident
1239 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1240 of drawing a nearly complete circle (good).
1241 e.g. canvas.drawArc(0, 359.99, ...)
1242 -vs- canvas.drawArc(0, 359.9, ...)
1243 We try to detect this edge case, and tweak the stop vector
1244 */
1245 if (start == stop) {
1246 SkScalar sw = SkScalarAbs(sweepAngle);
1247 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1248 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1249 // make a guess at a tiny angle (in radians) to tweak by
1250 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1251 // not sure how much will be enough, so we use a loop
1252 do {
1253 stopRad -= deltaRad;
1254 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1255 } while (start == stop);
1256 }
1257 }
1258
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001260
reed@android.com8a1c16f2008-12-17 15:59:43 +00001261 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1262 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001263
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 return SkBuildQuadArc(start, stop,
1265 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1266 &matrix, pts);
1267}
1268
1269void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1270 bool forceMoveTo) {
1271 if (oval.width() < 0 || oval.height() < 0) {
1272 return;
1273 }
1274
1275 SkPoint pts[kSkBuildQuadArcStorage];
1276 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1277 SkASSERT((count & 1) == 1);
1278
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001279 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001280 forceMoveTo = true;
1281 }
1282 this->incReserve(count);
1283 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1284 for (int i = 1; i < count; i += 2) {
1285 this->quadTo(pts[i], pts[i+1]);
1286 }
1287}
1288
1289void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1290 SkScalar sweepAngle) {
1291 if (oval.isEmpty() || 0 == sweepAngle) {
1292 return;
1293 }
1294
1295 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1296
1297 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1298 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1299 return;
1300 }
1301
1302 SkPoint pts[kSkBuildQuadArcStorage];
1303 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1304
1305 this->incReserve(count);
1306 this->moveTo(pts[0]);
1307 for (int i = 1; i < count; i += 2) {
1308 this->quadTo(pts[i], pts[i+1]);
1309 }
1310}
1311
1312/*
1313 Need to handle the case when the angle is sharp, and our computed end-points
1314 for the arc go behind pt1 and/or p2...
1315*/
1316void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1317 SkScalar radius) {
1318 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001319
reed@android.com8a1c16f2008-12-17 15:59:43 +00001320 // need to know our prev pt so we can construct tangent vectors
1321 {
1322 SkPoint start;
1323 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001324 // Handle degenerate cases by adding a line to the first point and
1325 // bailing out.
1326 if ((x1 == start.fX && y1 == start.fY) ||
1327 (x1 == x2 && y1 == y2) ||
1328 radius == 0) {
1329 this->lineTo(x1, y1);
1330 return;
1331 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001332 before.setNormalize(x1 - start.fX, y1 - start.fY);
1333 after.setNormalize(x2 - x1, y2 - y1);
1334 }
reed@google.comabf15c12011-01-18 20:35:51 +00001335
reed@android.com8a1c16f2008-12-17 15:59:43 +00001336 SkScalar cosh = SkPoint::DotProduct(before, after);
1337 SkScalar sinh = SkPoint::CrossProduct(before, after);
1338
1339 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001340 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001341 return;
1342 }
reed@google.comabf15c12011-01-18 20:35:51 +00001343
reed@android.com8a1c16f2008-12-17 15:59:43 +00001344 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1345 if (dist < 0) {
1346 dist = -dist;
1347 }
1348
1349 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1350 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1351 SkRotationDirection arcDir;
1352
1353 // now turn before/after into normals
1354 if (sinh > 0) {
1355 before.rotateCCW();
1356 after.rotateCCW();
1357 arcDir = kCW_SkRotationDirection;
1358 } else {
1359 before.rotateCW();
1360 after.rotateCW();
1361 arcDir = kCCW_SkRotationDirection;
1362 }
1363
1364 SkMatrix matrix;
1365 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001366
reed@android.com8a1c16f2008-12-17 15:59:43 +00001367 matrix.setScale(radius, radius);
1368 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1369 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001370
reed@android.com8a1c16f2008-12-17 15:59:43 +00001371 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001372
reed@android.com8a1c16f2008-12-17 15:59:43 +00001373 this->incReserve(count);
1374 // [xx,yy] == pts[0]
1375 this->lineTo(xx, yy);
1376 for (int i = 1; i < count; i += 2) {
1377 this->quadTo(pts[i], pts[i+1]);
1378 }
1379}
1380
1381///////////////////////////////////////////////////////////////////////////////
1382
1383void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1384 SkMatrix matrix;
1385
1386 matrix.setTranslate(dx, dy);
1387 this->addPath(path, matrix);
1388}
1389
1390void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001391 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001392
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001393 fIsOval = false;
1394
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001395 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001396 SkPoint pts[4];
1397 Verb verb;
1398
1399 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1400
1401 while ((verb = iter.next(pts)) != kDone_Verb) {
1402 switch (verb) {
1403 case kMove_Verb:
1404 proc(matrix, &pts[0], &pts[0], 1);
1405 this->moveTo(pts[0]);
1406 break;
1407 case kLine_Verb:
1408 proc(matrix, &pts[1], &pts[1], 1);
1409 this->lineTo(pts[1]);
1410 break;
1411 case kQuad_Verb:
1412 proc(matrix, &pts[1], &pts[1], 2);
1413 this->quadTo(pts[1], pts[2]);
1414 break;
1415 case kCubic_Verb:
1416 proc(matrix, &pts[1], &pts[1], 3);
1417 this->cubicTo(pts[1], pts[2], pts[3]);
1418 break;
1419 case kClose_Verb:
1420 this->close();
1421 break;
1422 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001423 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001424 }
1425 }
1426}
1427
1428///////////////////////////////////////////////////////////////////////////////
1429
1430static const uint8_t gPtsInVerb[] = {
1431 1, // kMove
1432 1, // kLine
1433 2, // kQuad
1434 3, // kCubic
1435 0, // kClose
1436 0 // kDone
1437};
1438
1439// ignore the initial moveto, and stop when the 1st contour ends
1440void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001441 int i, vcount = path.fPathRef->countVerbs();
1442 // exit early if the path is empty, or just has a moveTo.
1443 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001444 return;
1445 }
1446
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001447 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001448
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001449 fIsOval = false;
1450
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001451 const uint8_t* verbs = path.fPathRef->verbs();
1452 // skip the initial moveTo
1453 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001454
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001455 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001457 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458 case kLine_Verb:
1459 this->lineTo(pts[0].fX, pts[0].fY);
1460 break;
1461 case kQuad_Verb:
1462 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1463 break;
1464 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001465 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 +00001466 break;
1467 case kClose_Verb:
1468 return;
1469 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001470 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471 }
1472}
1473
1474// ignore the last point of the 1st contour
1475void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001476 int i, vcount = path.fPathRef->countVerbs();
1477 // exit early if the path is empty, or just has a moveTo.
1478 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001479 return;
1480 }
1481
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001482 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001484 fIsOval = false;
1485
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001486 const uint8_t* verbs = path.fPathRef->verbs();
1487 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001488
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001489 SkASSERT(verbs[~0] == kMove_Verb);
1490 for (i = 1; i < vcount; ++i) {
1491 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492 if (n == 0) {
1493 break;
1494 }
1495 pts += n;
1496 }
1497
1498 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001499 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001500 case kLine_Verb:
1501 this->lineTo(pts[-1].fX, pts[-1].fY);
1502 break;
1503 case kQuad_Verb:
1504 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1505 break;
1506 case kCubic_Verb:
1507 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1508 pts[-3].fX, pts[-3].fY);
1509 break;
1510 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001511 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001512 break;
1513 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001514 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001515 }
1516}
1517
reed@google.com63d73742012-01-10 15:33:12 +00001518void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001519 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001520
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001521 const SkPoint* pts = src.fPathRef->pointsEnd();
1522 // we will iterator through src's verbs backwards
1523 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1524 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001525
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001526 fIsOval = false;
1527
reed@google.com63d73742012-01-10 15:33:12 +00001528 bool needMove = true;
1529 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001530 while (verbs < verbsEnd) {
1531 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001532 int n = gPtsInVerb[v];
1533
1534 if (needMove) {
1535 --pts;
1536 this->moveTo(pts->fX, pts->fY);
1537 needMove = false;
1538 }
1539 pts -= n;
1540 switch (v) {
1541 case kMove_Verb:
1542 if (needClose) {
1543 this->close();
1544 needClose = false;
1545 }
1546 needMove = true;
1547 pts += 1; // so we see the point in "if (needMove)" above
1548 break;
1549 case kLine_Verb:
1550 this->lineTo(pts[0]);
1551 break;
1552 case kQuad_Verb:
1553 this->quadTo(pts[1], pts[0]);
1554 break;
1555 case kCubic_Verb:
1556 this->cubicTo(pts[2], pts[1], pts[0]);
1557 break;
1558 case kClose_Verb:
1559 needClose = true;
1560 break;
1561 default:
1562 SkASSERT(!"unexpected verb");
1563 }
1564 }
1565}
1566
reed@android.com8a1c16f2008-12-17 15:59:43 +00001567///////////////////////////////////////////////////////////////////////////////
1568
1569void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1570 SkMatrix matrix;
1571
1572 matrix.setTranslate(dx, dy);
1573 this->transform(matrix, dst);
1574}
1575
1576#include "SkGeometry.h"
1577
1578static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1579 int level = 2) {
1580 if (--level >= 0) {
1581 SkPoint tmp[5];
1582
1583 SkChopQuadAtHalf(pts, tmp);
1584 subdivide_quad_to(path, &tmp[0], level);
1585 subdivide_quad_to(path, &tmp[2], level);
1586 } else {
1587 path->quadTo(pts[1], pts[2]);
1588 }
1589}
1590
1591static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1592 int level = 2) {
1593 if (--level >= 0) {
1594 SkPoint tmp[7];
1595
1596 SkChopCubicAtHalf(pts, tmp);
1597 subdivide_cubic_to(path, &tmp[0], level);
1598 subdivide_cubic_to(path, &tmp[3], level);
1599 } else {
1600 path->cubicTo(pts[1], pts[2], pts[3]);
1601 }
1602}
1603
1604void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1605 SkDEBUGCODE(this->validate();)
1606 if (dst == NULL) {
1607 dst = (SkPath*)this;
1608 }
1609
tomhudson@google.com8d430182011-06-06 19:11:19 +00001610 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001611 SkPath tmp;
1612 tmp.fFillType = fFillType;
1613
1614 SkPath::Iter iter(*this, false);
1615 SkPoint pts[4];
1616 SkPath::Verb verb;
1617
reed@google.com4a3b7142012-05-16 17:16:46 +00001618 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001619 switch (verb) {
1620 case kMove_Verb:
1621 tmp.moveTo(pts[0]);
1622 break;
1623 case kLine_Verb:
1624 tmp.lineTo(pts[1]);
1625 break;
1626 case kQuad_Verb:
1627 subdivide_quad_to(&tmp, pts);
1628 break;
1629 case kCubic_Verb:
1630 subdivide_cubic_to(&tmp, pts);
1631 break;
1632 case kClose_Verb:
1633 tmp.close();
1634 break;
1635 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001636 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 break;
1638 }
1639 }
1640
1641 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001642 SkPathRef::Editor ed(&dst->fPathRef);
1643 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001644 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001645 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001646 /*
1647 * If we're not in perspective, we can transform all of the points at
1648 * once.
1649 *
1650 * Here we also want to optimize bounds, by noting if the bounds are
1651 * already known, and if so, we just transform those as well and mark
1652 * them as "known", rather than force the transformed path to have to
1653 * recompute them.
1654 *
1655 * Special gotchas if the path is effectively empty (<= 1 point) or
1656 * if it is non-finite. In those cases bounds need to stay empty,
1657 * regardless of the matrix.
1658 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001659 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001660 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001661 if (fIsFinite) {
1662 matrix.mapRect(&dst->fBounds, fBounds);
1663 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1664 dst->fBounds.setEmpty();
1665 }
1666 } else {
1667 dst->fIsFinite = false;
1668 dst->fBounds.setEmpty();
1669 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001670 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001671 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001672 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001673 }
1674
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001675 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1676
reed@android.com8a1c16f2008-12-17 15:59:43 +00001677 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001678 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001679 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001680 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001681 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001682
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001683 if (kUnknown_Direction == fDirection) {
1684 dst->fDirection = kUnknown_Direction;
1685 } else {
1686 SkScalar det2x2 =
1687 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1688 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1689 if (det2x2 < 0) {
1690 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1691 } else if (det2x2 > 0) {
1692 dst->fDirection = fDirection;
1693 } else {
1694 dst->fDirection = kUnknown_Direction;
1695 }
1696 }
1697
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001698 // It's an oval only if it stays a rect.
1699 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001700
reed@android.com8a1c16f2008-12-17 15:59:43 +00001701 SkDEBUGCODE(dst->validate();)
1702 }
1703}
1704
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705///////////////////////////////////////////////////////////////////////////////
1706///////////////////////////////////////////////////////////////////////////////
1707
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001708enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001709 kEmptyContour_SegmentState, // The current contour is empty. We may be
1710 // starting processing or we may have just
1711 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001712 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1713 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1714 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001715};
1716
1717SkPath::Iter::Iter() {
1718#ifdef SK_DEBUG
1719 fPts = NULL;
1720 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001721 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001722 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001723#endif
1724 // need to init enough to make next() harmlessly return kDone_Verb
1725 fVerbs = NULL;
1726 fVerbStop = NULL;
1727 fNeedClose = false;
1728}
1729
1730SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1731 this->setPath(path, forceClose);
1732}
1733
1734void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001735 fPts = path.fPathRef->points();
1736 fVerbs = path.fPathRef->verbs();
1737 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001738 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001739 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 fForceClose = SkToU8(forceClose);
1741 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001742 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743}
1744
1745bool SkPath::Iter::isClosedContour() const {
1746 if (fVerbs == NULL || fVerbs == fVerbStop) {
1747 return false;
1748 }
1749 if (fForceClose) {
1750 return true;
1751 }
1752
1753 const uint8_t* verbs = fVerbs;
1754 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001755
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001756 if (kMove_Verb == *(verbs - 1)) {
1757 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 }
1759
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001760 while (verbs > stop) {
1761 // verbs points one beyond the current verb, decrement first.
1762 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763 if (kMove_Verb == v) {
1764 break;
1765 }
1766 if (kClose_Verb == v) {
1767 return true;
1768 }
1769 }
1770 return false;
1771}
1772
1773SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001774 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001775 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001776 // A special case: if both points are NaN, SkPoint::operation== returns
1777 // false, but the iterator expects that they are treated as the same.
1778 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001779 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1780 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001781 return kClose_Verb;
1782 }
1783
reed@google.com9e25dbf2012-05-15 17:05:38 +00001784 pts[0] = fLastPt;
1785 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786 fLastPt = fMoveTo;
1787 fCloseLine = true;
1788 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001789 } else {
1790 pts[0] = fMoveTo;
1791 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793}
1794
reed@google.com9e25dbf2012-05-15 17:05:38 +00001795const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001796 if (fSegmentState == kAfterMove_SegmentState) {
1797 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001798 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001799 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001800 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001801 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1802 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001803 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805}
1806
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001807void SkPath::Iter::consumeDegenerateSegments() {
1808 // We need to step over anything that will not move the current draw point
1809 // forward before the next move is seen
1810 const uint8_t* lastMoveVerb = 0;
1811 const SkPoint* lastMovePt = 0;
1812 SkPoint lastPt = fLastPt;
1813 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001814 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001815 switch (verb) {
1816 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001817 // Keep a record of this most recent move
1818 lastMoveVerb = fVerbs;
1819 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001820 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001821 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001822 fPts++;
1823 break;
1824
1825 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001826 // A close when we are in a segment is always valid except when it
1827 // follows a move which follows a segment.
1828 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001829 return;
1830 }
1831 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001832 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001833 break;
1834
1835 case kLine_Verb:
1836 if (!IsLineDegenerate(lastPt, fPts[0])) {
1837 if (lastMoveVerb) {
1838 fVerbs = lastMoveVerb;
1839 fPts = lastMovePt;
1840 return;
1841 }
1842 return;
1843 }
1844 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001845 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001846 fPts++;
1847 break;
1848
1849 case kQuad_Verb:
1850 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1851 if (lastMoveVerb) {
1852 fVerbs = lastMoveVerb;
1853 fPts = lastMovePt;
1854 return;
1855 }
1856 return;
1857 }
1858 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001859 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001860 fPts += 2;
1861 break;
1862
1863 case kCubic_Verb:
1864 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1865 if (lastMoveVerb) {
1866 fVerbs = lastMoveVerb;
1867 fPts = lastMovePt;
1868 return;
1869 }
1870 return;
1871 }
1872 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001873 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874 fPts += 3;
1875 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001876
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001877 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001878 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001879 }
1880 }
1881}
1882
reed@google.com4a3b7142012-05-16 17:16:46 +00001883SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001884 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001885
reed@android.com8a1c16f2008-12-17 15:59:43 +00001886 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001887 // Close the curve if requested and if there is some curve to close
1888 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001889 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001890 return kLine_Verb;
1891 }
1892 fNeedClose = false;
1893 return kClose_Verb;
1894 }
1895 return kDone_Verb;
1896 }
1897
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001898 // fVerbs is one beyond the current verb, decrement first
1899 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001900 const SkPoint* SK_RESTRICT srcPts = fPts;
1901 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001902
1903 switch (verb) {
1904 case kMove_Verb:
1905 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001906 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001907 verb = this->autoClose(pts);
1908 if (verb == kClose_Verb) {
1909 fNeedClose = false;
1910 }
1911 return (Verb)verb;
1912 }
1913 if (fVerbs == fVerbStop) { // might be a trailing moveto
1914 return kDone_Verb;
1915 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001916 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001917 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001918 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001919 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001920 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001921 fNeedClose = fForceClose;
1922 break;
1923 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001924 pts[0] = this->cons_moveTo();
1925 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001926 fLastPt = srcPts[0];
1927 fCloseLine = false;
1928 srcPts += 1;
1929 break;
1930 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001931 pts[0] = this->cons_moveTo();
1932 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933 fLastPt = srcPts[1];
1934 srcPts += 2;
1935 break;
1936 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001937 pts[0] = this->cons_moveTo();
1938 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001939 fLastPt = srcPts[2];
1940 srcPts += 3;
1941 break;
1942 case kClose_Verb:
1943 verb = this->autoClose(pts);
1944 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001945 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 } else {
1947 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001948 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001950 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 break;
1952 }
1953 fPts = srcPts;
1954 return (Verb)verb;
1955}
1956
1957///////////////////////////////////////////////////////////////////////////////
1958
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001959SkPath::RawIter::RawIter() {
1960#ifdef SK_DEBUG
1961 fPts = NULL;
1962 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1963#endif
1964 // need to init enough to make next() harmlessly return kDone_Verb
1965 fVerbs = NULL;
1966 fVerbStop = NULL;
1967}
1968
1969SkPath::RawIter::RawIter(const SkPath& path) {
1970 this->setPath(path);
1971}
1972
1973void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001974 fPts = path.fPathRef->points();
1975 fVerbs = path.fPathRef->verbs();
1976 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001977 fMoveTo.fX = fMoveTo.fY = 0;
1978 fLastPt.fX = fLastPt.fY = 0;
1979}
1980
1981SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001982 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001983 if (fVerbs == fVerbStop) {
1984 return kDone_Verb;
1985 }
1986
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001987 // fVerbs points one beyond next verb so decrement first.
1988 unsigned verb = *(--fVerbs);
1989 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001990
1991 switch (verb) {
1992 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001993 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001994 fMoveTo = srcPts[0];
1995 fLastPt = fMoveTo;
1996 srcPts += 1;
1997 break;
1998 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001999 pts[0] = fLastPt;
2000 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002001 fLastPt = srcPts[0];
2002 srcPts += 1;
2003 break;
2004 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002005 pts[0] = fLastPt;
2006 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002007 fLastPt = srcPts[1];
2008 srcPts += 2;
2009 break;
2010 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002011 pts[0] = fLastPt;
2012 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002013 fLastPt = srcPts[2];
2014 srcPts += 3;
2015 break;
2016 case kClose_Verb:
2017 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002018 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002019 break;
2020 }
2021 fPts = srcPts;
2022 return (Verb)verb;
2023}
2024
2025///////////////////////////////////////////////////////////////////////////////
2026
reed@android.com8a1c16f2008-12-17 15:59:43 +00002027/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002028 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002029*/
2030
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002031uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002032 SkDEBUGCODE(this->validate();)
2033
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002034 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002035 const int byteCount = sizeof(int32_t)
2036#if NEW_PICTURE_FORMAT
2037 + fPathRef->writeSize()
2038#else
2039 + 2 * sizeof(int32_t)
2040 + sizeof(SkPoint) * fPathRef->countPoints()
2041 + sizeof(uint8_t) * fPathRef->countVerbs()
2042#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002043 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002044 return SkAlign4(byteCount);
2045 }
2046
2047 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002048#if !NEW_PICTURE_FORMAT
2049 buffer.write32(fPathRef->countPoints());
2050 buffer.write32(fPathRef->countVerbs());
2051#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002052
2053 // Call getBounds() to ensure (as a side-effect) that fBounds
2054 // and fIsFinite are computed.
2055 const SkRect& bounds = this->getBounds();
2056 SkASSERT(!fBoundsIsDirty);
2057
2058 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2059 ((fIsOval & 1) << kIsOval_SerializationShift) |
2060 (fConvexity << kConvexity_SerializationShift) |
2061 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002062 (fSegmentMask << kSegmentMask_SerializationShift) |
2063 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002064
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002065 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002066
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002067 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002068
2069 buffer.write(&bounds, sizeof(bounds));
2070
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002071 buffer.padToAlign4();
2072 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002073}
2074
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002075uint32_t SkPath::readFromMemory(const void* storage) {
2076 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002077#if !NEW_PICTURE_FORMAT
2078 int32_t pcount = buffer.readS32();
2079 int32_t vcount = buffer.readS32();
2080#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002081
reed@google.com98b11f12011-09-21 18:40:27 +00002082 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002083 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2084 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2085 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2086 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002087 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
2088 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002089
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002090#if NEW_PICTURE_FORMAT
2091 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
2092#else
2093 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
2094#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002095
2096 buffer.read(&fBounds, sizeof(fBounds));
2097 fBoundsIsDirty = false;
2098
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002099 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002100
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002101 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002102
2103 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002104 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105}
2106
2107///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002108
reed@android.com8a1c16f2008-12-17 15:59:43 +00002109void SkPath::dump(bool forceClose, const char title[]) const {
2110 Iter iter(*this, forceClose);
2111 SkPoint pts[4];
2112 Verb verb;
2113
2114 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2115 title ? title : "");
2116
reed@google.com4a3b7142012-05-16 17:16:46 +00002117 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118 switch (verb) {
2119 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120 SkDebugf(" path: moveTo [%g %g]\n",
2121 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002122 break;
2123 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002124 SkDebugf(" path: lineTo [%g %g]\n",
2125 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 break;
2127 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002128 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
2129 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2130 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 break;
2132 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
2134 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2135 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
2136 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137 break;
2138 case kClose_Verb:
2139 SkDebugf(" path: close\n");
2140 break;
2141 default:
2142 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2143 verb = kDone_Verb; // stop the loop
2144 break;
2145 }
2146 }
2147 SkDebugf("path: done %s\n", title ? title : "");
2148}
2149
reed@android.come522ca52009-11-23 20:10:41 +00002150void SkPath::dump() const {
2151 this->dump(false);
2152}
2153
2154#ifdef SK_DEBUG
2155void SkPath::validate() const {
2156 SkASSERT(this != NULL);
2157 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002158
djsollen@google.com077348c2012-10-22 20:23:32 +00002159#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002160 if (!fBoundsIsDirty) {
2161 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002162
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002163 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002164 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002165
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002166 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002167 // if we're empty, fBounds may be empty but translated, so we can't
2168 // necessarily compare to bounds directly
2169 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2170 // be [2, 2, 2, 2]
2171 SkASSERT(bounds.isEmpty());
2172 SkASSERT(fBounds.isEmpty());
2173 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002174 if (bounds.isEmpty()) {
2175 SkASSERT(fBounds.isEmpty());
2176 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002177 if (!fBounds.isEmpty()) {
2178 SkASSERT(fBounds.contains(bounds));
2179 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002180 }
reed@android.come522ca52009-11-23 20:10:41 +00002181 }
2182 }
reed@google.com10296cc2011-09-21 12:29:05 +00002183
2184 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002185 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2186 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2187 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002188 case kLine_Verb:
2189 mask |= kLine_SegmentMask;
2190 break;
2191 case kQuad_Verb:
2192 mask |= kQuad_SegmentMask;
2193 break;
2194 case kCubic_Verb:
2195 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002196 case kMove_Verb: // these verbs aren't included in the segment mask.
2197 case kClose_Verb:
2198 break;
2199 case kDone_Verb:
2200 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2201 break;
2202 default:
2203 SkDEBUGFAIL("Unknown Verb");
2204 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002205 }
2206 }
2207 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002208#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002209}
djsollen@google.com077348c2012-10-22 20:23:32 +00002210#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002211
reed@google.com04863fa2011-05-15 04:08:24 +00002212///////////////////////////////////////////////////////////////////////////////
2213
reed@google.com0b7b9822011-05-16 12:29:27 +00002214static int sign(SkScalar x) { return x < 0; }
2215#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002216
reed@google.com04863fa2011-05-15 04:08:24 +00002217static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002218 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002219}
2220
2221// only valid for a single contour
2222struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002223 Convexicator()
2224 : fPtCount(0)
2225 , fConvexity(SkPath::kConvex_Convexity)
2226 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002227 fSign = 0;
2228 // warnings
2229 fCurrPt.set(0, 0);
2230 fVec0.set(0, 0);
2231 fVec1.set(0, 0);
2232 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002233
2234 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002235 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002236 }
2237
2238 SkPath::Convexity getConvexity() const { return fConvexity; }
2239
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002240 /** The direction returned is only valid if the path is determined convex */
2241 SkPath::Direction getDirection() const { return fDirection; }
2242
reed@google.com04863fa2011-05-15 04:08:24 +00002243 void addPt(const SkPoint& pt) {
2244 if (SkPath::kConcave_Convexity == fConvexity) {
2245 return;
2246 }
2247
2248 if (0 == fPtCount) {
2249 fCurrPt = pt;
2250 ++fPtCount;
2251 } else {
2252 SkVector vec = pt - fCurrPt;
2253 if (vec.fX || vec.fY) {
2254 fCurrPt = pt;
2255 if (++fPtCount == 2) {
2256 fFirstVec = fVec1 = vec;
2257 } else {
2258 SkASSERT(fPtCount > 2);
2259 this->addVec(vec);
2260 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002261
reed@google.com85b6e392011-05-15 20:25:17 +00002262 int sx = sign(vec.fX);
2263 int sy = sign(vec.fY);
2264 fDx += (sx != fSx);
2265 fDy += (sy != fSy);
2266 fSx = sx;
2267 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002268
reed@google.com85b6e392011-05-15 20:25:17 +00002269 if (fDx > 3 || fDy > 3) {
2270 fConvexity = SkPath::kConcave_Convexity;
2271 }
reed@google.com04863fa2011-05-15 04:08:24 +00002272 }
2273 }
2274 }
2275
2276 void close() {
2277 if (fPtCount > 2) {
2278 this->addVec(fFirstVec);
2279 }
2280 }
2281
2282private:
2283 void addVec(const SkVector& vec) {
2284 SkASSERT(vec.fX || vec.fY);
2285 fVec0 = fVec1;
2286 fVec1 = vec;
2287 int sign = CrossProductSign(fVec0, fVec1);
2288 if (0 == fSign) {
2289 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002290 if (1 == sign) {
2291 fDirection = SkPath::kCW_Direction;
2292 } else if (-1 == sign) {
2293 fDirection = SkPath::kCCW_Direction;
2294 }
reed@google.com04863fa2011-05-15 04:08:24 +00002295 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002296 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002297 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002298 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002299 }
2300 }
2301 }
2302
2303 SkPoint fCurrPt;
2304 SkVector fVec0, fVec1, fFirstVec;
2305 int fPtCount; // non-degenerate points
2306 int fSign;
2307 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002308 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002309 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002310};
2311
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002312SkPath::Convexity SkPath::internalGetConvexity() const {
2313 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002314 SkPoint pts[4];
2315 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002316 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002317
2318 int contourCount = 0;
2319 int count;
2320 Convexicator state;
2321
2322 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2323 switch (verb) {
2324 case kMove_Verb:
2325 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002326 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002327 return kConcave_Convexity;
2328 }
2329 pts[1] = pts[0];
2330 count = 1;
2331 break;
2332 case kLine_Verb: count = 1; break;
2333 case kQuad_Verb: count = 2; break;
2334 case kCubic_Verb: count = 3; break;
2335 case kClose_Verb:
2336 state.close();
2337 count = 0;
2338 break;
2339 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002340 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002341 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002342 return kConcave_Convexity;
2343 }
2344
2345 for (int i = 1; i <= count; i++) {
2346 state.addPt(pts[i]);
2347 }
2348 // early exit
2349 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002350 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002351 return kConcave_Convexity;
2352 }
2353 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002354 fConvexity = state.getConvexity();
2355 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2356 fDirection = state.getDirection();
2357 }
2358 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002359}
reed@google.com69a99432012-01-10 18:00:10 +00002360
2361///////////////////////////////////////////////////////////////////////////////
2362
2363class ContourIter {
2364public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002365 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002366
2367 bool done() const { return fDone; }
2368 // if !done() then these may be called
2369 int count() const { return fCurrPtCount; }
2370 const SkPoint* pts() const { return fCurrPt; }
2371 void next();
2372
2373private:
2374 int fCurrPtCount;
2375 const SkPoint* fCurrPt;
2376 const uint8_t* fCurrVerb;
2377 const uint8_t* fStopVerbs;
2378 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002379 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002380};
2381
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002382ContourIter::ContourIter(const SkPathRef& pathRef) {
2383 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002384 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002385 fCurrPt = pathRef.points();
2386 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002387 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002388 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002389 this->next();
2390}
2391
2392void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002393 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002394 fDone = true;
2395 }
2396 if (fDone) {
2397 return;
2398 }
2399
2400 // skip pts of prev contour
2401 fCurrPt += fCurrPtCount;
2402
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002403 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002404 int ptCount = 1; // moveTo
2405 const uint8_t* verbs = fCurrVerb;
2406
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002407 for (--verbs; verbs > fStopVerbs; --verbs) {
2408 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002409 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002410 goto CONTOUR_END;
2411 case SkPath::kLine_Verb:
2412 ptCount += 1;
2413 break;
2414 case SkPath::kQuad_Verb:
2415 ptCount += 2;
2416 break;
2417 case SkPath::kCubic_Verb:
2418 ptCount += 3;
2419 break;
2420 default: // kClose_Verb, just keep going
2421 break;
2422 }
2423 }
2424CONTOUR_END:
2425 fCurrPtCount = ptCount;
2426 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002427 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002428}
2429
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002430// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002431static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002432 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2433 // We may get 0 when the above subtracts underflow. We expect this to be
2434 // very rare and lazily promote to double.
2435 if (0 == cross) {
2436 double p0x = SkScalarToDouble(p0.fX);
2437 double p0y = SkScalarToDouble(p0.fY);
2438
2439 double p1x = SkScalarToDouble(p1.fX);
2440 double p1y = SkScalarToDouble(p1.fY);
2441
2442 double p2x = SkScalarToDouble(p2.fX);
2443 double p2y = SkScalarToDouble(p2.fY);
2444
2445 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2446 (p1y - p0y) * (p2x - p0x));
2447
2448 }
2449 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002450}
2451
reed@google.comc1ea60a2012-01-31 15:15:36 +00002452// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002453static int find_max_y(const SkPoint pts[], int count) {
2454 SkASSERT(count > 0);
2455 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002456 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002457 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002458 SkScalar y = pts[i].fY;
2459 if (y > max) {
2460 max = y;
2461 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002462 }
2463 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002464 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002465}
2466
reed@google.comcabaf1d2012-01-11 21:03:05 +00002467static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2468 int i = index;
2469 for (;;) {
2470 i = (i + inc) % n;
2471 if (i == index) { // we wrapped around, so abort
2472 break;
2473 }
2474 if (pts[index] != pts[i]) { // found a different point, success!
2475 break;
2476 }
2477 }
2478 return i;
2479}
2480
reed@google.comc1ea60a2012-01-31 15:15:36 +00002481/**
2482 * Starting at index, and moving forward (incrementing), find the xmin and
2483 * xmax of the contiguous points that have the same Y.
2484 */
2485static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2486 int* maxIndexPtr) {
2487 const SkScalar y = pts[index].fY;
2488 SkScalar min = pts[index].fX;
2489 SkScalar max = min;
2490 int minIndex = index;
2491 int maxIndex = index;
2492 for (int i = index + 1; i < n; ++i) {
2493 if (pts[i].fY != y) {
2494 break;
2495 }
2496 SkScalar x = pts[i].fX;
2497 if (x < min) {
2498 min = x;
2499 minIndex = i;
2500 } else if (x > max) {
2501 max = x;
2502 maxIndex = i;
2503 }
2504 }
2505 *maxIndexPtr = maxIndex;
2506 return minIndex;
2507}
2508
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002509static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002510 if (dir) {
2511 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2512 }
reed@google.comac8543f2012-01-30 20:51:25 +00002513}
2514
reed@google.comc1ea60a2012-01-31 15:15:36 +00002515#if 0
2516#include "SkString.h"
2517#include "../utils/SkParsePath.h"
2518static void dumpPath(const SkPath& path) {
2519 SkString str;
2520 SkParsePath::ToSVGString(path, &str);
2521 SkDebugf("%s\n", str.c_str());
2522}
2523#endif
2524
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002525namespace {
2526// for use with convex_dir_test
2527double mul(double a, double b) { return a * b; }
2528SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2529double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2530SkScalar toScalar(SkScalar a) { return a; }
2531
2532// determines the winding direction of a convex polygon with the precision
2533// of T. CAST_SCALAR casts an SkScalar to T.
2534template <typename T, T (CAST_SCALAR)(SkScalar)>
2535bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2536 // we find the first three points that form a non-degenerate
2537 // triangle. If there are no such points then the path is
2538 // degenerate. The first is always point 0. Now we find the second
2539 // point.
2540 int i = 0;
2541 enum { kX = 0, kY = 1 };
2542 T v0[2];
2543 while (1) {
2544 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2545 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2546 if (v0[kX] || v0[kY]) {
2547 break;
2548 }
2549 if (++i == n - 1) {
2550 return false;
2551 }
2552 }
2553 // now find a third point that is not colinear with the first two
2554 // points and check the orientation of the triangle (which will be
2555 // the same as the orientation of the path).
2556 for (++i; i < n; ++i) {
2557 T v1[2];
2558 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2559 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2560 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2561 if (0 != cross) {
2562 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2563 return true;
2564 }
2565 }
2566 return false;
2567}
2568}
2569
reed@google.comac8543f2012-01-30 20:51:25 +00002570/*
2571 * We loop through all contours, and keep the computed cross-product of the
2572 * contour that contained the global y-max. If we just look at the first
2573 * contour, we may find one that is wound the opposite way (correctly) since
2574 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2575 * that is outer most (or at least has the global y-max) before we can consider
2576 * its cross product.
2577 */
reed@google.com69a99432012-01-10 18:00:10 +00002578bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002579// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002580 // don't want to pay the cost for computing this if it
2581 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002582
2583 if (kUnknown_Direction != fDirection) {
2584 *dir = static_cast<Direction>(fDirection);
2585 return true;
2586 }
reed@google.com69a99432012-01-10 18:00:10 +00002587 const Convexity conv = this->getConvexityOrUnknown();
2588
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002589 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002590
reed@google.comac8543f2012-01-30 20:51:25 +00002591 // initialize with our logical y-min
2592 SkScalar ymax = this->getBounds().fTop;
2593 SkScalar ymaxCross = 0;
2594
reed@google.com69a99432012-01-10 18:00:10 +00002595 for (; !iter.done(); iter.next()) {
2596 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002597 if (n < 3) {
2598 continue;
2599 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002600
reed@google.comcabaf1d2012-01-11 21:03:05 +00002601 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002602 SkScalar cross = 0;
2603 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002604 // We try first at scalar precision, and then again at double
2605 // precision. This is because the vectors computed between distant
2606 // points may lose too much precision.
2607 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002608 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002609 return true;
2610 }
2611 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002612 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002613 return true;
2614 } else {
2615 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002616 }
2617 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002618 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002619 if (pts[index].fY < ymax) {
2620 continue;
2621 }
2622
reed@google.comc1ea60a2012-01-31 15:15:36 +00002623 // If there is more than 1 distinct point at the y-max, we take the
2624 // x-min and x-max of them and just subtract to compute the dir.
2625 if (pts[(index + 1) % n].fY == pts[index].fY) {
2626 int maxIndex;
2627 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002628 if (minIndex == maxIndex) {
2629 goto TRY_CROSSPROD;
2630 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002631 SkASSERT(pts[minIndex].fY == pts[index].fY);
2632 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2633 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2634 // we just subtract the indices, and let that auto-convert to
2635 // SkScalar, since we just want - or + to signal the direction.
2636 cross = minIndex - maxIndex;
2637 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002638 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002639 // Find a next and prev index to use for the cross-product test,
2640 // but we try to find pts that form non-zero vectors from pts[index]
2641 //
2642 // Its possible that we can't find two non-degenerate vectors, so
2643 // we have to guard our search (e.g. all the pts could be in the
2644 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002645
reed@google.comc1ea60a2012-01-31 15:15:36 +00002646 // we pass n - 1 instead of -1 so we don't foul up % operator by
2647 // passing it a negative LH argument.
2648 int prev = find_diff_pt(pts, index, n, n - 1);
2649 if (prev == index) {
2650 // completely degenerate, skip to next contour
2651 continue;
2652 }
2653 int next = find_diff_pt(pts, index, n, 1);
2654 SkASSERT(next != index);
2655 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002656 // 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 +00002657 // x-direction. We really should continue to walk away from the degeneracy until
2658 // there is a divergence.
2659 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002660 // construct the subtract so we get the correct Direction below
2661 cross = pts[index].fX - pts[next].fX;
2662 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002663 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002664
reed@google.comac8543f2012-01-30 20:51:25 +00002665 if (cross) {
2666 // record our best guess so far
2667 ymax = pts[index].fY;
2668 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002669 }
reed@google.com69a99432012-01-10 18:00:10 +00002670 }
2671 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002672 if (ymaxCross) {
2673 crossToDir(ymaxCross, dir);
2674 fDirection = *dir;
2675 return true;
2676 } else {
2677 return false;
2678 }
reed@google.comac8543f2012-01-30 20:51:25 +00002679}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002680
2681///////////////////////////////////////////////////////////////////////////////
2682
2683static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2684 SkScalar D, SkScalar t) {
2685 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2686}
2687
2688static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2689 SkScalar t) {
2690 SkScalar A = c3 + 3*(c1 - c2) - c0;
2691 SkScalar B = 3*(c2 - c1 - c1 + c0);
2692 SkScalar C = 3*(c1 - c0);
2693 SkScalar D = c0;
2694 return eval_cubic_coeff(A, B, C, D, t);
2695}
2696
2697/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2698 t value such that cubic(t) = target
2699 */
2700static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2701 SkScalar target, SkScalar* t) {
2702 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2703 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002704
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002705 SkScalar D = c0 - target;
2706 SkScalar A = c3 + 3*(c1 - c2) - c0;
2707 SkScalar B = 3*(c2 - c1 - c1 + c0);
2708 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002709
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002710 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2711 SkScalar minT = 0;
2712 SkScalar maxT = SK_Scalar1;
2713 SkScalar mid;
2714 int i;
2715 for (i = 0; i < 16; i++) {
2716 mid = SkScalarAve(minT, maxT);
2717 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2718 if (delta < 0) {
2719 minT = mid;
2720 delta = -delta;
2721 } else {
2722 maxT = mid;
2723 }
2724 if (delta < TOLERANCE) {
2725 break;
2726 }
2727 }
2728 *t = mid;
2729 return true;
2730}
2731
2732template <size_t N> static void find_minmax(const SkPoint pts[],
2733 SkScalar* minPtr, SkScalar* maxPtr) {
2734 SkScalar min, max;
2735 min = max = pts[0].fX;
2736 for (size_t i = 1; i < N; ++i) {
2737 min = SkMinScalar(min, pts[i].fX);
2738 max = SkMaxScalar(max, pts[i].fX);
2739 }
2740 *minPtr = min;
2741 *maxPtr = max;
2742}
2743
2744static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2745 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002746
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002747 int dir = 1;
2748 if (pts[0].fY > pts[3].fY) {
2749 storage[0] = pts[3];
2750 storage[1] = pts[2];
2751 storage[2] = pts[1];
2752 storage[3] = pts[0];
2753 pts = storage;
2754 dir = -1;
2755 }
2756 if (y < pts[0].fY || y >= pts[3].fY) {
2757 return 0;
2758 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002759
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002760 // quickreject or quickaccept
2761 SkScalar min, max;
2762 find_minmax<4>(pts, &min, &max);
2763 if (x < min) {
2764 return 0;
2765 }
2766 if (x > max) {
2767 return dir;
2768 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002769
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002770 // compute the actual x(t) value
2771 SkScalar t, xt;
2772 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2773 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2774 } else {
2775 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2776 xt = y < mid ? pts[0].fX : pts[3].fX;
2777 }
2778 return xt < x ? dir : 0;
2779}
2780
2781static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2782 SkPoint dst[10];
2783 int n = SkChopCubicAtYExtrema(pts, dst);
2784 int w = 0;
2785 for (int i = 0; i <= n; ++i) {
2786 w += winding_mono_cubic(&dst[i * 3], x, y);
2787 }
2788 return w;
2789}
2790
2791static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2792 SkScalar y0 = pts[0].fY;
2793 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002794
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002795 int dir = 1;
2796 if (y0 > y2) {
2797 SkTSwap(y0, y2);
2798 dir = -1;
2799 }
2800 if (y < y0 || y >= y2) {
2801 return 0;
2802 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002803
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002804 // bounds check on X (not required. is it faster?)
2805#if 0
2806 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2807 return 0;
2808 }
2809#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002810
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002811 SkScalar roots[2];
2812 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2813 2 * (pts[1].fY - pts[0].fY),
2814 pts[0].fY - y,
2815 roots);
2816 SkASSERT(n <= 1);
2817 SkScalar xt;
2818 if (0 == n) {
2819 SkScalar mid = SkScalarAve(y0, y2);
2820 // Need [0] and [2] if dir == 1
2821 // and [2] and [0] if dir == -1
2822 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2823 } else {
2824 SkScalar t = roots[0];
2825 SkScalar C = pts[0].fX;
2826 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2827 SkScalar B = 2 * (pts[1].fX - C);
2828 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2829 }
2830 return xt < x ? dir : 0;
2831}
2832
2833static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2834 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2835 if (y0 == y1) {
2836 return true;
2837 }
2838 if (y0 < y1) {
2839 return y1 <= y2;
2840 } else {
2841 return y1 >= y2;
2842 }
2843}
2844
2845static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2846 SkPoint dst[5];
2847 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002848
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002849 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2850 n = SkChopQuadAtYExtrema(pts, dst);
2851 pts = dst;
2852 }
2853 int w = winding_mono_quad(pts, x, y);
2854 if (n > 0) {
2855 w += winding_mono_quad(&pts[2], x, y);
2856 }
2857 return w;
2858}
2859
2860static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2861 SkScalar x0 = pts[0].fX;
2862 SkScalar y0 = pts[0].fY;
2863 SkScalar x1 = pts[1].fX;
2864 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002865
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002866 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002867
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002868 int dir = 1;
2869 if (y0 > y1) {
2870 SkTSwap(y0, y1);
2871 dir = -1;
2872 }
2873 if (y < y0 || y >= y1) {
2874 return 0;
2875 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002876
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002877 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2878 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002879
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002880 if (SkScalarSignAsInt(cross) == dir) {
2881 dir = 0;
2882 }
2883 return dir;
2884}
2885
2886bool SkPath::contains(SkScalar x, SkScalar y) const {
2887 bool isInverse = this->isInverseFillType();
2888 if (this->isEmpty()) {
2889 return isInverse;
2890 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002891
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002892 const SkRect& bounds = this->getBounds();
2893 if (!bounds.contains(x, y)) {
2894 return isInverse;
2895 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002896
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002897 SkPath::Iter iter(*this, true);
2898 bool done = false;
2899 int w = 0;
2900 do {
2901 SkPoint pts[4];
2902 switch (iter.next(pts, false)) {
2903 case SkPath::kMove_Verb:
2904 case SkPath::kClose_Verb:
2905 break;
2906 case SkPath::kLine_Verb:
2907 w += winding_line(pts, x, y);
2908 break;
2909 case SkPath::kQuad_Verb:
2910 w += winding_quad(pts, x, y);
2911 break;
2912 case SkPath::kCubic_Verb:
2913 w += winding_cubic(pts, x, y);
2914 break;
2915 case SkPath::kDone_Verb:
2916 done = true;
2917 break;
2918 }
2919 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002920
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002921 switch (this->getFillType()) {
2922 case SkPath::kEvenOdd_FillType:
2923 case SkPath::kInverseEvenOdd_FillType:
2924 w &= 1;
2925 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002926 default:
2927 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002928 }
2929 return SkToBool(w);
2930}
2931