blob: ae1d187d0ddf130437dbd9c02f01a92e00436728 [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) {
1038 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
1039 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +00001040
reed@android.com8a1c16f2008-12-17 15:59:43 +00001041 SkRect r;
1042 r.set(-rx, -ry, rx, ry);
1043
1044 switch (startAngle) {
1045 case 0:
1046 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
1047 break;
1048 case 90:
1049 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
1050 break;
1051 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
1052 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001053 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001054 }
reed@google.comabf15c12011-01-18 20:35:51 +00001055
reed@android.com8a1c16f2008-12-17 15:59:43 +00001056 SkScalar start = SkIntToScalar(startAngle);
1057 SkScalar sweep = SkIntToScalar(90);
1058 if (SkPath::kCCW_Direction == dir) {
1059 start += sweep;
1060 sweep = -sweep;
1061 }
reed@google.comabf15c12011-01-18 20:35:51 +00001062
reed@android.com8a1c16f2008-12-17 15:59:43 +00001063 path->arcTo(r, start, sweep, forceMoveTo);
1064}
1065
1066void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
1067 Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001068 assert_known_direction(dir);
1069
reed@google.com44b2c732011-01-18 20:55:57 +00001070 // abort before we invoke SkAutoPathBoundsUpdate()
1071 if (rect.isEmpty()) {
1072 return;
1073 }
1074
reed@android.com8a1c16f2008-12-17 15:59:43 +00001075 SkAutoPathBoundsUpdate apbu(this, rect);
1076
1077 if (kCW_Direction == dir) {
1078 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
1079 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
1080 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
1081 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
1082 } else {
1083 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
1084 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
1085 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
1086 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
1087 }
1088 this->close();
1089}
1090
reed@google.com4ed0fb72012-12-12 20:48:18 +00001091void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
1092 const SkRect& bounds = rrect.getBounds();
1093
1094 if (rrect.isRect()) {
1095 this->addRect(bounds, dir);
1096 } else if (rrect.isOval()) {
1097 this->addOval(bounds, dir);
1098 } else if (rrect.isSimple()) {
1099 const SkVector& rad = rrect.getSimpleRadii();
1100 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1101 } else {
1102 this->addRoundRect(bounds, (const SkScalar*)&rrect.fRadii[0], dir);
1103 }
1104}
1105
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001106bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001107 int count = fPathRef->countVerbs();
1108 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1109 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001110 if (*verbs == kLine_Verb ||
1111 *verbs == kQuad_Verb ||
1112 *verbs == kCubic_Verb) {
1113 return false;
1114 }
1115 ++verbs;
1116 }
1117 return true;
1118}
1119
reed@android.com8a1c16f2008-12-17 15:59:43 +00001120void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001121 assert_known_direction(dir);
1122
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001123 /* If addOval() is called after previous moveTo(),
1124 this path is still marked as an oval. This is used to
1125 fit into WebKit's calling sequences.
1126 We can't simply check isEmpty() in this case, as additional
1127 moveTo() would mark the path non empty.
1128 */
1129 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001130 if (fIsOval) {
1131 fDirection = dir;
1132 } else {
1133 fDirection = kUnknown_Direction;
1134 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001135
1136 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001137 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001138
reed@android.com8a1c16f2008-12-17 15:59:43 +00001139 SkAutoPathBoundsUpdate apbu(this, oval);
1140
1141 SkScalar cx = oval.centerX();
1142 SkScalar cy = oval.centerY();
1143 SkScalar rx = SkScalarHalf(oval.width());
1144 SkScalar ry = SkScalarHalf(oval.height());
1145#if 0 // these seem faster than using quads (1/2 the number of edges)
1146 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1147 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
1148
1149 this->incReserve(13);
1150 this->moveTo(cx + rx, cy);
1151 if (dir == kCCW_Direction) {
1152 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
1153 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
1154 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
1155 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
1156 } else {
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 }
1162#else
1163 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1164 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1165 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1166 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1167
1168 /*
1169 To handle imprecision in computing the center and radii, we revert to
1170 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1171 to ensure that we don't exceed the oval's bounds *ever*, since we want
1172 to use oval for our fast-bounds, rather than have to recompute it.
1173 */
1174 const SkScalar L = oval.fLeft; // cx - rx
1175 const SkScalar T = oval.fTop; // cy - ry
1176 const SkScalar R = oval.fRight; // cx + rx
1177 const SkScalar B = oval.fBottom; // cy + ry
1178
1179 this->incReserve(17); // 8 quads + close
1180 this->moveTo(R, cy);
1181 if (dir == kCCW_Direction) {
1182 this->quadTo( R, cy - sy, cx + mx, cy - my);
1183 this->quadTo(cx + sx, T, cx , T);
1184 this->quadTo(cx - sx, T, cx - mx, cy - my);
1185 this->quadTo( L, cy - sy, L, cy );
1186 this->quadTo( L, cy + sy, cx - mx, cy + my);
1187 this->quadTo(cx - sx, B, cx , B);
1188 this->quadTo(cx + sx, B, cx + mx, cy + my);
1189 this->quadTo( R, cy + sy, R, cy );
1190 } else {
1191 this->quadTo( R, 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( L, cy + sy, L, cy );
1195 this->quadTo( L, cy - sy, cx - mx, cy - my);
1196 this->quadTo(cx - sx, T, cx , T);
1197 this->quadTo(cx + sx, T, cx + mx, cy - my);
1198 this->quadTo( R, cy - sy, R, cy );
1199 }
1200#endif
1201 this->close();
1202}
1203
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001204bool SkPath::isOval(SkRect* rect) const {
1205 if (fIsOval && rect) {
1206 *rect = getBounds();
1207 }
1208
1209 return fIsOval;
1210}
1211
reed@android.com8a1c16f2008-12-17 15:59:43 +00001212void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1213 if (r > 0) {
1214 SkRect rect;
1215 rect.set(x - r, y - r, x + r, y + r);
1216 this->addOval(rect, dir);
1217 }
1218}
1219
1220#include "SkGeometry.h"
1221
1222static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1223 SkScalar sweepAngle,
1224 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001225
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001226 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001227 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1228 // Chrome uses this path to move into and out of ovals. If not
1229 // treated as a special case the moves can distort the oval's
1230 // bounding box (and break the circle special case).
1231 pts[0].set(oval.fRight, oval.centerY());
1232 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001233 } else if (0 == oval.width() && 0 == oval.height()) {
1234 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001235 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001236 // a rect.
1237 // TODO: optimizing the case where only one of width or height is zero
1238 // should also be considered. This case, however, doesn't seem to be
1239 // as common as the single point case.
1240 pts[0].set(oval.fRight, oval.fTop);
1241 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001242 }
1243
reed@android.com8a1c16f2008-12-17 15:59:43 +00001244 SkVector start, stop;
1245
1246 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1247 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1248 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001249
1250 /* If the sweep angle is nearly (but less than) 360, then due to precision
1251 loss in radians-conversion and/or sin/cos, we may end up with coincident
1252 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1253 of drawing a nearly complete circle (good).
1254 e.g. canvas.drawArc(0, 359.99, ...)
1255 -vs- canvas.drawArc(0, 359.9, ...)
1256 We try to detect this edge case, and tweak the stop vector
1257 */
1258 if (start == stop) {
1259 SkScalar sw = SkScalarAbs(sweepAngle);
1260 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1261 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1262 // make a guess at a tiny angle (in radians) to tweak by
1263 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1264 // not sure how much will be enough, so we use a loop
1265 do {
1266 stopRad -= deltaRad;
1267 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1268 } while (start == stop);
1269 }
1270 }
1271
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001273
reed@android.com8a1c16f2008-12-17 15:59:43 +00001274 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1275 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001276
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277 return SkBuildQuadArc(start, stop,
1278 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1279 &matrix, pts);
1280}
1281
1282void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1283 bool forceMoveTo) {
1284 if (oval.width() < 0 || oval.height() < 0) {
1285 return;
1286 }
1287
1288 SkPoint pts[kSkBuildQuadArcStorage];
1289 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1290 SkASSERT((count & 1) == 1);
1291
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001292 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001293 forceMoveTo = true;
1294 }
1295 this->incReserve(count);
1296 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1297 for (int i = 1; i < count; i += 2) {
1298 this->quadTo(pts[i], pts[i+1]);
1299 }
1300}
1301
1302void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1303 SkScalar sweepAngle) {
1304 if (oval.isEmpty() || 0 == sweepAngle) {
1305 return;
1306 }
1307
1308 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1309
1310 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1311 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1312 return;
1313 }
1314
1315 SkPoint pts[kSkBuildQuadArcStorage];
1316 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1317
1318 this->incReserve(count);
1319 this->moveTo(pts[0]);
1320 for (int i = 1; i < count; i += 2) {
1321 this->quadTo(pts[i], pts[i+1]);
1322 }
1323}
1324
1325/*
1326 Need to handle the case when the angle is sharp, and our computed end-points
1327 for the arc go behind pt1 and/or p2...
1328*/
1329void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1330 SkScalar radius) {
1331 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001332
reed@android.com8a1c16f2008-12-17 15:59:43 +00001333 // need to know our prev pt so we can construct tangent vectors
1334 {
1335 SkPoint start;
1336 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001337 // Handle degenerate cases by adding a line to the first point and
1338 // bailing out.
1339 if ((x1 == start.fX && y1 == start.fY) ||
1340 (x1 == x2 && y1 == y2) ||
1341 radius == 0) {
1342 this->lineTo(x1, y1);
1343 return;
1344 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345 before.setNormalize(x1 - start.fX, y1 - start.fY);
1346 after.setNormalize(x2 - x1, y2 - y1);
1347 }
reed@google.comabf15c12011-01-18 20:35:51 +00001348
reed@android.com8a1c16f2008-12-17 15:59:43 +00001349 SkScalar cosh = SkPoint::DotProduct(before, after);
1350 SkScalar sinh = SkPoint::CrossProduct(before, after);
1351
1352 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001353 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001354 return;
1355 }
reed@google.comabf15c12011-01-18 20:35:51 +00001356
reed@android.com8a1c16f2008-12-17 15:59:43 +00001357 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1358 if (dist < 0) {
1359 dist = -dist;
1360 }
1361
1362 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1363 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1364 SkRotationDirection arcDir;
1365
1366 // now turn before/after into normals
1367 if (sinh > 0) {
1368 before.rotateCCW();
1369 after.rotateCCW();
1370 arcDir = kCW_SkRotationDirection;
1371 } else {
1372 before.rotateCW();
1373 after.rotateCW();
1374 arcDir = kCCW_SkRotationDirection;
1375 }
1376
1377 SkMatrix matrix;
1378 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001379
reed@android.com8a1c16f2008-12-17 15:59:43 +00001380 matrix.setScale(radius, radius);
1381 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1382 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001383
reed@android.com8a1c16f2008-12-17 15:59:43 +00001384 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001385
reed@android.com8a1c16f2008-12-17 15:59:43 +00001386 this->incReserve(count);
1387 // [xx,yy] == pts[0]
1388 this->lineTo(xx, yy);
1389 for (int i = 1; i < count; i += 2) {
1390 this->quadTo(pts[i], pts[i+1]);
1391 }
1392}
1393
1394///////////////////////////////////////////////////////////////////////////////
1395
1396void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1397 SkMatrix matrix;
1398
1399 matrix.setTranslate(dx, dy);
1400 this->addPath(path, matrix);
1401}
1402
1403void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001404 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001405
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001406 fIsOval = false;
1407
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001408 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001409 SkPoint pts[4];
1410 Verb verb;
1411
1412 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1413
1414 while ((verb = iter.next(pts)) != kDone_Verb) {
1415 switch (verb) {
1416 case kMove_Verb:
1417 proc(matrix, &pts[0], &pts[0], 1);
1418 this->moveTo(pts[0]);
1419 break;
1420 case kLine_Verb:
1421 proc(matrix, &pts[1], &pts[1], 1);
1422 this->lineTo(pts[1]);
1423 break;
1424 case kQuad_Verb:
1425 proc(matrix, &pts[1], &pts[1], 2);
1426 this->quadTo(pts[1], pts[2]);
1427 break;
1428 case kCubic_Verb:
1429 proc(matrix, &pts[1], &pts[1], 3);
1430 this->cubicTo(pts[1], pts[2], pts[3]);
1431 break;
1432 case kClose_Verb:
1433 this->close();
1434 break;
1435 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001436 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001437 }
1438 }
1439}
1440
1441///////////////////////////////////////////////////////////////////////////////
1442
1443static const uint8_t gPtsInVerb[] = {
1444 1, // kMove
1445 1, // kLine
1446 2, // kQuad
1447 3, // kCubic
1448 0, // kClose
1449 0 // kDone
1450};
1451
1452// ignore the initial moveto, and stop when the 1st contour ends
1453void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001454 int i, vcount = path.fPathRef->countVerbs();
1455 // exit early if the path is empty, or just has a moveTo.
1456 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 return;
1458 }
1459
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001460 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001461
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001462 fIsOval = false;
1463
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001464 const uint8_t* verbs = path.fPathRef->verbs();
1465 // skip the initial moveTo
1466 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001468 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001470 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001471 case kLine_Verb:
1472 this->lineTo(pts[0].fX, pts[0].fY);
1473 break;
1474 case kQuad_Verb:
1475 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1476 break;
1477 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001478 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 +00001479 break;
1480 case kClose_Verb:
1481 return;
1482 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001483 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001484 }
1485}
1486
1487// ignore the last point of the 1st contour
1488void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001489 int i, vcount = path.fPathRef->countVerbs();
1490 // exit early if the path is empty, or just has a moveTo.
1491 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492 return;
1493 }
1494
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001495 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001497 fIsOval = false;
1498
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001499 const uint8_t* verbs = path.fPathRef->verbs();
1500 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001502 SkASSERT(verbs[~0] == kMove_Verb);
1503 for (i = 1; i < vcount; ++i) {
1504 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001505 if (n == 0) {
1506 break;
1507 }
1508 pts += n;
1509 }
1510
1511 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001512 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513 case kLine_Verb:
1514 this->lineTo(pts[-1].fX, pts[-1].fY);
1515 break;
1516 case kQuad_Verb:
1517 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1518 break;
1519 case kCubic_Verb:
1520 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1521 pts[-3].fX, pts[-3].fY);
1522 break;
1523 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001524 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001525 break;
1526 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001527 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528 }
1529}
1530
reed@google.com63d73742012-01-10 15:33:12 +00001531void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001532 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001533
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001534 const SkPoint* pts = src.fPathRef->pointsEnd();
1535 // we will iterator through src's verbs backwards
1536 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1537 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001538
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001539 fIsOval = false;
1540
reed@google.com63d73742012-01-10 15:33:12 +00001541 bool needMove = true;
1542 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001543 while (verbs < verbsEnd) {
1544 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001545 int n = gPtsInVerb[v];
1546
1547 if (needMove) {
1548 --pts;
1549 this->moveTo(pts->fX, pts->fY);
1550 needMove = false;
1551 }
1552 pts -= n;
1553 switch (v) {
1554 case kMove_Verb:
1555 if (needClose) {
1556 this->close();
1557 needClose = false;
1558 }
1559 needMove = true;
1560 pts += 1; // so we see the point in "if (needMove)" above
1561 break;
1562 case kLine_Verb:
1563 this->lineTo(pts[0]);
1564 break;
1565 case kQuad_Verb:
1566 this->quadTo(pts[1], pts[0]);
1567 break;
1568 case kCubic_Verb:
1569 this->cubicTo(pts[2], pts[1], pts[0]);
1570 break;
1571 case kClose_Verb:
1572 needClose = true;
1573 break;
1574 default:
1575 SkASSERT(!"unexpected verb");
1576 }
1577 }
1578}
1579
reed@android.com8a1c16f2008-12-17 15:59:43 +00001580///////////////////////////////////////////////////////////////////////////////
1581
1582void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1583 SkMatrix matrix;
1584
1585 matrix.setTranslate(dx, dy);
1586 this->transform(matrix, dst);
1587}
1588
1589#include "SkGeometry.h"
1590
1591static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1592 int level = 2) {
1593 if (--level >= 0) {
1594 SkPoint tmp[5];
1595
1596 SkChopQuadAtHalf(pts, tmp);
1597 subdivide_quad_to(path, &tmp[0], level);
1598 subdivide_quad_to(path, &tmp[2], level);
1599 } else {
1600 path->quadTo(pts[1], pts[2]);
1601 }
1602}
1603
1604static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1605 int level = 2) {
1606 if (--level >= 0) {
1607 SkPoint tmp[7];
1608
1609 SkChopCubicAtHalf(pts, tmp);
1610 subdivide_cubic_to(path, &tmp[0], level);
1611 subdivide_cubic_to(path, &tmp[3], level);
1612 } else {
1613 path->cubicTo(pts[1], pts[2], pts[3]);
1614 }
1615}
1616
1617void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1618 SkDEBUGCODE(this->validate();)
1619 if (dst == NULL) {
1620 dst = (SkPath*)this;
1621 }
1622
tomhudson@google.com8d430182011-06-06 19:11:19 +00001623 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001624 SkPath tmp;
1625 tmp.fFillType = fFillType;
1626
1627 SkPath::Iter iter(*this, false);
1628 SkPoint pts[4];
1629 SkPath::Verb verb;
1630
reed@google.com4a3b7142012-05-16 17:16:46 +00001631 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632 switch (verb) {
1633 case kMove_Verb:
1634 tmp.moveTo(pts[0]);
1635 break;
1636 case kLine_Verb:
1637 tmp.lineTo(pts[1]);
1638 break;
1639 case kQuad_Verb:
1640 subdivide_quad_to(&tmp, pts);
1641 break;
1642 case kCubic_Verb:
1643 subdivide_cubic_to(&tmp, pts);
1644 break;
1645 case kClose_Verb:
1646 tmp.close();
1647 break;
1648 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001649 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001650 break;
1651 }
1652 }
1653
1654 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001655 SkPathRef::Editor ed(&dst->fPathRef);
1656 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001657 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001658 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001659 /*
1660 * If we're not in perspective, we can transform all of the points at
1661 * once.
1662 *
1663 * Here we also want to optimize bounds, by noting if the bounds are
1664 * already known, and if so, we just transform those as well and mark
1665 * them as "known", rather than force the transformed path to have to
1666 * recompute them.
1667 *
1668 * Special gotchas if the path is effectively empty (<= 1 point) or
1669 * if it is non-finite. In those cases bounds need to stay empty,
1670 * regardless of the matrix.
1671 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001672 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001673 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001674 if (fIsFinite) {
1675 matrix.mapRect(&dst->fBounds, fBounds);
1676 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1677 dst->fBounds.setEmpty();
1678 }
1679 } else {
1680 dst->fIsFinite = false;
1681 dst->fBounds.setEmpty();
1682 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001683 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001684 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001685 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 }
1687
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001688 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1689
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001692 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001693 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001695
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001696 if (kUnknown_Direction == fDirection) {
1697 dst->fDirection = kUnknown_Direction;
1698 } else {
1699 SkScalar det2x2 =
1700 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1701 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1702 if (det2x2 < 0) {
1703 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1704 } else if (det2x2 > 0) {
1705 dst->fDirection = fDirection;
1706 } else {
1707 dst->fDirection = kUnknown_Direction;
1708 }
1709 }
1710
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001711 // It's an oval only if it stays a rect.
1712 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001713
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 SkDEBUGCODE(dst->validate();)
1715 }
1716}
1717
reed@android.com8a1c16f2008-12-17 15:59:43 +00001718///////////////////////////////////////////////////////////////////////////////
1719///////////////////////////////////////////////////////////////////////////////
1720
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001721enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001722 kEmptyContour_SegmentState, // The current contour is empty. We may be
1723 // starting processing or we may have just
1724 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001725 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1726 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1727 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001728};
1729
1730SkPath::Iter::Iter() {
1731#ifdef SK_DEBUG
1732 fPts = NULL;
1733 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001734 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001735 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001736#endif
1737 // need to init enough to make next() harmlessly return kDone_Verb
1738 fVerbs = NULL;
1739 fVerbStop = NULL;
1740 fNeedClose = false;
1741}
1742
1743SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1744 this->setPath(path, forceClose);
1745}
1746
1747void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001748 fPts = path.fPathRef->points();
1749 fVerbs = path.fPathRef->verbs();
1750 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001751 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001752 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001753 fForceClose = SkToU8(forceClose);
1754 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001755 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756}
1757
1758bool SkPath::Iter::isClosedContour() const {
1759 if (fVerbs == NULL || fVerbs == fVerbStop) {
1760 return false;
1761 }
1762 if (fForceClose) {
1763 return true;
1764 }
1765
1766 const uint8_t* verbs = fVerbs;
1767 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001768
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001769 if (kMove_Verb == *(verbs - 1)) {
1770 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001771 }
1772
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001773 while (verbs > stop) {
1774 // verbs points one beyond the current verb, decrement first.
1775 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001776 if (kMove_Verb == v) {
1777 break;
1778 }
1779 if (kClose_Verb == v) {
1780 return true;
1781 }
1782 }
1783 return false;
1784}
1785
1786SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001787 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001788 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001789 // A special case: if both points are NaN, SkPoint::operation== returns
1790 // false, but the iterator expects that they are treated as the same.
1791 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001792 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1793 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001794 return kClose_Verb;
1795 }
1796
reed@google.com9e25dbf2012-05-15 17:05:38 +00001797 pts[0] = fLastPt;
1798 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799 fLastPt = fMoveTo;
1800 fCloseLine = true;
1801 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001802 } else {
1803 pts[0] = fMoveTo;
1804 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001806}
1807
reed@google.com9e25dbf2012-05-15 17:05:38 +00001808const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001809 if (fSegmentState == kAfterMove_SegmentState) {
1810 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001811 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001812 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001813 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001814 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1815 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001816 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818}
1819
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820void SkPath::Iter::consumeDegenerateSegments() {
1821 // We need to step over anything that will not move the current draw point
1822 // forward before the next move is seen
1823 const uint8_t* lastMoveVerb = 0;
1824 const SkPoint* lastMovePt = 0;
1825 SkPoint lastPt = fLastPt;
1826 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001827 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001828 switch (verb) {
1829 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001830 // Keep a record of this most recent move
1831 lastMoveVerb = fVerbs;
1832 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001833 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001834 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001835 fPts++;
1836 break;
1837
1838 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001839 // A close when we are in a segment is always valid except when it
1840 // follows a move which follows a segment.
1841 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001842 return;
1843 }
1844 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001845 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001846 break;
1847
1848 case kLine_Verb:
1849 if (!IsLineDegenerate(lastPt, fPts[0])) {
1850 if (lastMoveVerb) {
1851 fVerbs = lastMoveVerb;
1852 fPts = lastMovePt;
1853 return;
1854 }
1855 return;
1856 }
1857 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001858 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001859 fPts++;
1860 break;
1861
1862 case kQuad_Verb:
1863 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1864 if (lastMoveVerb) {
1865 fVerbs = lastMoveVerb;
1866 fPts = lastMovePt;
1867 return;
1868 }
1869 return;
1870 }
1871 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001872 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001873 fPts += 2;
1874 break;
1875
1876 case kCubic_Verb:
1877 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1878 if (lastMoveVerb) {
1879 fVerbs = lastMoveVerb;
1880 fPts = lastMovePt;
1881 return;
1882 }
1883 return;
1884 }
1885 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001886 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001887 fPts += 3;
1888 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001889
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001890 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001891 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 }
1893 }
1894}
1895
reed@google.com4a3b7142012-05-16 17:16:46 +00001896SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001897 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001898
reed@android.com8a1c16f2008-12-17 15:59:43 +00001899 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001900 // Close the curve if requested and if there is some curve to close
1901 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001902 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001903 return kLine_Verb;
1904 }
1905 fNeedClose = false;
1906 return kClose_Verb;
1907 }
1908 return kDone_Verb;
1909 }
1910
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001911 // fVerbs is one beyond the current verb, decrement first
1912 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001913 const SkPoint* SK_RESTRICT srcPts = fPts;
1914 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001915
1916 switch (verb) {
1917 case kMove_Verb:
1918 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001919 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001920 verb = this->autoClose(pts);
1921 if (verb == kClose_Verb) {
1922 fNeedClose = false;
1923 }
1924 return (Verb)verb;
1925 }
1926 if (fVerbs == fVerbStop) { // might be a trailing moveto
1927 return kDone_Verb;
1928 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001929 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001930 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001932 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001933 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001934 fNeedClose = fForceClose;
1935 break;
1936 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001937 pts[0] = this->cons_moveTo();
1938 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001939 fLastPt = srcPts[0];
1940 fCloseLine = false;
1941 srcPts += 1;
1942 break;
1943 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001944 pts[0] = this->cons_moveTo();
1945 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001946 fLastPt = srcPts[1];
1947 srcPts += 2;
1948 break;
1949 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001950 pts[0] = this->cons_moveTo();
1951 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952 fLastPt = srcPts[2];
1953 srcPts += 3;
1954 break;
1955 case kClose_Verb:
1956 verb = this->autoClose(pts);
1957 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001958 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001959 } else {
1960 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001961 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001962 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001963 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 break;
1965 }
1966 fPts = srcPts;
1967 return (Verb)verb;
1968}
1969
1970///////////////////////////////////////////////////////////////////////////////
1971
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001972SkPath::RawIter::RawIter() {
1973#ifdef SK_DEBUG
1974 fPts = NULL;
1975 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1976#endif
1977 // need to init enough to make next() harmlessly return kDone_Verb
1978 fVerbs = NULL;
1979 fVerbStop = NULL;
1980}
1981
1982SkPath::RawIter::RawIter(const SkPath& path) {
1983 this->setPath(path);
1984}
1985
1986void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001987 fPts = path.fPathRef->points();
1988 fVerbs = path.fPathRef->verbs();
1989 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001990 fMoveTo.fX = fMoveTo.fY = 0;
1991 fLastPt.fX = fLastPt.fY = 0;
1992}
1993
1994SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001995 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001996 if (fVerbs == fVerbStop) {
1997 return kDone_Verb;
1998 }
1999
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002000 // fVerbs points one beyond next verb so decrement first.
2001 unsigned verb = *(--fVerbs);
2002 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002003
2004 switch (verb) {
2005 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002006 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002007 fMoveTo = srcPts[0];
2008 fLastPt = fMoveTo;
2009 srcPts += 1;
2010 break;
2011 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002012 pts[0] = fLastPt;
2013 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002014 fLastPt = srcPts[0];
2015 srcPts += 1;
2016 break;
2017 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002018 pts[0] = fLastPt;
2019 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002020 fLastPt = srcPts[1];
2021 srcPts += 2;
2022 break;
2023 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002024 pts[0] = fLastPt;
2025 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002026 fLastPt = srcPts[2];
2027 srcPts += 3;
2028 break;
2029 case kClose_Verb:
2030 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002031 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002032 break;
2033 }
2034 fPts = srcPts;
2035 return (Verb)verb;
2036}
2037
2038///////////////////////////////////////////////////////////////////////////////
2039
reed@android.com8a1c16f2008-12-17 15:59:43 +00002040/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002041 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042*/
2043
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002044uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002045 SkDEBUGCODE(this->validate();)
2046
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002047 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002048 const int byteCount = sizeof(int32_t)
2049#if NEW_PICTURE_FORMAT
2050 + fPathRef->writeSize()
2051#else
2052 + 2 * sizeof(int32_t)
2053 + sizeof(SkPoint) * fPathRef->countPoints()
2054 + sizeof(uint8_t) * fPathRef->countVerbs()
2055#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002056 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002057 return SkAlign4(byteCount);
2058 }
2059
2060 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002061#if !NEW_PICTURE_FORMAT
2062 buffer.write32(fPathRef->countPoints());
2063 buffer.write32(fPathRef->countVerbs());
2064#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002065
2066 // Call getBounds() to ensure (as a side-effect) that fBounds
2067 // and fIsFinite are computed.
2068 const SkRect& bounds = this->getBounds();
2069 SkASSERT(!fBoundsIsDirty);
2070
2071 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2072 ((fIsOval & 1) << kIsOval_SerializationShift) |
2073 (fConvexity << kConvexity_SerializationShift) |
2074 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002075 (fSegmentMask << kSegmentMask_SerializationShift) |
2076 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002077
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002078 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002079
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002080 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002081
2082 buffer.write(&bounds, sizeof(bounds));
2083
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002084 buffer.padToAlign4();
2085 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002086}
2087
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002088uint32_t SkPath::readFromMemory(const void* storage) {
2089 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002090#if !NEW_PICTURE_FORMAT
2091 int32_t pcount = buffer.readS32();
2092 int32_t vcount = buffer.readS32();
2093#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002094
reed@google.com98b11f12011-09-21 18:40:27 +00002095 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002096 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2097 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2098 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2099 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002100 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
2101 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002102
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002103#if NEW_PICTURE_FORMAT
2104 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
2105#else
2106 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
2107#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002108
2109 buffer.read(&fBounds, sizeof(fBounds));
2110 fBoundsIsDirty = false;
2111
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002112 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002113
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002114 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002115
2116 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002117 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118}
2119
2120///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121
reed@android.com8a1c16f2008-12-17 15:59:43 +00002122void SkPath::dump(bool forceClose, const char title[]) const {
2123 Iter iter(*this, forceClose);
2124 SkPoint pts[4];
2125 Verb verb;
2126
2127 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2128 title ? title : "");
2129
reed@google.com4a3b7142012-05-16 17:16:46 +00002130 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 switch (verb) {
2132 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002133 SkDebugf(" path: moveTo [%g %g]\n",
2134 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 break;
2136 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002137 SkDebugf(" path: lineTo [%g %g]\n",
2138 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002139 break;
2140 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002141 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
2142 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2143 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002144 break;
2145 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002146 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
2147 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2148 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
2149 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002150 break;
2151 case kClose_Verb:
2152 SkDebugf(" path: close\n");
2153 break;
2154 default:
2155 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2156 verb = kDone_Verb; // stop the loop
2157 break;
2158 }
2159 }
2160 SkDebugf("path: done %s\n", title ? title : "");
2161}
2162
reed@android.come522ca52009-11-23 20:10:41 +00002163void SkPath::dump() const {
2164 this->dump(false);
2165}
2166
2167#ifdef SK_DEBUG
2168void SkPath::validate() const {
2169 SkASSERT(this != NULL);
2170 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002171
djsollen@google.com077348c2012-10-22 20:23:32 +00002172#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002173 if (!fBoundsIsDirty) {
2174 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002175
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002176 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002177 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002178
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002179 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002180 // if we're empty, fBounds may be empty but translated, so we can't
2181 // necessarily compare to bounds directly
2182 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2183 // be [2, 2, 2, 2]
2184 SkASSERT(bounds.isEmpty());
2185 SkASSERT(fBounds.isEmpty());
2186 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002187 if (bounds.isEmpty()) {
2188 SkASSERT(fBounds.isEmpty());
2189 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002190 if (!fBounds.isEmpty()) {
2191 SkASSERT(fBounds.contains(bounds));
2192 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002193 }
reed@android.come522ca52009-11-23 20:10:41 +00002194 }
2195 }
reed@google.com10296cc2011-09-21 12:29:05 +00002196
2197 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002198 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2199 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2200 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002201 case kLine_Verb:
2202 mask |= kLine_SegmentMask;
2203 break;
2204 case kQuad_Verb:
2205 mask |= kQuad_SegmentMask;
2206 break;
2207 case kCubic_Verb:
2208 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002209 case kMove_Verb: // these verbs aren't included in the segment mask.
2210 case kClose_Verb:
2211 break;
2212 case kDone_Verb:
2213 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2214 break;
2215 default:
2216 SkDEBUGFAIL("Unknown Verb");
2217 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002218 }
2219 }
2220 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002221#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002222}
djsollen@google.com077348c2012-10-22 20:23:32 +00002223#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002224
reed@google.com04863fa2011-05-15 04:08:24 +00002225///////////////////////////////////////////////////////////////////////////////
2226
reed@google.com0b7b9822011-05-16 12:29:27 +00002227static int sign(SkScalar x) { return x < 0; }
2228#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002229
reed@google.com04863fa2011-05-15 04:08:24 +00002230static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002231 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002232}
2233
2234// only valid for a single contour
2235struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002236 Convexicator()
2237 : fPtCount(0)
2238 , fConvexity(SkPath::kConvex_Convexity)
2239 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002240 fSign = 0;
2241 // warnings
2242 fCurrPt.set(0, 0);
2243 fVec0.set(0, 0);
2244 fVec1.set(0, 0);
2245 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002246
2247 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002248 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002249 }
2250
2251 SkPath::Convexity getConvexity() const { return fConvexity; }
2252
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002253 /** The direction returned is only valid if the path is determined convex */
2254 SkPath::Direction getDirection() const { return fDirection; }
2255
reed@google.com04863fa2011-05-15 04:08:24 +00002256 void addPt(const SkPoint& pt) {
2257 if (SkPath::kConcave_Convexity == fConvexity) {
2258 return;
2259 }
2260
2261 if (0 == fPtCount) {
2262 fCurrPt = pt;
2263 ++fPtCount;
2264 } else {
2265 SkVector vec = pt - fCurrPt;
2266 if (vec.fX || vec.fY) {
2267 fCurrPt = pt;
2268 if (++fPtCount == 2) {
2269 fFirstVec = fVec1 = vec;
2270 } else {
2271 SkASSERT(fPtCount > 2);
2272 this->addVec(vec);
2273 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002274
reed@google.com85b6e392011-05-15 20:25:17 +00002275 int sx = sign(vec.fX);
2276 int sy = sign(vec.fY);
2277 fDx += (sx != fSx);
2278 fDy += (sy != fSy);
2279 fSx = sx;
2280 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002281
reed@google.com85b6e392011-05-15 20:25:17 +00002282 if (fDx > 3 || fDy > 3) {
2283 fConvexity = SkPath::kConcave_Convexity;
2284 }
reed@google.com04863fa2011-05-15 04:08:24 +00002285 }
2286 }
2287 }
2288
2289 void close() {
2290 if (fPtCount > 2) {
2291 this->addVec(fFirstVec);
2292 }
2293 }
2294
2295private:
2296 void addVec(const SkVector& vec) {
2297 SkASSERT(vec.fX || vec.fY);
2298 fVec0 = fVec1;
2299 fVec1 = vec;
2300 int sign = CrossProductSign(fVec0, fVec1);
2301 if (0 == fSign) {
2302 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002303 if (1 == sign) {
2304 fDirection = SkPath::kCW_Direction;
2305 } else if (-1 == sign) {
2306 fDirection = SkPath::kCCW_Direction;
2307 }
reed@google.com04863fa2011-05-15 04:08:24 +00002308 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002309 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002310 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002311 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002312 }
2313 }
2314 }
2315
2316 SkPoint fCurrPt;
2317 SkVector fVec0, fVec1, fFirstVec;
2318 int fPtCount; // non-degenerate points
2319 int fSign;
2320 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002321 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002322 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002323};
2324
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002325SkPath::Convexity SkPath::internalGetConvexity() const {
2326 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002327 SkPoint pts[4];
2328 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002329 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002330
2331 int contourCount = 0;
2332 int count;
2333 Convexicator state;
2334
2335 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2336 switch (verb) {
2337 case kMove_Verb:
2338 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002339 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002340 return kConcave_Convexity;
2341 }
2342 pts[1] = pts[0];
2343 count = 1;
2344 break;
2345 case kLine_Verb: count = 1; break;
2346 case kQuad_Verb: count = 2; break;
2347 case kCubic_Verb: count = 3; break;
2348 case kClose_Verb:
2349 state.close();
2350 count = 0;
2351 break;
2352 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002353 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002354 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002355 return kConcave_Convexity;
2356 }
2357
2358 for (int i = 1; i <= count; i++) {
2359 state.addPt(pts[i]);
2360 }
2361 // early exit
2362 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002363 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002364 return kConcave_Convexity;
2365 }
2366 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002367 fConvexity = state.getConvexity();
2368 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2369 fDirection = state.getDirection();
2370 }
2371 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002372}
reed@google.com69a99432012-01-10 18:00:10 +00002373
2374///////////////////////////////////////////////////////////////////////////////
2375
2376class ContourIter {
2377public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002378 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002379
2380 bool done() const { return fDone; }
2381 // if !done() then these may be called
2382 int count() const { return fCurrPtCount; }
2383 const SkPoint* pts() const { return fCurrPt; }
2384 void next();
2385
2386private:
2387 int fCurrPtCount;
2388 const SkPoint* fCurrPt;
2389 const uint8_t* fCurrVerb;
2390 const uint8_t* fStopVerbs;
2391 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002392 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002393};
2394
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002395ContourIter::ContourIter(const SkPathRef& pathRef) {
2396 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002397 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002398 fCurrPt = pathRef.points();
2399 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002400 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002401 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002402 this->next();
2403}
2404
2405void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002406 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002407 fDone = true;
2408 }
2409 if (fDone) {
2410 return;
2411 }
2412
2413 // skip pts of prev contour
2414 fCurrPt += fCurrPtCount;
2415
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002416 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002417 int ptCount = 1; // moveTo
2418 const uint8_t* verbs = fCurrVerb;
2419
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002420 for (--verbs; verbs > fStopVerbs; --verbs) {
2421 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002422 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002423 goto CONTOUR_END;
2424 case SkPath::kLine_Verb:
2425 ptCount += 1;
2426 break;
2427 case SkPath::kQuad_Verb:
2428 ptCount += 2;
2429 break;
2430 case SkPath::kCubic_Verb:
2431 ptCount += 3;
2432 break;
2433 default: // kClose_Verb, just keep going
2434 break;
2435 }
2436 }
2437CONTOUR_END:
2438 fCurrPtCount = ptCount;
2439 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002440 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002441}
2442
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002443// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002444static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002445 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2446 // We may get 0 when the above subtracts underflow. We expect this to be
2447 // very rare and lazily promote to double.
2448 if (0 == cross) {
2449 double p0x = SkScalarToDouble(p0.fX);
2450 double p0y = SkScalarToDouble(p0.fY);
2451
2452 double p1x = SkScalarToDouble(p1.fX);
2453 double p1y = SkScalarToDouble(p1.fY);
2454
2455 double p2x = SkScalarToDouble(p2.fX);
2456 double p2y = SkScalarToDouble(p2.fY);
2457
2458 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2459 (p1y - p0y) * (p2x - p0x));
2460
2461 }
2462 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002463}
2464
reed@google.comc1ea60a2012-01-31 15:15:36 +00002465// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002466static int find_max_y(const SkPoint pts[], int count) {
2467 SkASSERT(count > 0);
2468 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002469 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002470 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002471 SkScalar y = pts[i].fY;
2472 if (y > max) {
2473 max = y;
2474 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002475 }
2476 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002477 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002478}
2479
reed@google.comcabaf1d2012-01-11 21:03:05 +00002480static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2481 int i = index;
2482 for (;;) {
2483 i = (i + inc) % n;
2484 if (i == index) { // we wrapped around, so abort
2485 break;
2486 }
2487 if (pts[index] != pts[i]) { // found a different point, success!
2488 break;
2489 }
2490 }
2491 return i;
2492}
2493
reed@google.comc1ea60a2012-01-31 15:15:36 +00002494/**
2495 * Starting at index, and moving forward (incrementing), find the xmin and
2496 * xmax of the contiguous points that have the same Y.
2497 */
2498static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2499 int* maxIndexPtr) {
2500 const SkScalar y = pts[index].fY;
2501 SkScalar min = pts[index].fX;
2502 SkScalar max = min;
2503 int minIndex = index;
2504 int maxIndex = index;
2505 for (int i = index + 1; i < n; ++i) {
2506 if (pts[i].fY != y) {
2507 break;
2508 }
2509 SkScalar x = pts[i].fX;
2510 if (x < min) {
2511 min = x;
2512 minIndex = i;
2513 } else if (x > max) {
2514 max = x;
2515 maxIndex = i;
2516 }
2517 }
2518 *maxIndexPtr = maxIndex;
2519 return minIndex;
2520}
2521
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002522static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002523 if (dir) {
2524 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2525 }
reed@google.comac8543f2012-01-30 20:51:25 +00002526}
2527
reed@google.comc1ea60a2012-01-31 15:15:36 +00002528#if 0
2529#include "SkString.h"
2530#include "../utils/SkParsePath.h"
2531static void dumpPath(const SkPath& path) {
2532 SkString str;
2533 SkParsePath::ToSVGString(path, &str);
2534 SkDebugf("%s\n", str.c_str());
2535}
2536#endif
2537
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002538namespace {
2539// for use with convex_dir_test
2540double mul(double a, double b) { return a * b; }
2541SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2542double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2543SkScalar toScalar(SkScalar a) { return a; }
2544
2545// determines the winding direction of a convex polygon with the precision
2546// of T. CAST_SCALAR casts an SkScalar to T.
2547template <typename T, T (CAST_SCALAR)(SkScalar)>
2548bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2549 // we find the first three points that form a non-degenerate
2550 // triangle. If there are no such points then the path is
2551 // degenerate. The first is always point 0. Now we find the second
2552 // point.
2553 int i = 0;
2554 enum { kX = 0, kY = 1 };
2555 T v0[2];
2556 while (1) {
2557 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2558 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2559 if (v0[kX] || v0[kY]) {
2560 break;
2561 }
2562 if (++i == n - 1) {
2563 return false;
2564 }
2565 }
2566 // now find a third point that is not colinear with the first two
2567 // points and check the orientation of the triangle (which will be
2568 // the same as the orientation of the path).
2569 for (++i; i < n; ++i) {
2570 T v1[2];
2571 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2572 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2573 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2574 if (0 != cross) {
2575 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2576 return true;
2577 }
2578 }
2579 return false;
2580}
2581}
2582
reed@google.comac8543f2012-01-30 20:51:25 +00002583/*
2584 * We loop through all contours, and keep the computed cross-product of the
2585 * contour that contained the global y-max. If we just look at the first
2586 * contour, we may find one that is wound the opposite way (correctly) since
2587 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2588 * that is outer most (or at least has the global y-max) before we can consider
2589 * its cross product.
2590 */
reed@google.com69a99432012-01-10 18:00:10 +00002591bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002592// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002593 // don't want to pay the cost for computing this if it
2594 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002595
2596 if (kUnknown_Direction != fDirection) {
2597 *dir = static_cast<Direction>(fDirection);
2598 return true;
2599 }
reed@google.com69a99432012-01-10 18:00:10 +00002600 const Convexity conv = this->getConvexityOrUnknown();
2601
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002602 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002603
reed@google.comac8543f2012-01-30 20:51:25 +00002604 // initialize with our logical y-min
2605 SkScalar ymax = this->getBounds().fTop;
2606 SkScalar ymaxCross = 0;
2607
reed@google.com69a99432012-01-10 18:00:10 +00002608 for (; !iter.done(); iter.next()) {
2609 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002610 if (n < 3) {
2611 continue;
2612 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002613
reed@google.comcabaf1d2012-01-11 21:03:05 +00002614 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002615 SkScalar cross = 0;
2616 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002617 // We try first at scalar precision, and then again at double
2618 // precision. This is because the vectors computed between distant
2619 // points may lose too much precision.
2620 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002621 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002622 return true;
2623 }
2624 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002625 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002626 return true;
2627 } else {
2628 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002629 }
2630 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002631 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002632 if (pts[index].fY < ymax) {
2633 continue;
2634 }
2635
reed@google.comc1ea60a2012-01-31 15:15:36 +00002636 // If there is more than 1 distinct point at the y-max, we take the
2637 // x-min and x-max of them and just subtract to compute the dir.
2638 if (pts[(index + 1) % n].fY == pts[index].fY) {
2639 int maxIndex;
2640 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002641 if (minIndex == maxIndex) {
2642 goto TRY_CROSSPROD;
2643 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002644 SkASSERT(pts[minIndex].fY == pts[index].fY);
2645 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2646 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2647 // we just subtract the indices, and let that auto-convert to
2648 // SkScalar, since we just want - or + to signal the direction.
2649 cross = minIndex - maxIndex;
2650 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002651 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002652 // Find a next and prev index to use for the cross-product test,
2653 // but we try to find pts that form non-zero vectors from pts[index]
2654 //
2655 // Its possible that we can't find two non-degenerate vectors, so
2656 // we have to guard our search (e.g. all the pts could be in the
2657 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002658
reed@google.comc1ea60a2012-01-31 15:15:36 +00002659 // we pass n - 1 instead of -1 so we don't foul up % operator by
2660 // passing it a negative LH argument.
2661 int prev = find_diff_pt(pts, index, n, n - 1);
2662 if (prev == index) {
2663 // completely degenerate, skip to next contour
2664 continue;
2665 }
2666 int next = find_diff_pt(pts, index, n, 1);
2667 SkASSERT(next != index);
2668 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002669 // 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 +00002670 // x-direction. We really should continue to walk away from the degeneracy until
2671 // there is a divergence.
2672 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002673 // construct the subtract so we get the correct Direction below
2674 cross = pts[index].fX - pts[next].fX;
2675 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002676 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002677
reed@google.comac8543f2012-01-30 20:51:25 +00002678 if (cross) {
2679 // record our best guess so far
2680 ymax = pts[index].fY;
2681 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002682 }
reed@google.com69a99432012-01-10 18:00:10 +00002683 }
2684 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002685 if (ymaxCross) {
2686 crossToDir(ymaxCross, dir);
2687 fDirection = *dir;
2688 return true;
2689 } else {
2690 return false;
2691 }
reed@google.comac8543f2012-01-30 20:51:25 +00002692}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002693
2694///////////////////////////////////////////////////////////////////////////////
2695
2696static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2697 SkScalar D, SkScalar t) {
2698 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2699}
2700
2701static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2702 SkScalar t) {
2703 SkScalar A = c3 + 3*(c1 - c2) - c0;
2704 SkScalar B = 3*(c2 - c1 - c1 + c0);
2705 SkScalar C = 3*(c1 - c0);
2706 SkScalar D = c0;
2707 return eval_cubic_coeff(A, B, C, D, t);
2708}
2709
2710/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2711 t value such that cubic(t) = target
2712 */
2713static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2714 SkScalar target, SkScalar* t) {
2715 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2716 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002717
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002718 SkScalar D = c0 - target;
2719 SkScalar A = c3 + 3*(c1 - c2) - c0;
2720 SkScalar B = 3*(c2 - c1 - c1 + c0);
2721 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002722
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002723 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2724 SkScalar minT = 0;
2725 SkScalar maxT = SK_Scalar1;
2726 SkScalar mid;
2727 int i;
2728 for (i = 0; i < 16; i++) {
2729 mid = SkScalarAve(minT, maxT);
2730 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2731 if (delta < 0) {
2732 minT = mid;
2733 delta = -delta;
2734 } else {
2735 maxT = mid;
2736 }
2737 if (delta < TOLERANCE) {
2738 break;
2739 }
2740 }
2741 *t = mid;
2742 return true;
2743}
2744
2745template <size_t N> static void find_minmax(const SkPoint pts[],
2746 SkScalar* minPtr, SkScalar* maxPtr) {
2747 SkScalar min, max;
2748 min = max = pts[0].fX;
2749 for (size_t i = 1; i < N; ++i) {
2750 min = SkMinScalar(min, pts[i].fX);
2751 max = SkMaxScalar(max, pts[i].fX);
2752 }
2753 *minPtr = min;
2754 *maxPtr = max;
2755}
2756
2757static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2758 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002759
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002760 int dir = 1;
2761 if (pts[0].fY > pts[3].fY) {
2762 storage[0] = pts[3];
2763 storage[1] = pts[2];
2764 storage[2] = pts[1];
2765 storage[3] = pts[0];
2766 pts = storage;
2767 dir = -1;
2768 }
2769 if (y < pts[0].fY || y >= pts[3].fY) {
2770 return 0;
2771 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002772
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002773 // quickreject or quickaccept
2774 SkScalar min, max;
2775 find_minmax<4>(pts, &min, &max);
2776 if (x < min) {
2777 return 0;
2778 }
2779 if (x > max) {
2780 return dir;
2781 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002782
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002783 // compute the actual x(t) value
2784 SkScalar t, xt;
2785 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2786 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2787 } else {
2788 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2789 xt = y < mid ? pts[0].fX : pts[3].fX;
2790 }
2791 return xt < x ? dir : 0;
2792}
2793
2794static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2795 SkPoint dst[10];
2796 int n = SkChopCubicAtYExtrema(pts, dst);
2797 int w = 0;
2798 for (int i = 0; i <= n; ++i) {
2799 w += winding_mono_cubic(&dst[i * 3], x, y);
2800 }
2801 return w;
2802}
2803
2804static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2805 SkScalar y0 = pts[0].fY;
2806 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002807
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002808 int dir = 1;
2809 if (y0 > y2) {
2810 SkTSwap(y0, y2);
2811 dir = -1;
2812 }
2813 if (y < y0 || y >= y2) {
2814 return 0;
2815 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002816
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002817 // bounds check on X (not required. is it faster?)
2818#if 0
2819 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2820 return 0;
2821 }
2822#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002823
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002824 SkScalar roots[2];
2825 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2826 2 * (pts[1].fY - pts[0].fY),
2827 pts[0].fY - y,
2828 roots);
2829 SkASSERT(n <= 1);
2830 SkScalar xt;
2831 if (0 == n) {
2832 SkScalar mid = SkScalarAve(y0, y2);
2833 // Need [0] and [2] if dir == 1
2834 // and [2] and [0] if dir == -1
2835 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2836 } else {
2837 SkScalar t = roots[0];
2838 SkScalar C = pts[0].fX;
2839 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2840 SkScalar B = 2 * (pts[1].fX - C);
2841 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2842 }
2843 return xt < x ? dir : 0;
2844}
2845
2846static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2847 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2848 if (y0 == y1) {
2849 return true;
2850 }
2851 if (y0 < y1) {
2852 return y1 <= y2;
2853 } else {
2854 return y1 >= y2;
2855 }
2856}
2857
2858static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2859 SkPoint dst[5];
2860 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002861
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002862 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2863 n = SkChopQuadAtYExtrema(pts, dst);
2864 pts = dst;
2865 }
2866 int w = winding_mono_quad(pts, x, y);
2867 if (n > 0) {
2868 w += winding_mono_quad(&pts[2], x, y);
2869 }
2870 return w;
2871}
2872
2873static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2874 SkScalar x0 = pts[0].fX;
2875 SkScalar y0 = pts[0].fY;
2876 SkScalar x1 = pts[1].fX;
2877 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002878
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002879 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002880
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002881 int dir = 1;
2882 if (y0 > y1) {
2883 SkTSwap(y0, y1);
2884 dir = -1;
2885 }
2886 if (y < y0 || y >= y1) {
2887 return 0;
2888 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002889
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002890 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2891 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002892
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002893 if (SkScalarSignAsInt(cross) == dir) {
2894 dir = 0;
2895 }
2896 return dir;
2897}
2898
2899bool SkPath::contains(SkScalar x, SkScalar y) const {
2900 bool isInverse = this->isInverseFillType();
2901 if (this->isEmpty()) {
2902 return isInverse;
2903 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002904
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002905 const SkRect& bounds = this->getBounds();
2906 if (!bounds.contains(x, y)) {
2907 return isInverse;
2908 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002909
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002910 SkPath::Iter iter(*this, true);
2911 bool done = false;
2912 int w = 0;
2913 do {
2914 SkPoint pts[4];
2915 switch (iter.next(pts, false)) {
2916 case SkPath::kMove_Verb:
2917 case SkPath::kClose_Verb:
2918 break;
2919 case SkPath::kLine_Verb:
2920 w += winding_line(pts, x, y);
2921 break;
2922 case SkPath::kQuad_Verb:
2923 w += winding_quad(pts, x, y);
2924 break;
2925 case SkPath::kCubic_Verb:
2926 w += winding_cubic(pts, x, y);
2927 break;
2928 case SkPath::kDone_Verb:
2929 done = true;
2930 break;
2931 }
2932 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002933
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002934 switch (this->getFillType()) {
2935 case SkPath::kEvenOdd_FillType:
2936 case SkPath::kInverseEvenOdd_FillType:
2937 w &= 1;
2938 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002939 default:
2940 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002941 }
2942 return SkToBool(w);
2943}
2944