blob: b9f3286fed84dbda9651ce825f05388c8bc9d720 [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 +0000942#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
943
944void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
945 Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000946 assert_known_direction(dir);
947
reed@android.com8a1c16f2008-12-17 15:59:43 +0000948 SkScalar w = rect.width();
949 SkScalar halfW = SkScalarHalf(w);
950 SkScalar h = rect.height();
951 SkScalar halfH = SkScalarHalf(h);
952
953 if (halfW <= 0 || halfH <= 0) {
954 return;
955 }
956
reed@google.comabf15c12011-01-18 20:35:51 +0000957 bool skip_hori = rx >= halfW;
958 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000959
960 if (skip_hori && skip_vert) {
961 this->addOval(rect, dir);
962 return;
963 }
reed@google.comabf15c12011-01-18 20:35:51 +0000964
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000965 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
966
reed@google.comabf15c12011-01-18 20:35:51 +0000967 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000968 SkAutoDisableDirectionCheck(this);
reed@google.comabf15c12011-01-18 20:35:51 +0000969
reed@android.com8a1c16f2008-12-17 15:59:43 +0000970 if (skip_hori) {
971 rx = halfW;
972 } else if (skip_vert) {
973 ry = halfH;
974 }
975
976 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
977 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
978
979 this->incReserve(17);
980 this->moveTo(rect.fRight - rx, rect.fTop);
981 if (dir == kCCW_Direction) {
982 if (!skip_hori) {
983 this->lineTo(rect.fLeft + rx, rect.fTop); // top
984 }
985 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
986 rect.fLeft, rect.fTop + ry - sy,
987 rect.fLeft, rect.fTop + ry); // top-left
988 if (!skip_vert) {
989 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
990 }
991 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
992 rect.fLeft + rx - sx, rect.fBottom,
993 rect.fLeft + rx, rect.fBottom); // bot-left
994 if (!skip_hori) {
995 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
996 }
997 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
998 rect.fRight, rect.fBottom - ry + sy,
999 rect.fRight, rect.fBottom - ry); // bot-right
1000 if (!skip_vert) {
1001 this->lineTo(rect.fRight, rect.fTop + ry);
1002 }
1003 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1004 rect.fRight - rx + sx, rect.fTop,
1005 rect.fRight - rx, rect.fTop); // top-right
1006 } else {
1007 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1008 rect.fRight, rect.fTop + ry - sy,
1009 rect.fRight, rect.fTop + ry); // top-right
1010 if (!skip_vert) {
1011 this->lineTo(rect.fRight, rect.fBottom - ry);
1012 }
1013 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1014 rect.fRight - rx + sx, rect.fBottom,
1015 rect.fRight - rx, rect.fBottom); // bot-right
1016 if (!skip_hori) {
1017 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1018 }
1019 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1020 rect.fLeft, rect.fBottom - ry + sy,
1021 rect.fLeft, rect.fBottom - ry); // bot-left
1022 if (!skip_vert) {
1023 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1024 }
1025 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1026 rect.fLeft + rx - sx, rect.fTop,
1027 rect.fLeft + rx, rect.fTop); // top-left
1028 if (!skip_hori) {
1029 this->lineTo(rect.fRight - rx, rect.fTop); // top
1030 }
1031 }
1032 this->close();
1033}
1034
1035static void add_corner_arc(SkPath* path, const SkRect& rect,
1036 SkScalar rx, SkScalar ry, int startAngle,
1037 SkPath::Direction dir, bool forceMoveTo) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001038 // These two asserts are not sufficient, since really we want to know
1039 // that the pair of radii (e.g. left and right, or top and bottom) sum
1040 // to <= dimension, but we don't have that data here, so we just have
1041 // these conservative asserts.
1042 SkASSERT(0 <= rx && rx <= rect.width());
1043 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +00001044
reed@android.com8a1c16f2008-12-17 15:59:43 +00001045 SkRect r;
1046 r.set(-rx, -ry, rx, ry);
1047
1048 switch (startAngle) {
1049 case 0:
1050 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
1051 break;
1052 case 90:
1053 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
1054 break;
1055 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
1056 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001057 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001058 }
reed@google.comabf15c12011-01-18 20:35:51 +00001059
reed@android.com8a1c16f2008-12-17 15:59:43 +00001060 SkScalar start = SkIntToScalar(startAngle);
1061 SkScalar sweep = SkIntToScalar(90);
1062 if (SkPath::kCCW_Direction == dir) {
1063 start += sweep;
1064 sweep = -sweep;
1065 }
reed@google.comabf15c12011-01-18 20:35:51 +00001066
reed@android.com8a1c16f2008-12-17 15:59:43 +00001067 path->arcTo(r, start, sweep, forceMoveTo);
1068}
1069
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001070void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +00001071 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001072 SkRRect rrect;
1073 rrect.setRectRadii(rect, (const SkVector*) radii);
1074 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075}
1076
reed@google.com4ed0fb72012-12-12 20:48:18 +00001077void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001078 assert_known_direction(dir);
1079
1080 if (rrect.isEmpty()) {
1081 return;
1082 }
1083
reed@google.com4ed0fb72012-12-12 20:48:18 +00001084 const SkRect& bounds = rrect.getBounds();
1085
1086 if (rrect.isRect()) {
1087 this->addRect(bounds, dir);
1088 } else if (rrect.isOval()) {
1089 this->addOval(bounds, dir);
1090 } else if (rrect.isSimple()) {
1091 const SkVector& rad = rrect.getSimpleRadii();
1092 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1093 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001094 SkAutoPathBoundsUpdate apbu(this, bounds);
1095
1096 if (kCW_Direction == dir) {
1097 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1098 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1099 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1100 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1101 } else {
1102 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1103 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1104 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1105 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1106 }
1107 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001108 }
1109}
1110
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001111bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001112 int count = fPathRef->countVerbs();
1113 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1114 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001115 if (*verbs == kLine_Verb ||
1116 *verbs == kQuad_Verb ||
1117 *verbs == kCubic_Verb) {
1118 return false;
1119 }
1120 ++verbs;
1121 }
1122 return true;
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());
1150#if 0 // these seem faster than using quads (1/2 the number of edges)
1151 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1152 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
1153
1154 this->incReserve(13);
1155 this->moveTo(cx + rx, cy);
1156 if (dir == kCCW_Direction) {
1157 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
1158 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
1159 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
1160 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
1161 } else {
1162 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
1163 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
1164 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
1165 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
1166 }
1167#else
1168 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1169 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1170 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1171 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1172
1173 /*
1174 To handle imprecision in computing the center and radii, we revert to
1175 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1176 to ensure that we don't exceed the oval's bounds *ever*, since we want
1177 to use oval for our fast-bounds, rather than have to recompute it.
1178 */
1179 const SkScalar L = oval.fLeft; // cx - rx
1180 const SkScalar T = oval.fTop; // cy - ry
1181 const SkScalar R = oval.fRight; // cx + rx
1182 const SkScalar B = oval.fBottom; // cy + ry
1183
1184 this->incReserve(17); // 8 quads + close
1185 this->moveTo(R, cy);
1186 if (dir == kCCW_Direction) {
1187 this->quadTo( R, cy - sy, cx + mx, cy - my);
1188 this->quadTo(cx + sx, T, cx , T);
1189 this->quadTo(cx - sx, T, cx - mx, cy - my);
1190 this->quadTo( L, cy - sy, L, cy );
1191 this->quadTo( L, cy + sy, cx - mx, cy + my);
1192 this->quadTo(cx - sx, B, cx , B);
1193 this->quadTo(cx + sx, B, cx + mx, cy + my);
1194 this->quadTo( R, cy + sy, R, cy );
1195 } else {
1196 this->quadTo( R, cy + sy, cx + mx, cy + my);
1197 this->quadTo(cx + sx, B, cx , B);
1198 this->quadTo(cx - sx, B, cx - mx, cy + my);
1199 this->quadTo( L, cy + sy, L, cy );
1200 this->quadTo( L, cy - sy, cx - mx, cy - my);
1201 this->quadTo(cx - sx, T, cx , T);
1202 this->quadTo(cx + sx, T, cx + mx, cy - my);
1203 this->quadTo( R, cy - sy, R, cy );
1204 }
1205#endif
1206 this->close();
1207}
1208
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001209bool SkPath::isOval(SkRect* rect) const {
1210 if (fIsOval && rect) {
1211 *rect = getBounds();
1212 }
1213
1214 return fIsOval;
1215}
1216
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1218 if (r > 0) {
1219 SkRect rect;
1220 rect.set(x - r, y - r, x + r, y + r);
1221 this->addOval(rect, dir);
1222 }
1223}
1224
1225#include "SkGeometry.h"
1226
1227static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1228 SkScalar sweepAngle,
1229 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001230
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001231 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001232 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1233 // Chrome uses this path to move into and out of ovals. If not
1234 // treated as a special case the moves can distort the oval's
1235 // bounding box (and break the circle special case).
1236 pts[0].set(oval.fRight, oval.centerY());
1237 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001238 } else if (0 == oval.width() && 0 == oval.height()) {
1239 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001240 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001241 // a rect.
1242 // TODO: optimizing the case where only one of width or height is zero
1243 // should also be considered. This case, however, doesn't seem to be
1244 // as common as the single point case.
1245 pts[0].set(oval.fRight, oval.fTop);
1246 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001247 }
1248
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 SkVector start, stop;
1250
1251 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1252 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1253 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001254
1255 /* If the sweep angle is nearly (but less than) 360, then due to precision
1256 loss in radians-conversion and/or sin/cos, we may end up with coincident
1257 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1258 of drawing a nearly complete circle (good).
1259 e.g. canvas.drawArc(0, 359.99, ...)
1260 -vs- canvas.drawArc(0, 359.9, ...)
1261 We try to detect this edge case, and tweak the stop vector
1262 */
1263 if (start == stop) {
1264 SkScalar sw = SkScalarAbs(sweepAngle);
1265 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1266 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1267 // make a guess at a tiny angle (in radians) to tweak by
1268 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1269 // not sure how much will be enough, so we use a loop
1270 do {
1271 stopRad -= deltaRad;
1272 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1273 } while (start == stop);
1274 }
1275 }
1276
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001278
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1280 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001281
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282 return SkBuildQuadArc(start, stop,
1283 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1284 &matrix, pts);
1285}
1286
1287void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1288 bool forceMoveTo) {
1289 if (oval.width() < 0 || oval.height() < 0) {
1290 return;
1291 }
1292
1293 SkPoint pts[kSkBuildQuadArcStorage];
1294 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1295 SkASSERT((count & 1) == 1);
1296
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001297 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001298 forceMoveTo = true;
1299 }
1300 this->incReserve(count);
1301 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1302 for (int i = 1; i < count; i += 2) {
1303 this->quadTo(pts[i], pts[i+1]);
1304 }
1305}
1306
1307void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1308 SkScalar sweepAngle) {
1309 if (oval.isEmpty() || 0 == sweepAngle) {
1310 return;
1311 }
1312
1313 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1314
1315 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1316 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1317 return;
1318 }
1319
1320 SkPoint pts[kSkBuildQuadArcStorage];
1321 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1322
1323 this->incReserve(count);
1324 this->moveTo(pts[0]);
1325 for (int i = 1; i < count; i += 2) {
1326 this->quadTo(pts[i], pts[i+1]);
1327 }
1328}
1329
1330/*
1331 Need to handle the case when the angle is sharp, and our computed end-points
1332 for the arc go behind pt1 and/or p2...
1333*/
1334void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1335 SkScalar radius) {
1336 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001337
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338 // need to know our prev pt so we can construct tangent vectors
1339 {
1340 SkPoint start;
1341 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001342 // Handle degenerate cases by adding a line to the first point and
1343 // bailing out.
1344 if ((x1 == start.fX && y1 == start.fY) ||
1345 (x1 == x2 && y1 == y2) ||
1346 radius == 0) {
1347 this->lineTo(x1, y1);
1348 return;
1349 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350 before.setNormalize(x1 - start.fX, y1 - start.fY);
1351 after.setNormalize(x2 - x1, y2 - y1);
1352 }
reed@google.comabf15c12011-01-18 20:35:51 +00001353
reed@android.com8a1c16f2008-12-17 15:59:43 +00001354 SkScalar cosh = SkPoint::DotProduct(before, after);
1355 SkScalar sinh = SkPoint::CrossProduct(before, after);
1356
1357 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001358 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001359 return;
1360 }
reed@google.comabf15c12011-01-18 20:35:51 +00001361
reed@android.com8a1c16f2008-12-17 15:59:43 +00001362 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1363 if (dist < 0) {
1364 dist = -dist;
1365 }
1366
1367 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1368 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1369 SkRotationDirection arcDir;
1370
1371 // now turn before/after into normals
1372 if (sinh > 0) {
1373 before.rotateCCW();
1374 after.rotateCCW();
1375 arcDir = kCW_SkRotationDirection;
1376 } else {
1377 before.rotateCW();
1378 after.rotateCW();
1379 arcDir = kCCW_SkRotationDirection;
1380 }
1381
1382 SkMatrix matrix;
1383 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001384
reed@android.com8a1c16f2008-12-17 15:59:43 +00001385 matrix.setScale(radius, radius);
1386 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1387 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001388
reed@android.com8a1c16f2008-12-17 15:59:43 +00001389 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001390
reed@android.com8a1c16f2008-12-17 15:59:43 +00001391 this->incReserve(count);
1392 // [xx,yy] == pts[0]
1393 this->lineTo(xx, yy);
1394 for (int i = 1; i < count; i += 2) {
1395 this->quadTo(pts[i], pts[i+1]);
1396 }
1397}
1398
1399///////////////////////////////////////////////////////////////////////////////
1400
1401void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1402 SkMatrix matrix;
1403
1404 matrix.setTranslate(dx, dy);
1405 this->addPath(path, matrix);
1406}
1407
1408void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001409 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001410
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001411 fIsOval = false;
1412
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001413 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414 SkPoint pts[4];
1415 Verb verb;
1416
1417 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1418
1419 while ((verb = iter.next(pts)) != kDone_Verb) {
1420 switch (verb) {
1421 case kMove_Verb:
1422 proc(matrix, &pts[0], &pts[0], 1);
1423 this->moveTo(pts[0]);
1424 break;
1425 case kLine_Verb:
1426 proc(matrix, &pts[1], &pts[1], 1);
1427 this->lineTo(pts[1]);
1428 break;
1429 case kQuad_Verb:
1430 proc(matrix, &pts[1], &pts[1], 2);
1431 this->quadTo(pts[1], pts[2]);
1432 break;
1433 case kCubic_Verb:
1434 proc(matrix, &pts[1], &pts[1], 3);
1435 this->cubicTo(pts[1], pts[2], pts[3]);
1436 break;
1437 case kClose_Verb:
1438 this->close();
1439 break;
1440 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001441 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 }
1443 }
1444}
1445
1446///////////////////////////////////////////////////////////////////////////////
1447
1448static const uint8_t gPtsInVerb[] = {
1449 1, // kMove
1450 1, // kLine
1451 2, // kQuad
1452 3, // kCubic
1453 0, // kClose
1454 0 // kDone
1455};
1456
1457// ignore the initial moveto, and stop when the 1st contour ends
1458void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001459 int i, vcount = path.fPathRef->countVerbs();
1460 // exit early if the path is empty, or just has a moveTo.
1461 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 return;
1463 }
1464
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001465 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001467 fIsOval = false;
1468
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001469 const uint8_t* verbs = path.fPathRef->verbs();
1470 // skip the initial moveTo
1471 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001472
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001473 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001475 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476 case kLine_Verb:
1477 this->lineTo(pts[0].fX, pts[0].fY);
1478 break;
1479 case kQuad_Verb:
1480 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1481 break;
1482 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001483 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 +00001484 break;
1485 case kClose_Verb:
1486 return;
1487 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001488 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489 }
1490}
1491
1492// ignore the last point of the 1st contour
1493void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001494 int i, vcount = path.fPathRef->countVerbs();
1495 // exit early if the path is empty, or just has a moveTo.
1496 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001497 return;
1498 }
1499
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001500 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001502 fIsOval = false;
1503
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001504 const uint8_t* verbs = path.fPathRef->verbs();
1505 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001507 SkASSERT(verbs[~0] == kMove_Verb);
1508 for (i = 1; i < vcount; ++i) {
1509 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 if (n == 0) {
1511 break;
1512 }
1513 pts += n;
1514 }
1515
1516 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001517 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 case kLine_Verb:
1519 this->lineTo(pts[-1].fX, pts[-1].fY);
1520 break;
1521 case kQuad_Verb:
1522 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1523 break;
1524 case kCubic_Verb:
1525 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1526 pts[-3].fX, pts[-3].fY);
1527 break;
1528 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001529 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530 break;
1531 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001532 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 }
1534}
1535
reed@google.com63d73742012-01-10 15:33:12 +00001536void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001537 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001538
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001539 const SkPoint* pts = src.fPathRef->pointsEnd();
1540 // we will iterator through src's verbs backwards
1541 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1542 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001543
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001544 fIsOval = false;
1545
reed@google.com63d73742012-01-10 15:33:12 +00001546 bool needMove = true;
1547 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001548 while (verbs < verbsEnd) {
1549 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001550 int n = gPtsInVerb[v];
1551
1552 if (needMove) {
1553 --pts;
1554 this->moveTo(pts->fX, pts->fY);
1555 needMove = false;
1556 }
1557 pts -= n;
1558 switch (v) {
1559 case kMove_Verb:
1560 if (needClose) {
1561 this->close();
1562 needClose = false;
1563 }
1564 needMove = true;
1565 pts += 1; // so we see the point in "if (needMove)" above
1566 break;
1567 case kLine_Verb:
1568 this->lineTo(pts[0]);
1569 break;
1570 case kQuad_Verb:
1571 this->quadTo(pts[1], pts[0]);
1572 break;
1573 case kCubic_Verb:
1574 this->cubicTo(pts[2], pts[1], pts[0]);
1575 break;
1576 case kClose_Verb:
1577 needClose = true;
1578 break;
1579 default:
1580 SkASSERT(!"unexpected verb");
1581 }
1582 }
1583}
1584
reed@android.com8a1c16f2008-12-17 15:59:43 +00001585///////////////////////////////////////////////////////////////////////////////
1586
1587void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1588 SkMatrix matrix;
1589
1590 matrix.setTranslate(dx, dy);
1591 this->transform(matrix, dst);
1592}
1593
1594#include "SkGeometry.h"
1595
1596static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1597 int level = 2) {
1598 if (--level >= 0) {
1599 SkPoint tmp[5];
1600
1601 SkChopQuadAtHalf(pts, tmp);
1602 subdivide_quad_to(path, &tmp[0], level);
1603 subdivide_quad_to(path, &tmp[2], level);
1604 } else {
1605 path->quadTo(pts[1], pts[2]);
1606 }
1607}
1608
1609static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1610 int level = 2) {
1611 if (--level >= 0) {
1612 SkPoint tmp[7];
1613
1614 SkChopCubicAtHalf(pts, tmp);
1615 subdivide_cubic_to(path, &tmp[0], level);
1616 subdivide_cubic_to(path, &tmp[3], level);
1617 } else {
1618 path->cubicTo(pts[1], pts[2], pts[3]);
1619 }
1620}
1621
1622void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1623 SkDEBUGCODE(this->validate();)
1624 if (dst == NULL) {
1625 dst = (SkPath*)this;
1626 }
1627
tomhudson@google.com8d430182011-06-06 19:11:19 +00001628 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001629 SkPath tmp;
1630 tmp.fFillType = fFillType;
1631
1632 SkPath::Iter iter(*this, false);
1633 SkPoint pts[4];
1634 SkPath::Verb verb;
1635
reed@google.com4a3b7142012-05-16 17:16:46 +00001636 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 switch (verb) {
1638 case kMove_Verb:
1639 tmp.moveTo(pts[0]);
1640 break;
1641 case kLine_Verb:
1642 tmp.lineTo(pts[1]);
1643 break;
1644 case kQuad_Verb:
1645 subdivide_quad_to(&tmp, pts);
1646 break;
1647 case kCubic_Verb:
1648 subdivide_cubic_to(&tmp, pts);
1649 break;
1650 case kClose_Verb:
1651 tmp.close();
1652 break;
1653 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001654 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001655 break;
1656 }
1657 }
1658
1659 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001660 SkPathRef::Editor ed(&dst->fPathRef);
1661 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001662 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001664 /*
1665 * If we're not in perspective, we can transform all of the points at
1666 * once.
1667 *
1668 * Here we also want to optimize bounds, by noting if the bounds are
1669 * already known, and if so, we just transform those as well and mark
1670 * them as "known", rather than force the transformed path to have to
1671 * recompute them.
1672 *
1673 * Special gotchas if the path is effectively empty (<= 1 point) or
1674 * if it is non-finite. In those cases bounds need to stay empty,
1675 * regardless of the matrix.
1676 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001677 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001678 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001679 if (fIsFinite) {
1680 matrix.mapRect(&dst->fBounds, fBounds);
1681 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1682 dst->fBounds.setEmpty();
1683 }
1684 } else {
1685 dst->fIsFinite = false;
1686 dst->fBounds.setEmpty();
1687 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001689 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001690 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 }
1692
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001693 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1694
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001696 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001697 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001698 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001699 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001700
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001701 if (kUnknown_Direction == fDirection) {
1702 dst->fDirection = kUnknown_Direction;
1703 } else {
1704 SkScalar det2x2 =
1705 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1706 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1707 if (det2x2 < 0) {
1708 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1709 } else if (det2x2 > 0) {
1710 dst->fDirection = fDirection;
1711 } else {
1712 dst->fDirection = kUnknown_Direction;
1713 }
1714 }
1715
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001716 // It's an oval only if it stays a rect.
1717 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001718
reed@android.com8a1c16f2008-12-17 15:59:43 +00001719 SkDEBUGCODE(dst->validate();)
1720 }
1721}
1722
reed@android.com8a1c16f2008-12-17 15:59:43 +00001723///////////////////////////////////////////////////////////////////////////////
1724///////////////////////////////////////////////////////////////////////////////
1725
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001726enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001727 kEmptyContour_SegmentState, // The current contour is empty. We may be
1728 // starting processing or we may have just
1729 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001730 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1731 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1732 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733};
1734
1735SkPath::Iter::Iter() {
1736#ifdef SK_DEBUG
1737 fPts = NULL;
1738 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001739 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001740 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741#endif
1742 // need to init enough to make next() harmlessly return kDone_Verb
1743 fVerbs = NULL;
1744 fVerbStop = NULL;
1745 fNeedClose = false;
1746}
1747
1748SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1749 this->setPath(path, forceClose);
1750}
1751
1752void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001753 fPts = path.fPathRef->points();
1754 fVerbs = path.fPathRef->verbs();
1755 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001756 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001757 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758 fForceClose = SkToU8(forceClose);
1759 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001760 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761}
1762
1763bool SkPath::Iter::isClosedContour() const {
1764 if (fVerbs == NULL || fVerbs == fVerbStop) {
1765 return false;
1766 }
1767 if (fForceClose) {
1768 return true;
1769 }
1770
1771 const uint8_t* verbs = fVerbs;
1772 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001773
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001774 if (kMove_Verb == *(verbs - 1)) {
1775 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776 }
1777
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001778 while (verbs > stop) {
1779 // verbs points one beyond the current verb, decrement first.
1780 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001781 if (kMove_Verb == v) {
1782 break;
1783 }
1784 if (kClose_Verb == v) {
1785 return true;
1786 }
1787 }
1788 return false;
1789}
1790
1791SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001792 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001794 // A special case: if both points are NaN, SkPoint::operation== returns
1795 // false, but the iterator expects that they are treated as the same.
1796 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001797 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1798 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001799 return kClose_Verb;
1800 }
1801
reed@google.com9e25dbf2012-05-15 17:05:38 +00001802 pts[0] = fLastPt;
1803 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804 fLastPt = fMoveTo;
1805 fCloseLine = true;
1806 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001807 } else {
1808 pts[0] = fMoveTo;
1809 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001811}
1812
reed@google.com9e25dbf2012-05-15 17:05:38 +00001813const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814 if (fSegmentState == kAfterMove_SegmentState) {
1815 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001816 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001817 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001819 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1820 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001821 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823}
1824
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001825void SkPath::Iter::consumeDegenerateSegments() {
1826 // We need to step over anything that will not move the current draw point
1827 // forward before the next move is seen
1828 const uint8_t* lastMoveVerb = 0;
1829 const SkPoint* lastMovePt = 0;
1830 SkPoint lastPt = fLastPt;
1831 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001832 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001833 switch (verb) {
1834 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001835 // Keep a record of this most recent move
1836 lastMoveVerb = fVerbs;
1837 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001838 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001839 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001840 fPts++;
1841 break;
1842
1843 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001844 // A close when we are in a segment is always valid except when it
1845 // follows a move which follows a segment.
1846 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001847 return;
1848 }
1849 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001850 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001851 break;
1852
1853 case kLine_Verb:
1854 if (!IsLineDegenerate(lastPt, fPts[0])) {
1855 if (lastMoveVerb) {
1856 fVerbs = lastMoveVerb;
1857 fPts = lastMovePt;
1858 return;
1859 }
1860 return;
1861 }
1862 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001863 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001864 fPts++;
1865 break;
1866
1867 case kQuad_Verb:
1868 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1869 if (lastMoveVerb) {
1870 fVerbs = lastMoveVerb;
1871 fPts = lastMovePt;
1872 return;
1873 }
1874 return;
1875 }
1876 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001877 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001878 fPts += 2;
1879 break;
1880
1881 case kCubic_Verb:
1882 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1883 if (lastMoveVerb) {
1884 fVerbs = lastMoveVerb;
1885 fPts = lastMovePt;
1886 return;
1887 }
1888 return;
1889 }
1890 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001891 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 fPts += 3;
1893 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001894
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001895 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001896 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 }
1898 }
1899}
1900
reed@google.com4a3b7142012-05-16 17:16:46 +00001901SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001902 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903
reed@android.com8a1c16f2008-12-17 15:59:43 +00001904 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001905 // Close the curve if requested and if there is some curve to close
1906 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001907 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001908 return kLine_Verb;
1909 }
1910 fNeedClose = false;
1911 return kClose_Verb;
1912 }
1913 return kDone_Verb;
1914 }
1915
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001916 // fVerbs is one beyond the current verb, decrement first
1917 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001918 const SkPoint* SK_RESTRICT srcPts = fPts;
1919 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001920
1921 switch (verb) {
1922 case kMove_Verb:
1923 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001924 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001925 verb = this->autoClose(pts);
1926 if (verb == kClose_Verb) {
1927 fNeedClose = false;
1928 }
1929 return (Verb)verb;
1930 }
1931 if (fVerbs == fVerbStop) { // might be a trailing moveto
1932 return kDone_Verb;
1933 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001934 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001935 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001936 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001937 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001938 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001939 fNeedClose = fForceClose;
1940 break;
1941 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001942 pts[0] = this->cons_moveTo();
1943 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001944 fLastPt = srcPts[0];
1945 fCloseLine = false;
1946 srcPts += 1;
1947 break;
1948 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001949 pts[0] = this->cons_moveTo();
1950 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001951 fLastPt = srcPts[1];
1952 srcPts += 2;
1953 break;
1954 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001955 pts[0] = this->cons_moveTo();
1956 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 fLastPt = srcPts[2];
1958 srcPts += 3;
1959 break;
1960 case kClose_Verb:
1961 verb = this->autoClose(pts);
1962 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001963 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 } else {
1965 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001966 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001967 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001968 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001969 break;
1970 }
1971 fPts = srcPts;
1972 return (Verb)verb;
1973}
1974
1975///////////////////////////////////////////////////////////////////////////////
1976
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001977SkPath::RawIter::RawIter() {
1978#ifdef SK_DEBUG
1979 fPts = NULL;
1980 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1981#endif
1982 // need to init enough to make next() harmlessly return kDone_Verb
1983 fVerbs = NULL;
1984 fVerbStop = NULL;
1985}
1986
1987SkPath::RawIter::RawIter(const SkPath& path) {
1988 this->setPath(path);
1989}
1990
1991void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001992 fPts = path.fPathRef->points();
1993 fVerbs = path.fPathRef->verbs();
1994 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001995 fMoveTo.fX = fMoveTo.fY = 0;
1996 fLastPt.fX = fLastPt.fY = 0;
1997}
1998
1999SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002000 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002001 if (fVerbs == fVerbStop) {
2002 return kDone_Verb;
2003 }
2004
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002005 // fVerbs points one beyond next verb so decrement first.
2006 unsigned verb = *(--fVerbs);
2007 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002008
2009 switch (verb) {
2010 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002011 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002012 fMoveTo = srcPts[0];
2013 fLastPt = fMoveTo;
2014 srcPts += 1;
2015 break;
2016 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002017 pts[0] = fLastPt;
2018 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002019 fLastPt = srcPts[0];
2020 srcPts += 1;
2021 break;
2022 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002023 pts[0] = fLastPt;
2024 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002025 fLastPt = srcPts[1];
2026 srcPts += 2;
2027 break;
2028 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002029 pts[0] = fLastPt;
2030 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002031 fLastPt = srcPts[2];
2032 srcPts += 3;
2033 break;
2034 case kClose_Verb:
2035 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002036 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002037 break;
2038 }
2039 fPts = srcPts;
2040 return (Verb)verb;
2041}
2042
2043///////////////////////////////////////////////////////////////////////////////
2044
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002046 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047*/
2048
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002049uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002050 SkDEBUGCODE(this->validate();)
2051
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002052 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002053 const int byteCount = sizeof(int32_t)
2054#if NEW_PICTURE_FORMAT
2055 + fPathRef->writeSize()
2056#else
2057 + 2 * sizeof(int32_t)
2058 + sizeof(SkPoint) * fPathRef->countPoints()
2059 + sizeof(uint8_t) * fPathRef->countVerbs()
2060#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002061 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002062 return SkAlign4(byteCount);
2063 }
2064
2065 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002066#if !NEW_PICTURE_FORMAT
2067 buffer.write32(fPathRef->countPoints());
2068 buffer.write32(fPathRef->countVerbs());
2069#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002070
2071 // Call getBounds() to ensure (as a side-effect) that fBounds
2072 // and fIsFinite are computed.
2073 const SkRect& bounds = this->getBounds();
2074 SkASSERT(!fBoundsIsDirty);
2075
2076 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2077 ((fIsOval & 1) << kIsOval_SerializationShift) |
2078 (fConvexity << kConvexity_SerializationShift) |
2079 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002080 (fSegmentMask << kSegmentMask_SerializationShift) |
2081 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002082
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002083 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002084
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002085 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002086
2087 buffer.write(&bounds, sizeof(bounds));
2088
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002089 buffer.padToAlign4();
2090 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002091}
2092
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002093uint32_t SkPath::readFromMemory(const void* storage) {
2094 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002095#if !NEW_PICTURE_FORMAT
2096 int32_t pcount = buffer.readS32();
2097 int32_t vcount = buffer.readS32();
2098#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002099
reed@google.com98b11f12011-09-21 18:40:27 +00002100 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002101 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2102 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2103 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2104 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002105 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
2106 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002107
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002108#if NEW_PICTURE_FORMAT
2109 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
2110#else
2111 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
2112#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002113
2114 buffer.read(&fBounds, sizeof(fBounds));
2115 fBoundsIsDirty = false;
2116
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002117 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002118
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002119 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120
2121 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002122 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002123}
2124
2125///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126
reed@android.com8a1c16f2008-12-17 15:59:43 +00002127void SkPath::dump(bool forceClose, const char title[]) const {
2128 Iter iter(*this, forceClose);
2129 SkPoint pts[4];
2130 Verb verb;
2131
2132 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2133 title ? title : "");
2134
reed@google.com4a3b7142012-05-16 17:16:46 +00002135 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002136 switch (verb) {
2137 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002138 SkDebugf(" path: moveTo [%g %g]\n",
2139 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002140 break;
2141 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002142 SkDebugf(" path: lineTo [%g %g]\n",
2143 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144 break;
2145 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
2147 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2148 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002149 break;
2150 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002151 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
2152 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2153 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
2154 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002155 break;
2156 case kClose_Verb:
2157 SkDebugf(" path: close\n");
2158 break;
2159 default:
2160 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2161 verb = kDone_Verb; // stop the loop
2162 break;
2163 }
2164 }
2165 SkDebugf("path: done %s\n", title ? title : "");
2166}
2167
reed@android.come522ca52009-11-23 20:10:41 +00002168void SkPath::dump() const {
2169 this->dump(false);
2170}
2171
2172#ifdef SK_DEBUG
2173void SkPath::validate() const {
2174 SkASSERT(this != NULL);
2175 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002176
djsollen@google.com077348c2012-10-22 20:23:32 +00002177#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002178 if (!fBoundsIsDirty) {
2179 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002180
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002181 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002182 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002183
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002184 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002185 // if we're empty, fBounds may be empty but translated, so we can't
2186 // necessarily compare to bounds directly
2187 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2188 // be [2, 2, 2, 2]
2189 SkASSERT(bounds.isEmpty());
2190 SkASSERT(fBounds.isEmpty());
2191 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002192 if (bounds.isEmpty()) {
2193 SkASSERT(fBounds.isEmpty());
2194 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002195 if (!fBounds.isEmpty()) {
2196 SkASSERT(fBounds.contains(bounds));
2197 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002198 }
reed@android.come522ca52009-11-23 20:10:41 +00002199 }
2200 }
reed@google.com10296cc2011-09-21 12:29:05 +00002201
2202 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002203 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2204 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2205 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002206 case kLine_Verb:
2207 mask |= kLine_SegmentMask;
2208 break;
2209 case kQuad_Verb:
2210 mask |= kQuad_SegmentMask;
2211 break;
2212 case kCubic_Verb:
2213 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002214 case kMove_Verb: // these verbs aren't included in the segment mask.
2215 case kClose_Verb:
2216 break;
2217 case kDone_Verb:
2218 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2219 break;
2220 default:
2221 SkDEBUGFAIL("Unknown Verb");
2222 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002223 }
2224 }
2225 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002226#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002227}
djsollen@google.com077348c2012-10-22 20:23:32 +00002228#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002229
reed@google.com04863fa2011-05-15 04:08:24 +00002230///////////////////////////////////////////////////////////////////////////////
2231
reed@google.com0b7b9822011-05-16 12:29:27 +00002232static int sign(SkScalar x) { return x < 0; }
2233#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002234
reed@google.com04863fa2011-05-15 04:08:24 +00002235static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002236 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002237}
2238
2239// only valid for a single contour
2240struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002241 Convexicator()
2242 : fPtCount(0)
2243 , fConvexity(SkPath::kConvex_Convexity)
2244 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002245 fSign = 0;
2246 // warnings
2247 fCurrPt.set(0, 0);
2248 fVec0.set(0, 0);
2249 fVec1.set(0, 0);
2250 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002251
2252 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002253 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002254 }
2255
2256 SkPath::Convexity getConvexity() const { return fConvexity; }
2257
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002258 /** The direction returned is only valid if the path is determined convex */
2259 SkPath::Direction getDirection() const { return fDirection; }
2260
reed@google.com04863fa2011-05-15 04:08:24 +00002261 void addPt(const SkPoint& pt) {
2262 if (SkPath::kConcave_Convexity == fConvexity) {
2263 return;
2264 }
2265
2266 if (0 == fPtCount) {
2267 fCurrPt = pt;
2268 ++fPtCount;
2269 } else {
2270 SkVector vec = pt - fCurrPt;
2271 if (vec.fX || vec.fY) {
2272 fCurrPt = pt;
2273 if (++fPtCount == 2) {
2274 fFirstVec = fVec1 = vec;
2275 } else {
2276 SkASSERT(fPtCount > 2);
2277 this->addVec(vec);
2278 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002279
reed@google.com85b6e392011-05-15 20:25:17 +00002280 int sx = sign(vec.fX);
2281 int sy = sign(vec.fY);
2282 fDx += (sx != fSx);
2283 fDy += (sy != fSy);
2284 fSx = sx;
2285 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002286
reed@google.com85b6e392011-05-15 20:25:17 +00002287 if (fDx > 3 || fDy > 3) {
2288 fConvexity = SkPath::kConcave_Convexity;
2289 }
reed@google.com04863fa2011-05-15 04:08:24 +00002290 }
2291 }
2292 }
2293
2294 void close() {
2295 if (fPtCount > 2) {
2296 this->addVec(fFirstVec);
2297 }
2298 }
2299
2300private:
2301 void addVec(const SkVector& vec) {
2302 SkASSERT(vec.fX || vec.fY);
2303 fVec0 = fVec1;
2304 fVec1 = vec;
2305 int sign = CrossProductSign(fVec0, fVec1);
2306 if (0 == fSign) {
2307 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002308 if (1 == sign) {
2309 fDirection = SkPath::kCW_Direction;
2310 } else if (-1 == sign) {
2311 fDirection = SkPath::kCCW_Direction;
2312 }
reed@google.com04863fa2011-05-15 04:08:24 +00002313 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002314 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002315 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002316 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002317 }
2318 }
2319 }
2320
2321 SkPoint fCurrPt;
2322 SkVector fVec0, fVec1, fFirstVec;
2323 int fPtCount; // non-degenerate points
2324 int fSign;
2325 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002326 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002327 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002328};
2329
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002330SkPath::Convexity SkPath::internalGetConvexity() const {
2331 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002332 SkPoint pts[4];
2333 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002334 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002335
2336 int contourCount = 0;
2337 int count;
2338 Convexicator state;
2339
2340 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2341 switch (verb) {
2342 case kMove_Verb:
2343 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002344 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002345 return kConcave_Convexity;
2346 }
2347 pts[1] = pts[0];
2348 count = 1;
2349 break;
2350 case kLine_Verb: count = 1; break;
2351 case kQuad_Verb: count = 2; break;
2352 case kCubic_Verb: count = 3; break;
2353 case kClose_Verb:
2354 state.close();
2355 count = 0;
2356 break;
2357 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002358 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002359 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002360 return kConcave_Convexity;
2361 }
2362
2363 for (int i = 1; i <= count; i++) {
2364 state.addPt(pts[i]);
2365 }
2366 // early exit
2367 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002368 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002369 return kConcave_Convexity;
2370 }
2371 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002372 fConvexity = state.getConvexity();
2373 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2374 fDirection = state.getDirection();
2375 }
2376 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002377}
reed@google.com69a99432012-01-10 18:00:10 +00002378
2379///////////////////////////////////////////////////////////////////////////////
2380
2381class ContourIter {
2382public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002383 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002384
2385 bool done() const { return fDone; }
2386 // if !done() then these may be called
2387 int count() const { return fCurrPtCount; }
2388 const SkPoint* pts() const { return fCurrPt; }
2389 void next();
2390
2391private:
2392 int fCurrPtCount;
2393 const SkPoint* fCurrPt;
2394 const uint8_t* fCurrVerb;
2395 const uint8_t* fStopVerbs;
2396 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002397 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002398};
2399
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002400ContourIter::ContourIter(const SkPathRef& pathRef) {
2401 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002402 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002403 fCurrPt = pathRef.points();
2404 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002405 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002406 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002407 this->next();
2408}
2409
2410void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002411 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002412 fDone = true;
2413 }
2414 if (fDone) {
2415 return;
2416 }
2417
2418 // skip pts of prev contour
2419 fCurrPt += fCurrPtCount;
2420
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002421 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002422 int ptCount = 1; // moveTo
2423 const uint8_t* verbs = fCurrVerb;
2424
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002425 for (--verbs; verbs > fStopVerbs; --verbs) {
2426 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002427 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002428 goto CONTOUR_END;
2429 case SkPath::kLine_Verb:
2430 ptCount += 1;
2431 break;
2432 case SkPath::kQuad_Verb:
2433 ptCount += 2;
2434 break;
2435 case SkPath::kCubic_Verb:
2436 ptCount += 3;
2437 break;
2438 default: // kClose_Verb, just keep going
2439 break;
2440 }
2441 }
2442CONTOUR_END:
2443 fCurrPtCount = ptCount;
2444 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002445 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002446}
2447
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002448// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002449static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002450 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2451 // We may get 0 when the above subtracts underflow. We expect this to be
2452 // very rare and lazily promote to double.
2453 if (0 == cross) {
2454 double p0x = SkScalarToDouble(p0.fX);
2455 double p0y = SkScalarToDouble(p0.fY);
2456
2457 double p1x = SkScalarToDouble(p1.fX);
2458 double p1y = SkScalarToDouble(p1.fY);
2459
2460 double p2x = SkScalarToDouble(p2.fX);
2461 double p2y = SkScalarToDouble(p2.fY);
2462
2463 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2464 (p1y - p0y) * (p2x - p0x));
2465
2466 }
2467 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002468}
2469
reed@google.comc1ea60a2012-01-31 15:15:36 +00002470// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002471static int find_max_y(const SkPoint pts[], int count) {
2472 SkASSERT(count > 0);
2473 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002474 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002475 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002476 SkScalar y = pts[i].fY;
2477 if (y > max) {
2478 max = y;
2479 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002480 }
2481 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002482 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002483}
2484
reed@google.comcabaf1d2012-01-11 21:03:05 +00002485static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2486 int i = index;
2487 for (;;) {
2488 i = (i + inc) % n;
2489 if (i == index) { // we wrapped around, so abort
2490 break;
2491 }
2492 if (pts[index] != pts[i]) { // found a different point, success!
2493 break;
2494 }
2495 }
2496 return i;
2497}
2498
reed@google.comc1ea60a2012-01-31 15:15:36 +00002499/**
2500 * Starting at index, and moving forward (incrementing), find the xmin and
2501 * xmax of the contiguous points that have the same Y.
2502 */
2503static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2504 int* maxIndexPtr) {
2505 const SkScalar y = pts[index].fY;
2506 SkScalar min = pts[index].fX;
2507 SkScalar max = min;
2508 int minIndex = index;
2509 int maxIndex = index;
2510 for (int i = index + 1; i < n; ++i) {
2511 if (pts[i].fY != y) {
2512 break;
2513 }
2514 SkScalar x = pts[i].fX;
2515 if (x < min) {
2516 min = x;
2517 minIndex = i;
2518 } else if (x > max) {
2519 max = x;
2520 maxIndex = i;
2521 }
2522 }
2523 *maxIndexPtr = maxIndex;
2524 return minIndex;
2525}
2526
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002527static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002528 if (dir) {
2529 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2530 }
reed@google.comac8543f2012-01-30 20:51:25 +00002531}
2532
reed@google.comc1ea60a2012-01-31 15:15:36 +00002533#if 0
2534#include "SkString.h"
2535#include "../utils/SkParsePath.h"
2536static void dumpPath(const SkPath& path) {
2537 SkString str;
2538 SkParsePath::ToSVGString(path, &str);
2539 SkDebugf("%s\n", str.c_str());
2540}
2541#endif
2542
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002543namespace {
2544// for use with convex_dir_test
2545double mul(double a, double b) { return a * b; }
2546SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2547double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2548SkScalar toScalar(SkScalar a) { return a; }
2549
2550// determines the winding direction of a convex polygon with the precision
2551// of T. CAST_SCALAR casts an SkScalar to T.
2552template <typename T, T (CAST_SCALAR)(SkScalar)>
2553bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2554 // we find the first three points that form a non-degenerate
2555 // triangle. If there are no such points then the path is
2556 // degenerate. The first is always point 0. Now we find the second
2557 // point.
2558 int i = 0;
2559 enum { kX = 0, kY = 1 };
2560 T v0[2];
2561 while (1) {
2562 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2563 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2564 if (v0[kX] || v0[kY]) {
2565 break;
2566 }
2567 if (++i == n - 1) {
2568 return false;
2569 }
2570 }
2571 // now find a third point that is not colinear with the first two
2572 // points and check the orientation of the triangle (which will be
2573 // the same as the orientation of the path).
2574 for (++i; i < n; ++i) {
2575 T v1[2];
2576 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2577 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2578 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2579 if (0 != cross) {
2580 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2581 return true;
2582 }
2583 }
2584 return false;
2585}
2586}
2587
reed@google.comac8543f2012-01-30 20:51:25 +00002588/*
2589 * We loop through all contours, and keep the computed cross-product of the
2590 * contour that contained the global y-max. If we just look at the first
2591 * contour, we may find one that is wound the opposite way (correctly) since
2592 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2593 * that is outer most (or at least has the global y-max) before we can consider
2594 * its cross product.
2595 */
reed@google.com69a99432012-01-10 18:00:10 +00002596bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002597// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002598 // don't want to pay the cost for computing this if it
2599 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002600
2601 if (kUnknown_Direction != fDirection) {
2602 *dir = static_cast<Direction>(fDirection);
2603 return true;
2604 }
reed@google.com69a99432012-01-10 18:00:10 +00002605 const Convexity conv = this->getConvexityOrUnknown();
2606
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002607 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002608
reed@google.comac8543f2012-01-30 20:51:25 +00002609 // initialize with our logical y-min
2610 SkScalar ymax = this->getBounds().fTop;
2611 SkScalar ymaxCross = 0;
2612
reed@google.com69a99432012-01-10 18:00:10 +00002613 for (; !iter.done(); iter.next()) {
2614 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002615 if (n < 3) {
2616 continue;
2617 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002618
reed@google.comcabaf1d2012-01-11 21:03:05 +00002619 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002620 SkScalar cross = 0;
2621 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002622 // We try first at scalar precision, and then again at double
2623 // precision. This is because the vectors computed between distant
2624 // points may lose too much precision.
2625 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002626 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002627 return true;
2628 }
2629 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002630 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002631 return true;
2632 } else {
2633 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002634 }
2635 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002636 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002637 if (pts[index].fY < ymax) {
2638 continue;
2639 }
2640
reed@google.comc1ea60a2012-01-31 15:15:36 +00002641 // If there is more than 1 distinct point at the y-max, we take the
2642 // x-min and x-max of them and just subtract to compute the dir.
2643 if (pts[(index + 1) % n].fY == pts[index].fY) {
2644 int maxIndex;
2645 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002646 if (minIndex == maxIndex) {
2647 goto TRY_CROSSPROD;
2648 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002649 SkASSERT(pts[minIndex].fY == pts[index].fY);
2650 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2651 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2652 // we just subtract the indices, and let that auto-convert to
2653 // SkScalar, since we just want - or + to signal the direction.
2654 cross = minIndex - maxIndex;
2655 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002656 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002657 // Find a next and prev index to use for the cross-product test,
2658 // but we try to find pts that form non-zero vectors from pts[index]
2659 //
2660 // Its possible that we can't find two non-degenerate vectors, so
2661 // we have to guard our search (e.g. all the pts could be in the
2662 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002663
reed@google.comc1ea60a2012-01-31 15:15:36 +00002664 // we pass n - 1 instead of -1 so we don't foul up % operator by
2665 // passing it a negative LH argument.
2666 int prev = find_diff_pt(pts, index, n, n - 1);
2667 if (prev == index) {
2668 // completely degenerate, skip to next contour
2669 continue;
2670 }
2671 int next = find_diff_pt(pts, index, n, 1);
2672 SkASSERT(next != index);
2673 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002674 // 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 +00002675 // x-direction. We really should continue to walk away from the degeneracy until
2676 // there is a divergence.
2677 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002678 // construct the subtract so we get the correct Direction below
2679 cross = pts[index].fX - pts[next].fX;
2680 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002681 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002682
reed@google.comac8543f2012-01-30 20:51:25 +00002683 if (cross) {
2684 // record our best guess so far
2685 ymax = pts[index].fY;
2686 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002687 }
reed@google.com69a99432012-01-10 18:00:10 +00002688 }
2689 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002690 if (ymaxCross) {
2691 crossToDir(ymaxCross, dir);
2692 fDirection = *dir;
2693 return true;
2694 } else {
2695 return false;
2696 }
reed@google.comac8543f2012-01-30 20:51:25 +00002697}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002698
2699///////////////////////////////////////////////////////////////////////////////
2700
2701static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2702 SkScalar D, SkScalar t) {
2703 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2704}
2705
2706static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2707 SkScalar t) {
2708 SkScalar A = c3 + 3*(c1 - c2) - c0;
2709 SkScalar B = 3*(c2 - c1 - c1 + c0);
2710 SkScalar C = 3*(c1 - c0);
2711 SkScalar D = c0;
2712 return eval_cubic_coeff(A, B, C, D, t);
2713}
2714
2715/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2716 t value such that cubic(t) = target
2717 */
2718static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2719 SkScalar target, SkScalar* t) {
2720 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2721 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002722
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002723 SkScalar D = c0 - target;
2724 SkScalar A = c3 + 3*(c1 - c2) - c0;
2725 SkScalar B = 3*(c2 - c1 - c1 + c0);
2726 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002727
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002728 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2729 SkScalar minT = 0;
2730 SkScalar maxT = SK_Scalar1;
2731 SkScalar mid;
2732 int i;
2733 for (i = 0; i < 16; i++) {
2734 mid = SkScalarAve(minT, maxT);
2735 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2736 if (delta < 0) {
2737 minT = mid;
2738 delta = -delta;
2739 } else {
2740 maxT = mid;
2741 }
2742 if (delta < TOLERANCE) {
2743 break;
2744 }
2745 }
2746 *t = mid;
2747 return true;
2748}
2749
2750template <size_t N> static void find_minmax(const SkPoint pts[],
2751 SkScalar* minPtr, SkScalar* maxPtr) {
2752 SkScalar min, max;
2753 min = max = pts[0].fX;
2754 for (size_t i = 1; i < N; ++i) {
2755 min = SkMinScalar(min, pts[i].fX);
2756 max = SkMaxScalar(max, pts[i].fX);
2757 }
2758 *minPtr = min;
2759 *maxPtr = max;
2760}
2761
2762static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2763 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002764
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002765 int dir = 1;
2766 if (pts[0].fY > pts[3].fY) {
2767 storage[0] = pts[3];
2768 storage[1] = pts[2];
2769 storage[2] = pts[1];
2770 storage[3] = pts[0];
2771 pts = storage;
2772 dir = -1;
2773 }
2774 if (y < pts[0].fY || y >= pts[3].fY) {
2775 return 0;
2776 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002777
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002778 // quickreject or quickaccept
2779 SkScalar min, max;
2780 find_minmax<4>(pts, &min, &max);
2781 if (x < min) {
2782 return 0;
2783 }
2784 if (x > max) {
2785 return dir;
2786 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002787
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002788 // compute the actual x(t) value
2789 SkScalar t, xt;
2790 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2791 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2792 } else {
2793 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2794 xt = y < mid ? pts[0].fX : pts[3].fX;
2795 }
2796 return xt < x ? dir : 0;
2797}
2798
2799static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2800 SkPoint dst[10];
2801 int n = SkChopCubicAtYExtrema(pts, dst);
2802 int w = 0;
2803 for (int i = 0; i <= n; ++i) {
2804 w += winding_mono_cubic(&dst[i * 3], x, y);
2805 }
2806 return w;
2807}
2808
2809static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2810 SkScalar y0 = pts[0].fY;
2811 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002812
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002813 int dir = 1;
2814 if (y0 > y2) {
2815 SkTSwap(y0, y2);
2816 dir = -1;
2817 }
2818 if (y < y0 || y >= y2) {
2819 return 0;
2820 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002821
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002822 // bounds check on X (not required. is it faster?)
2823#if 0
2824 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2825 return 0;
2826 }
2827#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002828
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002829 SkScalar roots[2];
2830 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2831 2 * (pts[1].fY - pts[0].fY),
2832 pts[0].fY - y,
2833 roots);
2834 SkASSERT(n <= 1);
2835 SkScalar xt;
2836 if (0 == n) {
2837 SkScalar mid = SkScalarAve(y0, y2);
2838 // Need [0] and [2] if dir == 1
2839 // and [2] and [0] if dir == -1
2840 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2841 } else {
2842 SkScalar t = roots[0];
2843 SkScalar C = pts[0].fX;
2844 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2845 SkScalar B = 2 * (pts[1].fX - C);
2846 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2847 }
2848 return xt < x ? dir : 0;
2849}
2850
2851static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2852 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2853 if (y0 == y1) {
2854 return true;
2855 }
2856 if (y0 < y1) {
2857 return y1 <= y2;
2858 } else {
2859 return y1 >= y2;
2860 }
2861}
2862
2863static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2864 SkPoint dst[5];
2865 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002866
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002867 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2868 n = SkChopQuadAtYExtrema(pts, dst);
2869 pts = dst;
2870 }
2871 int w = winding_mono_quad(pts, x, y);
2872 if (n > 0) {
2873 w += winding_mono_quad(&pts[2], x, y);
2874 }
2875 return w;
2876}
2877
2878static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2879 SkScalar x0 = pts[0].fX;
2880 SkScalar y0 = pts[0].fY;
2881 SkScalar x1 = pts[1].fX;
2882 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002883
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002884 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002885
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002886 int dir = 1;
2887 if (y0 > y1) {
2888 SkTSwap(y0, y1);
2889 dir = -1;
2890 }
2891 if (y < y0 || y >= y1) {
2892 return 0;
2893 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002894
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002895 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2896 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002897
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002898 if (SkScalarSignAsInt(cross) == dir) {
2899 dir = 0;
2900 }
2901 return dir;
2902}
2903
2904bool SkPath::contains(SkScalar x, SkScalar y) const {
2905 bool isInverse = this->isInverseFillType();
2906 if (this->isEmpty()) {
2907 return isInverse;
2908 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002909
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002910 const SkRect& bounds = this->getBounds();
2911 if (!bounds.contains(x, y)) {
2912 return isInverse;
2913 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002914
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002915 SkPath::Iter iter(*this, true);
2916 bool done = false;
2917 int w = 0;
2918 do {
2919 SkPoint pts[4];
2920 switch (iter.next(pts, false)) {
2921 case SkPath::kMove_Verb:
2922 case SkPath::kClose_Verb:
2923 break;
2924 case SkPath::kLine_Verb:
2925 w += winding_line(pts, x, y);
2926 break;
2927 case SkPath::kQuad_Verb:
2928 w += winding_quad(pts, x, y);
2929 break;
2930 case SkPath::kCubic_Verb:
2931 w += winding_cubic(pts, x, y);
2932 break;
2933 case SkPath::kDone_Verb:
2934 done = true;
2935 break;
2936 }
2937 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002938
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002939 switch (this->getFillType()) {
2940 case SkPath::kEvenOdd_FillType:
2941 case SkPath::kInverseEvenOdd_FillType:
2942 w &= 1;
2943 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002944 default:
2945 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002946 }
2947 return SkToBool(w);
2948}
2949