blob: 80ca3011e3f70c7ac3e9878fd5d0af3087a59e95 [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"
14#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000015
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000016
17////////////////////////////////////////////////////////////////////////////
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) {
579 *direction = firstDirection == (lastDirection + 1 & 3) ? kCCW_Direction : kCW_Direction;
580 }
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
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001091bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001092 int count = fPathRef->countVerbs();
1093 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1094 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001095 if (*verbs == kLine_Verb ||
1096 *verbs == kQuad_Verb ||
1097 *verbs == kCubic_Verb) {
1098 return false;
1099 }
1100 ++verbs;
1101 }
1102 return true;
1103}
1104
reed@android.com8a1c16f2008-12-17 15:59:43 +00001105void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001106 assert_known_direction(dir);
1107
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001108 /* If addOval() is called after previous moveTo(),
1109 this path is still marked as an oval. This is used to
1110 fit into WebKit's calling sequences.
1111 We can't simply check isEmpty() in this case, as additional
1112 moveTo() would mark the path non empty.
1113 */
1114 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001115 if (fIsOval) {
1116 fDirection = dir;
1117 } else {
1118 fDirection = kUnknown_Direction;
1119 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001120
1121 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001122 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001123
reed@android.com8a1c16f2008-12-17 15:59:43 +00001124 SkAutoPathBoundsUpdate apbu(this, oval);
1125
1126 SkScalar cx = oval.centerX();
1127 SkScalar cy = oval.centerY();
1128 SkScalar rx = SkScalarHalf(oval.width());
1129 SkScalar ry = SkScalarHalf(oval.height());
1130#if 0 // these seem faster than using quads (1/2 the number of edges)
1131 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1132 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
1133
1134 this->incReserve(13);
1135 this->moveTo(cx + rx, cy);
1136 if (dir == kCCW_Direction) {
1137 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
1138 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
1139 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
1140 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
1141 } else {
1142 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
1143 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
1144 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
1145 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
1146 }
1147#else
1148 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1149 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1150 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1151 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1152
1153 /*
1154 To handle imprecision in computing the center and radii, we revert to
1155 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1156 to ensure that we don't exceed the oval's bounds *ever*, since we want
1157 to use oval for our fast-bounds, rather than have to recompute it.
1158 */
1159 const SkScalar L = oval.fLeft; // cx - rx
1160 const SkScalar T = oval.fTop; // cy - ry
1161 const SkScalar R = oval.fRight; // cx + rx
1162 const SkScalar B = oval.fBottom; // cy + ry
1163
1164 this->incReserve(17); // 8 quads + close
1165 this->moveTo(R, cy);
1166 if (dir == kCCW_Direction) {
1167 this->quadTo( R, cy - sy, cx + mx, cy - my);
1168 this->quadTo(cx + sx, T, cx , T);
1169 this->quadTo(cx - sx, T, cx - mx, cy - my);
1170 this->quadTo( L, cy - sy, L, cy );
1171 this->quadTo( L, cy + sy, cx - mx, cy + my);
1172 this->quadTo(cx - sx, B, cx , B);
1173 this->quadTo(cx + sx, B, cx + mx, cy + my);
1174 this->quadTo( R, cy + sy, R, cy );
1175 } else {
1176 this->quadTo( R, cy + sy, cx + mx, cy + my);
1177 this->quadTo(cx + sx, B, cx , B);
1178 this->quadTo(cx - sx, B, cx - mx, cy + my);
1179 this->quadTo( L, cy + sy, L, cy );
1180 this->quadTo( L, cy - sy, cx - mx, cy - my);
1181 this->quadTo(cx - sx, T, cx , T);
1182 this->quadTo(cx + sx, T, cx + mx, cy - my);
1183 this->quadTo( R, cy - sy, R, cy );
1184 }
1185#endif
1186 this->close();
1187}
1188
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001189bool SkPath::isOval(SkRect* rect) const {
1190 if (fIsOval && rect) {
1191 *rect = getBounds();
1192 }
1193
1194 return fIsOval;
1195}
1196
reed@android.com8a1c16f2008-12-17 15:59:43 +00001197void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1198 if (r > 0) {
1199 SkRect rect;
1200 rect.set(x - r, y - r, x + r, y + r);
1201 this->addOval(rect, dir);
1202 }
1203}
1204
1205#include "SkGeometry.h"
1206
1207static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1208 SkScalar sweepAngle,
1209 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001210
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001211 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001212 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1213 // Chrome uses this path to move into and out of ovals. If not
1214 // treated as a special case the moves can distort the oval's
1215 // bounding box (and break the circle special case).
1216 pts[0].set(oval.fRight, oval.centerY());
1217 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001218 } else if (0 == oval.width() && 0 == oval.height()) {
1219 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001220 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001221 // a rect.
1222 // TODO: optimizing the case where only one of width or height is zero
1223 // should also be considered. This case, however, doesn't seem to be
1224 // as common as the single point case.
1225 pts[0].set(oval.fRight, oval.fTop);
1226 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001227 }
1228
reed@android.com8a1c16f2008-12-17 15:59:43 +00001229 SkVector start, stop;
1230
1231 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1232 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1233 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001234
1235 /* If the sweep angle is nearly (but less than) 360, then due to precision
1236 loss in radians-conversion and/or sin/cos, we may end up with coincident
1237 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1238 of drawing a nearly complete circle (good).
1239 e.g. canvas.drawArc(0, 359.99, ...)
1240 -vs- canvas.drawArc(0, 359.9, ...)
1241 We try to detect this edge case, and tweak the stop vector
1242 */
1243 if (start == stop) {
1244 SkScalar sw = SkScalarAbs(sweepAngle);
1245 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1246 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1247 // make a guess at a tiny angle (in radians) to tweak by
1248 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1249 // not sure how much will be enough, so we use a loop
1250 do {
1251 stopRad -= deltaRad;
1252 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1253 } while (start == stop);
1254 }
1255 }
1256
reed@android.com8a1c16f2008-12-17 15:59:43 +00001257 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001258
reed@android.com8a1c16f2008-12-17 15:59:43 +00001259 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1260 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001261
reed@android.com8a1c16f2008-12-17 15:59:43 +00001262 return SkBuildQuadArc(start, stop,
1263 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1264 &matrix, pts);
1265}
1266
1267void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1268 bool forceMoveTo) {
1269 if (oval.width() < 0 || oval.height() < 0) {
1270 return;
1271 }
1272
1273 SkPoint pts[kSkBuildQuadArcStorage];
1274 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1275 SkASSERT((count & 1) == 1);
1276
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001277 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001278 forceMoveTo = true;
1279 }
1280 this->incReserve(count);
1281 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1282 for (int i = 1; i < count; i += 2) {
1283 this->quadTo(pts[i], pts[i+1]);
1284 }
1285}
1286
1287void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1288 SkScalar sweepAngle) {
1289 if (oval.isEmpty() || 0 == sweepAngle) {
1290 return;
1291 }
1292
1293 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1294
1295 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1296 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1297 return;
1298 }
1299
1300 SkPoint pts[kSkBuildQuadArcStorage];
1301 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1302
1303 this->incReserve(count);
1304 this->moveTo(pts[0]);
1305 for (int i = 1; i < count; i += 2) {
1306 this->quadTo(pts[i], pts[i+1]);
1307 }
1308}
1309
1310/*
1311 Need to handle the case when the angle is sharp, and our computed end-points
1312 for the arc go behind pt1 and/or p2...
1313*/
1314void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1315 SkScalar radius) {
1316 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001317
reed@android.com8a1c16f2008-12-17 15:59:43 +00001318 // need to know our prev pt so we can construct tangent vectors
1319 {
1320 SkPoint start;
1321 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001322 // Handle degenerate cases by adding a line to the first point and
1323 // bailing out.
1324 if ((x1 == start.fX && y1 == start.fY) ||
1325 (x1 == x2 && y1 == y2) ||
1326 radius == 0) {
1327 this->lineTo(x1, y1);
1328 return;
1329 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001330 before.setNormalize(x1 - start.fX, y1 - start.fY);
1331 after.setNormalize(x2 - x1, y2 - y1);
1332 }
reed@google.comabf15c12011-01-18 20:35:51 +00001333
reed@android.com8a1c16f2008-12-17 15:59:43 +00001334 SkScalar cosh = SkPoint::DotProduct(before, after);
1335 SkScalar sinh = SkPoint::CrossProduct(before, after);
1336
1337 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001338 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001339 return;
1340 }
reed@google.comabf15c12011-01-18 20:35:51 +00001341
reed@android.com8a1c16f2008-12-17 15:59:43 +00001342 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1343 if (dist < 0) {
1344 dist = -dist;
1345 }
1346
1347 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1348 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1349 SkRotationDirection arcDir;
1350
1351 // now turn before/after into normals
1352 if (sinh > 0) {
1353 before.rotateCCW();
1354 after.rotateCCW();
1355 arcDir = kCW_SkRotationDirection;
1356 } else {
1357 before.rotateCW();
1358 after.rotateCW();
1359 arcDir = kCCW_SkRotationDirection;
1360 }
1361
1362 SkMatrix matrix;
1363 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001364
reed@android.com8a1c16f2008-12-17 15:59:43 +00001365 matrix.setScale(radius, radius);
1366 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1367 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001368
reed@android.com8a1c16f2008-12-17 15:59:43 +00001369 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001370
reed@android.com8a1c16f2008-12-17 15:59:43 +00001371 this->incReserve(count);
1372 // [xx,yy] == pts[0]
1373 this->lineTo(xx, yy);
1374 for (int i = 1; i < count; i += 2) {
1375 this->quadTo(pts[i], pts[i+1]);
1376 }
1377}
1378
1379///////////////////////////////////////////////////////////////////////////////
1380
1381void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1382 SkMatrix matrix;
1383
1384 matrix.setTranslate(dx, dy);
1385 this->addPath(path, matrix);
1386}
1387
1388void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001389 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001390
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001391 fIsOval = false;
1392
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001393 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001394 SkPoint pts[4];
1395 Verb verb;
1396
1397 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1398
1399 while ((verb = iter.next(pts)) != kDone_Verb) {
1400 switch (verb) {
1401 case kMove_Verb:
1402 proc(matrix, &pts[0], &pts[0], 1);
1403 this->moveTo(pts[0]);
1404 break;
1405 case kLine_Verb:
1406 proc(matrix, &pts[1], &pts[1], 1);
1407 this->lineTo(pts[1]);
1408 break;
1409 case kQuad_Verb:
1410 proc(matrix, &pts[1], &pts[1], 2);
1411 this->quadTo(pts[1], pts[2]);
1412 break;
1413 case kCubic_Verb:
1414 proc(matrix, &pts[1], &pts[1], 3);
1415 this->cubicTo(pts[1], pts[2], pts[3]);
1416 break;
1417 case kClose_Verb:
1418 this->close();
1419 break;
1420 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001421 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001422 }
1423 }
1424}
1425
1426///////////////////////////////////////////////////////////////////////////////
1427
1428static const uint8_t gPtsInVerb[] = {
1429 1, // kMove
1430 1, // kLine
1431 2, // kQuad
1432 3, // kCubic
1433 0, // kClose
1434 0 // kDone
1435};
1436
1437// ignore the initial moveto, and stop when the 1st contour ends
1438void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001439 int i, vcount = path.fPathRef->countVerbs();
1440 // exit early if the path is empty, or just has a moveTo.
1441 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 return;
1443 }
1444
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001445 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001446
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001447 fIsOval = false;
1448
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001449 const uint8_t* verbs = path.fPathRef->verbs();
1450 // skip the initial moveTo
1451 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001452
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001453 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001454 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001455 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001456 case kLine_Verb:
1457 this->lineTo(pts[0].fX, pts[0].fY);
1458 break;
1459 case kQuad_Verb:
1460 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1461 break;
1462 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001463 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 +00001464 break;
1465 case kClose_Verb:
1466 return;
1467 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001468 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001469 }
1470}
1471
1472// ignore the last point of the 1st contour
1473void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001474 int i, vcount = path.fPathRef->countVerbs();
1475 // exit early if the path is empty, or just has a moveTo.
1476 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001477 return;
1478 }
1479
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001480 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001481
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001482 fIsOval = false;
1483
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001484 const uint8_t* verbs = path.fPathRef->verbs();
1485 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001486
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001487 SkASSERT(verbs[~0] == kMove_Verb);
1488 for (i = 1; i < vcount; ++i) {
1489 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001490 if (n == 0) {
1491 break;
1492 }
1493 pts += n;
1494 }
1495
1496 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001497 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001498 case kLine_Verb:
1499 this->lineTo(pts[-1].fX, pts[-1].fY);
1500 break;
1501 case kQuad_Verb:
1502 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1503 break;
1504 case kCubic_Verb:
1505 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1506 pts[-3].fX, pts[-3].fY);
1507 break;
1508 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001509 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 break;
1511 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001512 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001513 }
1514}
1515
reed@google.com63d73742012-01-10 15:33:12 +00001516void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001517 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001518
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001519 const SkPoint* pts = src.fPathRef->pointsEnd();
1520 // we will iterator through src's verbs backwards
1521 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1522 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001523
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001524 fIsOval = false;
1525
reed@google.com63d73742012-01-10 15:33:12 +00001526 bool needMove = true;
1527 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001528 while (verbs < verbsEnd) {
1529 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001530 int n = gPtsInVerb[v];
1531
1532 if (needMove) {
1533 --pts;
1534 this->moveTo(pts->fX, pts->fY);
1535 needMove = false;
1536 }
1537 pts -= n;
1538 switch (v) {
1539 case kMove_Verb:
1540 if (needClose) {
1541 this->close();
1542 needClose = false;
1543 }
1544 needMove = true;
1545 pts += 1; // so we see the point in "if (needMove)" above
1546 break;
1547 case kLine_Verb:
1548 this->lineTo(pts[0]);
1549 break;
1550 case kQuad_Verb:
1551 this->quadTo(pts[1], pts[0]);
1552 break;
1553 case kCubic_Verb:
1554 this->cubicTo(pts[2], pts[1], pts[0]);
1555 break;
1556 case kClose_Verb:
1557 needClose = true;
1558 break;
1559 default:
1560 SkASSERT(!"unexpected verb");
1561 }
1562 }
1563}
1564
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565///////////////////////////////////////////////////////////////////////////////
1566
1567void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1568 SkMatrix matrix;
1569
1570 matrix.setTranslate(dx, dy);
1571 this->transform(matrix, dst);
1572}
1573
1574#include "SkGeometry.h"
1575
1576static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1577 int level = 2) {
1578 if (--level >= 0) {
1579 SkPoint tmp[5];
1580
1581 SkChopQuadAtHalf(pts, tmp);
1582 subdivide_quad_to(path, &tmp[0], level);
1583 subdivide_quad_to(path, &tmp[2], level);
1584 } else {
1585 path->quadTo(pts[1], pts[2]);
1586 }
1587}
1588
1589static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1590 int level = 2) {
1591 if (--level >= 0) {
1592 SkPoint tmp[7];
1593
1594 SkChopCubicAtHalf(pts, tmp);
1595 subdivide_cubic_to(path, &tmp[0], level);
1596 subdivide_cubic_to(path, &tmp[3], level);
1597 } else {
1598 path->cubicTo(pts[1], pts[2], pts[3]);
1599 }
1600}
1601
1602void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1603 SkDEBUGCODE(this->validate();)
1604 if (dst == NULL) {
1605 dst = (SkPath*)this;
1606 }
1607
tomhudson@google.com8d430182011-06-06 19:11:19 +00001608 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 SkPath tmp;
1610 tmp.fFillType = fFillType;
1611
1612 SkPath::Iter iter(*this, false);
1613 SkPoint pts[4];
1614 SkPath::Verb verb;
1615
reed@google.com4a3b7142012-05-16 17:16:46 +00001616 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001617 switch (verb) {
1618 case kMove_Verb:
1619 tmp.moveTo(pts[0]);
1620 break;
1621 case kLine_Verb:
1622 tmp.lineTo(pts[1]);
1623 break;
1624 case kQuad_Verb:
1625 subdivide_quad_to(&tmp, pts);
1626 break;
1627 case kCubic_Verb:
1628 subdivide_cubic_to(&tmp, pts);
1629 break;
1630 case kClose_Verb:
1631 tmp.close();
1632 break;
1633 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001634 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001635 break;
1636 }
1637 }
1638
1639 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001640 SkPathRef::Editor ed(&dst->fPathRef);
1641 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001642 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001643 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001644 /*
1645 * If we're not in perspective, we can transform all of the points at
1646 * once.
1647 *
1648 * Here we also want to optimize bounds, by noting if the bounds are
1649 * already known, and if so, we just transform those as well and mark
1650 * them as "known", rather than force the transformed path to have to
1651 * recompute them.
1652 *
1653 * Special gotchas if the path is effectively empty (<= 1 point) or
1654 * if it is non-finite. In those cases bounds need to stay empty,
1655 * regardless of the matrix.
1656 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001657 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001658 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001659 if (fIsFinite) {
1660 matrix.mapRect(&dst->fBounds, fBounds);
1661 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1662 dst->fBounds.setEmpty();
1663 }
1664 } else {
1665 dst->fIsFinite = false;
1666 dst->fBounds.setEmpty();
1667 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001669 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001670 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671 }
1672
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001673 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1674
reed@android.com8a1c16f2008-12-17 15:59:43 +00001675 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001676 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001677 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001678 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001679 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001680
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001681 if (kUnknown_Direction == fDirection) {
1682 dst->fDirection = kUnknown_Direction;
1683 } else {
1684 SkScalar det2x2 =
1685 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1686 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1687 if (det2x2 < 0) {
1688 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1689 } else if (det2x2 > 0) {
1690 dst->fDirection = fDirection;
1691 } else {
1692 dst->fDirection = kUnknown_Direction;
1693 }
1694 }
1695
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001696 // It's an oval only if it stays a rect.
1697 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001698
reed@android.com8a1c16f2008-12-17 15:59:43 +00001699 SkDEBUGCODE(dst->validate();)
1700 }
1701}
1702
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703///////////////////////////////////////////////////////////////////////////////
1704///////////////////////////////////////////////////////////////////////////////
1705
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001706enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001707 kEmptyContour_SegmentState, // The current contour is empty. We may be
1708 // starting processing or we may have just
1709 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001710 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1711 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1712 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001713};
1714
1715SkPath::Iter::Iter() {
1716#ifdef SK_DEBUG
1717 fPts = NULL;
1718 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001719 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001720 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721#endif
1722 // need to init enough to make next() harmlessly return kDone_Verb
1723 fVerbs = NULL;
1724 fVerbStop = NULL;
1725 fNeedClose = false;
1726}
1727
1728SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1729 this->setPath(path, forceClose);
1730}
1731
1732void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001733 fPts = path.fPathRef->points();
1734 fVerbs = path.fPathRef->verbs();
1735 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001736 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001737 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738 fForceClose = SkToU8(forceClose);
1739 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001740 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001741}
1742
1743bool SkPath::Iter::isClosedContour() const {
1744 if (fVerbs == NULL || fVerbs == fVerbStop) {
1745 return false;
1746 }
1747 if (fForceClose) {
1748 return true;
1749 }
1750
1751 const uint8_t* verbs = fVerbs;
1752 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001753
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001754 if (kMove_Verb == *(verbs - 1)) {
1755 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001756 }
1757
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001758 while (verbs > stop) {
1759 // verbs points one beyond the current verb, decrement first.
1760 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001761 if (kMove_Verb == v) {
1762 break;
1763 }
1764 if (kClose_Verb == v) {
1765 return true;
1766 }
1767 }
1768 return false;
1769}
1770
1771SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001772 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001774 // A special case: if both points are NaN, SkPoint::operation== returns
1775 // false, but the iterator expects that they are treated as the same.
1776 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001777 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1778 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001779 return kClose_Verb;
1780 }
1781
reed@google.com9e25dbf2012-05-15 17:05:38 +00001782 pts[0] = fLastPt;
1783 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001784 fLastPt = fMoveTo;
1785 fCloseLine = true;
1786 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001787 } else {
1788 pts[0] = fMoveTo;
1789 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791}
1792
reed@google.com9e25dbf2012-05-15 17:05:38 +00001793const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001794 if (fSegmentState == kAfterMove_SegmentState) {
1795 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001796 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001797 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001798 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001799 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1800 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001801 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001802 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001803}
1804
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001805void SkPath::Iter::consumeDegenerateSegments() {
1806 // We need to step over anything that will not move the current draw point
1807 // forward before the next move is seen
1808 const uint8_t* lastMoveVerb = 0;
1809 const SkPoint* lastMovePt = 0;
1810 SkPoint lastPt = fLastPt;
1811 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001812 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001813 switch (verb) {
1814 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001815 // Keep a record of this most recent move
1816 lastMoveVerb = fVerbs;
1817 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001818 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001819 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 fPts++;
1821 break;
1822
1823 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001824 // A close when we are in a segment is always valid except when it
1825 // follows a move which follows a segment.
1826 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001827 return;
1828 }
1829 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001830 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001831 break;
1832
1833 case kLine_Verb:
1834 if (!IsLineDegenerate(lastPt, fPts[0])) {
1835 if (lastMoveVerb) {
1836 fVerbs = lastMoveVerb;
1837 fPts = lastMovePt;
1838 return;
1839 }
1840 return;
1841 }
1842 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001843 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 fPts++;
1845 break;
1846
1847 case kQuad_Verb:
1848 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1849 if (lastMoveVerb) {
1850 fVerbs = lastMoveVerb;
1851 fPts = lastMovePt;
1852 return;
1853 }
1854 return;
1855 }
1856 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001857 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001858 fPts += 2;
1859 break;
1860
1861 case kCubic_Verb:
1862 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1863 if (lastMoveVerb) {
1864 fVerbs = lastMoveVerb;
1865 fPts = lastMovePt;
1866 return;
1867 }
1868 return;
1869 }
1870 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001871 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001872 fPts += 3;
1873 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001874
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001875 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001876 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001877 }
1878 }
1879}
1880
reed@google.com4a3b7142012-05-16 17:16:46 +00001881SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001882 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001883
reed@android.com8a1c16f2008-12-17 15:59:43 +00001884 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001885 // Close the curve if requested and if there is some curve to close
1886 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001887 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001888 return kLine_Verb;
1889 }
1890 fNeedClose = false;
1891 return kClose_Verb;
1892 }
1893 return kDone_Verb;
1894 }
1895
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001896 // fVerbs is one beyond the current verb, decrement first
1897 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001898 const SkPoint* SK_RESTRICT srcPts = fPts;
1899 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001900
1901 switch (verb) {
1902 case kMove_Verb:
1903 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001904 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001905 verb = this->autoClose(pts);
1906 if (verb == kClose_Verb) {
1907 fNeedClose = false;
1908 }
1909 return (Verb)verb;
1910 }
1911 if (fVerbs == fVerbStop) { // might be a trailing moveto
1912 return kDone_Verb;
1913 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001914 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001915 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001916 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001917 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001918 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001919 fNeedClose = fForceClose;
1920 break;
1921 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001922 pts[0] = this->cons_moveTo();
1923 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001924 fLastPt = srcPts[0];
1925 fCloseLine = false;
1926 srcPts += 1;
1927 break;
1928 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001929 pts[0] = this->cons_moveTo();
1930 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 fLastPt = srcPts[1];
1932 srcPts += 2;
1933 break;
1934 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001935 pts[0] = this->cons_moveTo();
1936 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001937 fLastPt = srcPts[2];
1938 srcPts += 3;
1939 break;
1940 case kClose_Verb:
1941 verb = this->autoClose(pts);
1942 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001943 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001944 } else {
1945 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001946 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001947 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001948 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001949 break;
1950 }
1951 fPts = srcPts;
1952 return (Verb)verb;
1953}
1954
1955///////////////////////////////////////////////////////////////////////////////
1956
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001957SkPath::RawIter::RawIter() {
1958#ifdef SK_DEBUG
1959 fPts = NULL;
1960 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1961#endif
1962 // need to init enough to make next() harmlessly return kDone_Verb
1963 fVerbs = NULL;
1964 fVerbStop = NULL;
1965}
1966
1967SkPath::RawIter::RawIter(const SkPath& path) {
1968 this->setPath(path);
1969}
1970
1971void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001972 fPts = path.fPathRef->points();
1973 fVerbs = path.fPathRef->verbs();
1974 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001975 fMoveTo.fX = fMoveTo.fY = 0;
1976 fLastPt.fX = fLastPt.fY = 0;
1977}
1978
1979SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001980 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001981 if (fVerbs == fVerbStop) {
1982 return kDone_Verb;
1983 }
1984
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001985 // fVerbs points one beyond next verb so decrement first.
1986 unsigned verb = *(--fVerbs);
1987 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001988
1989 switch (verb) {
1990 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001991 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001992 fMoveTo = srcPts[0];
1993 fLastPt = fMoveTo;
1994 srcPts += 1;
1995 break;
1996 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001997 pts[0] = fLastPt;
1998 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001999 fLastPt = srcPts[0];
2000 srcPts += 1;
2001 break;
2002 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002003 pts[0] = fLastPt;
2004 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002005 fLastPt = srcPts[1];
2006 srcPts += 2;
2007 break;
2008 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002009 pts[0] = fLastPt;
2010 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002011 fLastPt = srcPts[2];
2012 srcPts += 3;
2013 break;
2014 case kClose_Verb:
2015 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002016 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002017 break;
2018 }
2019 fPts = srcPts;
2020 return (Verb)verb;
2021}
2022
2023///////////////////////////////////////////////////////////////////////////////
2024
reed@android.com8a1c16f2008-12-17 15:59:43 +00002025/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002026 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002027*/
2028
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002029uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030 SkDEBUGCODE(this->validate();)
2031
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002032 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002033 const int byteCount = sizeof(int32_t)
2034#if NEW_PICTURE_FORMAT
2035 + fPathRef->writeSize()
2036#else
2037 + 2 * sizeof(int32_t)
2038 + sizeof(SkPoint) * fPathRef->countPoints()
2039 + sizeof(uint8_t) * fPathRef->countVerbs()
2040#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002041 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002042 return SkAlign4(byteCount);
2043 }
2044
2045 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002046#if !NEW_PICTURE_FORMAT
2047 buffer.write32(fPathRef->countPoints());
2048 buffer.write32(fPathRef->countVerbs());
2049#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002050
2051 // Call getBounds() to ensure (as a side-effect) that fBounds
2052 // and fIsFinite are computed.
2053 const SkRect& bounds = this->getBounds();
2054 SkASSERT(!fBoundsIsDirty);
2055
2056 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2057 ((fIsOval & 1) << kIsOval_SerializationShift) |
2058 (fConvexity << kConvexity_SerializationShift) |
2059 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002060 (fSegmentMask << kSegmentMask_SerializationShift) |
2061 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002062
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002063 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002064
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002065 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002066
2067 buffer.write(&bounds, sizeof(bounds));
2068
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002069 buffer.padToAlign4();
2070 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002071}
2072
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002073uint32_t SkPath::readFromMemory(const void* storage) {
2074 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002075#if !NEW_PICTURE_FORMAT
2076 int32_t pcount = buffer.readS32();
2077 int32_t vcount = buffer.readS32();
2078#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002079
reed@google.com98b11f12011-09-21 18:40:27 +00002080 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002081 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2082 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2083 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2084 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002085 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
2086 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002087
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002088#if NEW_PICTURE_FORMAT
2089 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
2090#else
2091 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
2092#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002093
2094 buffer.read(&fBounds, sizeof(fBounds));
2095 fBoundsIsDirty = false;
2096
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002097 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002098
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002099 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002100
2101 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002102 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002103}
2104
2105///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002106
reed@android.com8a1c16f2008-12-17 15:59:43 +00002107void SkPath::dump(bool forceClose, const char title[]) const {
2108 Iter iter(*this, forceClose);
2109 SkPoint pts[4];
2110 Verb verb;
2111
2112 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2113 title ? title : "");
2114
reed@google.com4a3b7142012-05-16 17:16:46 +00002115 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002116 switch (verb) {
2117 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118 SkDebugf(" path: moveTo [%g %g]\n",
2119 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120 break;
2121 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002122 SkDebugf(" path: lineTo [%g %g]\n",
2123 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002124 break;
2125 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
2127 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2128 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129 break;
2130 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002131 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
2132 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2133 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
2134 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002135 break;
2136 case kClose_Verb:
2137 SkDebugf(" path: close\n");
2138 break;
2139 default:
2140 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2141 verb = kDone_Verb; // stop the loop
2142 break;
2143 }
2144 }
2145 SkDebugf("path: done %s\n", title ? title : "");
2146}
2147
reed@android.come522ca52009-11-23 20:10:41 +00002148void SkPath::dump() const {
2149 this->dump(false);
2150}
2151
2152#ifdef SK_DEBUG
2153void SkPath::validate() const {
2154 SkASSERT(this != NULL);
2155 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002156
djsollen@google.com077348c2012-10-22 20:23:32 +00002157#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002158 if (!fBoundsIsDirty) {
2159 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002160
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002161 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002162 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002163
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002164 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002165 // if we're empty, fBounds may be empty but translated, so we can't
2166 // necessarily compare to bounds directly
2167 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2168 // be [2, 2, 2, 2]
2169 SkASSERT(bounds.isEmpty());
2170 SkASSERT(fBounds.isEmpty());
2171 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002172 if (bounds.isEmpty()) {
2173 SkASSERT(fBounds.isEmpty());
2174 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002175 if (!fBounds.isEmpty()) {
2176 SkASSERT(fBounds.contains(bounds));
2177 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002178 }
reed@android.come522ca52009-11-23 20:10:41 +00002179 }
2180 }
reed@google.com10296cc2011-09-21 12:29:05 +00002181
2182 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002183 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2184 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2185 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002186 case kLine_Verb:
2187 mask |= kLine_SegmentMask;
2188 break;
2189 case kQuad_Verb:
2190 mask |= kQuad_SegmentMask;
2191 break;
2192 case kCubic_Verb:
2193 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002194 case kMove_Verb: // these verbs aren't included in the segment mask.
2195 case kClose_Verb:
2196 break;
2197 case kDone_Verb:
2198 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2199 break;
2200 default:
2201 SkDEBUGFAIL("Unknown Verb");
2202 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002203 }
2204 }
2205 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002206#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002207}
djsollen@google.com077348c2012-10-22 20:23:32 +00002208#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002209
reed@google.com04863fa2011-05-15 04:08:24 +00002210///////////////////////////////////////////////////////////////////////////////
2211
reed@google.com0b7b9822011-05-16 12:29:27 +00002212static int sign(SkScalar x) { return x < 0; }
2213#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002214
reed@google.com04863fa2011-05-15 04:08:24 +00002215static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002216 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002217}
2218
2219// only valid for a single contour
2220struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002221 Convexicator()
2222 : fPtCount(0)
2223 , fConvexity(SkPath::kConvex_Convexity)
2224 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002225 fSign = 0;
2226 // warnings
2227 fCurrPt.set(0, 0);
2228 fVec0.set(0, 0);
2229 fVec1.set(0, 0);
2230 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002231
2232 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002233 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002234 }
2235
2236 SkPath::Convexity getConvexity() const { return fConvexity; }
2237
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002238 /** The direction returned is only valid if the path is determined convex */
2239 SkPath::Direction getDirection() const { return fDirection; }
2240
reed@google.com04863fa2011-05-15 04:08:24 +00002241 void addPt(const SkPoint& pt) {
2242 if (SkPath::kConcave_Convexity == fConvexity) {
2243 return;
2244 }
2245
2246 if (0 == fPtCount) {
2247 fCurrPt = pt;
2248 ++fPtCount;
2249 } else {
2250 SkVector vec = pt - fCurrPt;
2251 if (vec.fX || vec.fY) {
2252 fCurrPt = pt;
2253 if (++fPtCount == 2) {
2254 fFirstVec = fVec1 = vec;
2255 } else {
2256 SkASSERT(fPtCount > 2);
2257 this->addVec(vec);
2258 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002259
reed@google.com85b6e392011-05-15 20:25:17 +00002260 int sx = sign(vec.fX);
2261 int sy = sign(vec.fY);
2262 fDx += (sx != fSx);
2263 fDy += (sy != fSy);
2264 fSx = sx;
2265 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002266
reed@google.com85b6e392011-05-15 20:25:17 +00002267 if (fDx > 3 || fDy > 3) {
2268 fConvexity = SkPath::kConcave_Convexity;
2269 }
reed@google.com04863fa2011-05-15 04:08:24 +00002270 }
2271 }
2272 }
2273
2274 void close() {
2275 if (fPtCount > 2) {
2276 this->addVec(fFirstVec);
2277 }
2278 }
2279
2280private:
2281 void addVec(const SkVector& vec) {
2282 SkASSERT(vec.fX || vec.fY);
2283 fVec0 = fVec1;
2284 fVec1 = vec;
2285 int sign = CrossProductSign(fVec0, fVec1);
2286 if (0 == fSign) {
2287 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002288 if (1 == sign) {
2289 fDirection = SkPath::kCW_Direction;
2290 } else if (-1 == sign) {
2291 fDirection = SkPath::kCCW_Direction;
2292 }
reed@google.com04863fa2011-05-15 04:08:24 +00002293 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002294 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002295 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002296 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002297 }
2298 }
2299 }
2300
2301 SkPoint fCurrPt;
2302 SkVector fVec0, fVec1, fFirstVec;
2303 int fPtCount; // non-degenerate points
2304 int fSign;
2305 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002306 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002307 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002308};
2309
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002310SkPath::Convexity SkPath::internalGetConvexity() const {
2311 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002312 SkPoint pts[4];
2313 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002314 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002315
2316 int contourCount = 0;
2317 int count;
2318 Convexicator state;
2319
2320 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2321 switch (verb) {
2322 case kMove_Verb:
2323 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002324 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002325 return kConcave_Convexity;
2326 }
2327 pts[1] = pts[0];
2328 count = 1;
2329 break;
2330 case kLine_Verb: count = 1; break;
2331 case kQuad_Verb: count = 2; break;
2332 case kCubic_Verb: count = 3; break;
2333 case kClose_Verb:
2334 state.close();
2335 count = 0;
2336 break;
2337 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002338 SkDEBUGFAIL("bad verb");
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
2343 for (int i = 1; i <= count; i++) {
2344 state.addPt(pts[i]);
2345 }
2346 // early exit
2347 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002348 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002349 return kConcave_Convexity;
2350 }
2351 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002352 fConvexity = state.getConvexity();
2353 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2354 fDirection = state.getDirection();
2355 }
2356 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002357}
reed@google.com69a99432012-01-10 18:00:10 +00002358
2359///////////////////////////////////////////////////////////////////////////////
2360
2361class ContourIter {
2362public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002363 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002364
2365 bool done() const { return fDone; }
2366 // if !done() then these may be called
2367 int count() const { return fCurrPtCount; }
2368 const SkPoint* pts() const { return fCurrPt; }
2369 void next();
2370
2371private:
2372 int fCurrPtCount;
2373 const SkPoint* fCurrPt;
2374 const uint8_t* fCurrVerb;
2375 const uint8_t* fStopVerbs;
2376 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002377 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002378};
2379
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002380ContourIter::ContourIter(const SkPathRef& pathRef) {
2381 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002382 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002383 fCurrPt = pathRef.points();
2384 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002385 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002386 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002387 this->next();
2388}
2389
2390void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002391 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002392 fDone = true;
2393 }
2394 if (fDone) {
2395 return;
2396 }
2397
2398 // skip pts of prev contour
2399 fCurrPt += fCurrPtCount;
2400
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002401 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002402 int ptCount = 1; // moveTo
2403 const uint8_t* verbs = fCurrVerb;
2404
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002405 for (--verbs; verbs > fStopVerbs; --verbs) {
2406 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002407 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002408 goto CONTOUR_END;
2409 case SkPath::kLine_Verb:
2410 ptCount += 1;
2411 break;
2412 case SkPath::kQuad_Verb:
2413 ptCount += 2;
2414 break;
2415 case SkPath::kCubic_Verb:
2416 ptCount += 3;
2417 break;
2418 default: // kClose_Verb, just keep going
2419 break;
2420 }
2421 }
2422CONTOUR_END:
2423 fCurrPtCount = ptCount;
2424 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002425 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002426}
2427
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002428// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002429static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002430 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2431 // We may get 0 when the above subtracts underflow. We expect this to be
2432 // very rare and lazily promote to double.
2433 if (0 == cross) {
2434 double p0x = SkScalarToDouble(p0.fX);
2435 double p0y = SkScalarToDouble(p0.fY);
2436
2437 double p1x = SkScalarToDouble(p1.fX);
2438 double p1y = SkScalarToDouble(p1.fY);
2439
2440 double p2x = SkScalarToDouble(p2.fX);
2441 double p2y = SkScalarToDouble(p2.fY);
2442
2443 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2444 (p1y - p0y) * (p2x - p0x));
2445
2446 }
2447 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002448}
2449
reed@google.comc1ea60a2012-01-31 15:15:36 +00002450// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002451static int find_max_y(const SkPoint pts[], int count) {
2452 SkASSERT(count > 0);
2453 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002454 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002455 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002456 SkScalar y = pts[i].fY;
2457 if (y > max) {
2458 max = y;
2459 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002460 }
2461 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002462 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002463}
2464
reed@google.comcabaf1d2012-01-11 21:03:05 +00002465static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2466 int i = index;
2467 for (;;) {
2468 i = (i + inc) % n;
2469 if (i == index) { // we wrapped around, so abort
2470 break;
2471 }
2472 if (pts[index] != pts[i]) { // found a different point, success!
2473 break;
2474 }
2475 }
2476 return i;
2477}
2478
reed@google.comc1ea60a2012-01-31 15:15:36 +00002479/**
2480 * Starting at index, and moving forward (incrementing), find the xmin and
2481 * xmax of the contiguous points that have the same Y.
2482 */
2483static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2484 int* maxIndexPtr) {
2485 const SkScalar y = pts[index].fY;
2486 SkScalar min = pts[index].fX;
2487 SkScalar max = min;
2488 int minIndex = index;
2489 int maxIndex = index;
2490 for (int i = index + 1; i < n; ++i) {
2491 if (pts[i].fY != y) {
2492 break;
2493 }
2494 SkScalar x = pts[i].fX;
2495 if (x < min) {
2496 min = x;
2497 minIndex = i;
2498 } else if (x > max) {
2499 max = x;
2500 maxIndex = i;
2501 }
2502 }
2503 *maxIndexPtr = maxIndex;
2504 return minIndex;
2505}
2506
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002507static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002508 if (dir) {
2509 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2510 }
reed@google.comac8543f2012-01-30 20:51:25 +00002511}
2512
reed@google.comc1ea60a2012-01-31 15:15:36 +00002513#if 0
2514#include "SkString.h"
2515#include "../utils/SkParsePath.h"
2516static void dumpPath(const SkPath& path) {
2517 SkString str;
2518 SkParsePath::ToSVGString(path, &str);
2519 SkDebugf("%s\n", str.c_str());
2520}
2521#endif
2522
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002523namespace {
2524// for use with convex_dir_test
2525double mul(double a, double b) { return a * b; }
2526SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2527double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2528SkScalar toScalar(SkScalar a) { return a; }
2529
2530// determines the winding direction of a convex polygon with the precision
2531// of T. CAST_SCALAR casts an SkScalar to T.
2532template <typename T, T (CAST_SCALAR)(SkScalar)>
2533bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2534 // we find the first three points that form a non-degenerate
2535 // triangle. If there are no such points then the path is
2536 // degenerate. The first is always point 0. Now we find the second
2537 // point.
2538 int i = 0;
2539 enum { kX = 0, kY = 1 };
2540 T v0[2];
2541 while (1) {
2542 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2543 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2544 if (v0[kX] || v0[kY]) {
2545 break;
2546 }
2547 if (++i == n - 1) {
2548 return false;
2549 }
2550 }
2551 // now find a third point that is not colinear with the first two
2552 // points and check the orientation of the triangle (which will be
2553 // the same as the orientation of the path).
2554 for (++i; i < n; ++i) {
2555 T v1[2];
2556 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2557 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2558 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2559 if (0 != cross) {
2560 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2561 return true;
2562 }
2563 }
2564 return false;
2565}
2566}
2567
reed@google.comac8543f2012-01-30 20:51:25 +00002568/*
2569 * We loop through all contours, and keep the computed cross-product of the
2570 * contour that contained the global y-max. If we just look at the first
2571 * contour, we may find one that is wound the opposite way (correctly) since
2572 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2573 * that is outer most (or at least has the global y-max) before we can consider
2574 * its cross product.
2575 */
reed@google.com69a99432012-01-10 18:00:10 +00002576bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002577// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002578 // don't want to pay the cost for computing this if it
2579 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002580
2581 if (kUnknown_Direction != fDirection) {
2582 *dir = static_cast<Direction>(fDirection);
2583 return true;
2584 }
reed@google.com69a99432012-01-10 18:00:10 +00002585 const Convexity conv = this->getConvexityOrUnknown();
2586
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002587 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002588
reed@google.comac8543f2012-01-30 20:51:25 +00002589 // initialize with our logical y-min
2590 SkScalar ymax = this->getBounds().fTop;
2591 SkScalar ymaxCross = 0;
2592
reed@google.com69a99432012-01-10 18:00:10 +00002593 for (; !iter.done(); iter.next()) {
2594 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002595 if (n < 3) {
2596 continue;
2597 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002598
reed@google.comcabaf1d2012-01-11 21:03:05 +00002599 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002600 SkScalar cross = 0;
2601 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002602 // We try first at scalar precision, and then again at double
2603 // precision. This is because the vectors computed between distant
2604 // points may lose too much precision.
2605 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002606 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002607 return true;
2608 }
2609 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002610 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002611 return true;
2612 } else {
2613 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002614 }
2615 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002616 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002617 if (pts[index].fY < ymax) {
2618 continue;
2619 }
2620
reed@google.comc1ea60a2012-01-31 15:15:36 +00002621 // If there is more than 1 distinct point at the y-max, we take the
2622 // x-min and x-max of them and just subtract to compute the dir.
2623 if (pts[(index + 1) % n].fY == pts[index].fY) {
2624 int maxIndex;
2625 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002626 if (minIndex == maxIndex) {
2627 goto TRY_CROSSPROD;
2628 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002629 SkASSERT(pts[minIndex].fY == pts[index].fY);
2630 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2631 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2632 // we just subtract the indices, and let that auto-convert to
2633 // SkScalar, since we just want - or + to signal the direction.
2634 cross = minIndex - maxIndex;
2635 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002636 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002637 // Find a next and prev index to use for the cross-product test,
2638 // but we try to find pts that form non-zero vectors from pts[index]
2639 //
2640 // Its possible that we can't find two non-degenerate vectors, so
2641 // we have to guard our search (e.g. all the pts could be in the
2642 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002643
reed@google.comc1ea60a2012-01-31 15:15:36 +00002644 // we pass n - 1 instead of -1 so we don't foul up % operator by
2645 // passing it a negative LH argument.
2646 int prev = find_diff_pt(pts, index, n, n - 1);
2647 if (prev == index) {
2648 // completely degenerate, skip to next contour
2649 continue;
2650 }
2651 int next = find_diff_pt(pts, index, n, 1);
2652 SkASSERT(next != index);
2653 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002654 // 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 +00002655 // x-direction. We really should continue to walk away from the degeneracy until
2656 // there is a divergence.
2657 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002658 // construct the subtract so we get the correct Direction below
2659 cross = pts[index].fX - pts[next].fX;
2660 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002661 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002662
reed@google.comac8543f2012-01-30 20:51:25 +00002663 if (cross) {
2664 // record our best guess so far
2665 ymax = pts[index].fY;
2666 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002667 }
reed@google.com69a99432012-01-10 18:00:10 +00002668 }
2669 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002670 if (ymaxCross) {
2671 crossToDir(ymaxCross, dir);
2672 fDirection = *dir;
2673 return true;
2674 } else {
2675 return false;
2676 }
reed@google.comac8543f2012-01-30 20:51:25 +00002677}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002678
2679///////////////////////////////////////////////////////////////////////////////
2680
2681static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2682 SkScalar D, SkScalar t) {
2683 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2684}
2685
2686static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2687 SkScalar t) {
2688 SkScalar A = c3 + 3*(c1 - c2) - c0;
2689 SkScalar B = 3*(c2 - c1 - c1 + c0);
2690 SkScalar C = 3*(c1 - c0);
2691 SkScalar D = c0;
2692 return eval_cubic_coeff(A, B, C, D, t);
2693}
2694
2695/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2696 t value such that cubic(t) = target
2697 */
2698static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2699 SkScalar target, SkScalar* t) {
2700 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2701 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002702
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002703 SkScalar D = c0 - target;
2704 SkScalar A = c3 + 3*(c1 - c2) - c0;
2705 SkScalar B = 3*(c2 - c1 - c1 + c0);
2706 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002707
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002708 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2709 SkScalar minT = 0;
2710 SkScalar maxT = SK_Scalar1;
2711 SkScalar mid;
2712 int i;
2713 for (i = 0; i < 16; i++) {
2714 mid = SkScalarAve(minT, maxT);
2715 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2716 if (delta < 0) {
2717 minT = mid;
2718 delta = -delta;
2719 } else {
2720 maxT = mid;
2721 }
2722 if (delta < TOLERANCE) {
2723 break;
2724 }
2725 }
2726 *t = mid;
2727 return true;
2728}
2729
2730template <size_t N> static void find_minmax(const SkPoint pts[],
2731 SkScalar* minPtr, SkScalar* maxPtr) {
2732 SkScalar min, max;
2733 min = max = pts[0].fX;
2734 for (size_t i = 1; i < N; ++i) {
2735 min = SkMinScalar(min, pts[i].fX);
2736 max = SkMaxScalar(max, pts[i].fX);
2737 }
2738 *minPtr = min;
2739 *maxPtr = max;
2740}
2741
2742static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2743 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002744
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002745 int dir = 1;
2746 if (pts[0].fY > pts[3].fY) {
2747 storage[0] = pts[3];
2748 storage[1] = pts[2];
2749 storage[2] = pts[1];
2750 storage[3] = pts[0];
2751 pts = storage;
2752 dir = -1;
2753 }
2754 if (y < pts[0].fY || y >= pts[3].fY) {
2755 return 0;
2756 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002757
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002758 // quickreject or quickaccept
2759 SkScalar min, max;
2760 find_minmax<4>(pts, &min, &max);
2761 if (x < min) {
2762 return 0;
2763 }
2764 if (x > max) {
2765 return dir;
2766 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002767
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002768 // compute the actual x(t) value
2769 SkScalar t, xt;
2770 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2771 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2772 } else {
2773 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2774 xt = y < mid ? pts[0].fX : pts[3].fX;
2775 }
2776 return xt < x ? dir : 0;
2777}
2778
2779static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2780 SkPoint dst[10];
2781 int n = SkChopCubicAtYExtrema(pts, dst);
2782 int w = 0;
2783 for (int i = 0; i <= n; ++i) {
2784 w += winding_mono_cubic(&dst[i * 3], x, y);
2785 }
2786 return w;
2787}
2788
2789static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2790 SkScalar y0 = pts[0].fY;
2791 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002792
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002793 int dir = 1;
2794 if (y0 > y2) {
2795 SkTSwap(y0, y2);
2796 dir = -1;
2797 }
2798 if (y < y0 || y >= y2) {
2799 return 0;
2800 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002801
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002802 // bounds check on X (not required. is it faster?)
2803#if 0
2804 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2805 return 0;
2806 }
2807#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002808
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002809 SkScalar roots[2];
2810 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2811 2 * (pts[1].fY - pts[0].fY),
2812 pts[0].fY - y,
2813 roots);
2814 SkASSERT(n <= 1);
2815 SkScalar xt;
2816 if (0 == n) {
2817 SkScalar mid = SkScalarAve(y0, y2);
2818 // Need [0] and [2] if dir == 1
2819 // and [2] and [0] if dir == -1
2820 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2821 } else {
2822 SkScalar t = roots[0];
2823 SkScalar C = pts[0].fX;
2824 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2825 SkScalar B = 2 * (pts[1].fX - C);
2826 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2827 }
2828 return xt < x ? dir : 0;
2829}
2830
2831static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2832 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2833 if (y0 == y1) {
2834 return true;
2835 }
2836 if (y0 < y1) {
2837 return y1 <= y2;
2838 } else {
2839 return y1 >= y2;
2840 }
2841}
2842
2843static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2844 SkPoint dst[5];
2845 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002846
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002847 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2848 n = SkChopQuadAtYExtrema(pts, dst);
2849 pts = dst;
2850 }
2851 int w = winding_mono_quad(pts, x, y);
2852 if (n > 0) {
2853 w += winding_mono_quad(&pts[2], x, y);
2854 }
2855 return w;
2856}
2857
2858static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2859 SkScalar x0 = pts[0].fX;
2860 SkScalar y0 = pts[0].fY;
2861 SkScalar x1 = pts[1].fX;
2862 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002863
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002864 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002865
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002866 int dir = 1;
2867 if (y0 > y1) {
2868 SkTSwap(y0, y1);
2869 dir = -1;
2870 }
2871 if (y < y0 || y >= y1) {
2872 return 0;
2873 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002874
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2876 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002877
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002878 if (SkScalarSignAsInt(cross) == dir) {
2879 dir = 0;
2880 }
2881 return dir;
2882}
2883
2884bool SkPath::contains(SkScalar x, SkScalar y) const {
2885 bool isInverse = this->isInverseFillType();
2886 if (this->isEmpty()) {
2887 return isInverse;
2888 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002889
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002890 const SkRect& bounds = this->getBounds();
2891 if (!bounds.contains(x, y)) {
2892 return isInverse;
2893 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002894
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002895 SkPath::Iter iter(*this, true);
2896 bool done = false;
2897 int w = 0;
2898 do {
2899 SkPoint pts[4];
2900 switch (iter.next(pts, false)) {
2901 case SkPath::kMove_Verb:
2902 case SkPath::kClose_Verb:
2903 break;
2904 case SkPath::kLine_Verb:
2905 w += winding_line(pts, x, y);
2906 break;
2907 case SkPath::kQuad_Verb:
2908 w += winding_quad(pts, x, y);
2909 break;
2910 case SkPath::kCubic_Verb:
2911 w += winding_cubic(pts, x, y);
2912 break;
2913 case SkPath::kDone_Verb:
2914 done = true;
2915 break;
2916 }
2917 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002918
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002919 switch (this->getFillType()) {
2920 case SkPath::kEvenOdd_FillType:
2921 case SkPath::kInverseEvenOdd_FillType:
2922 w &= 1;
2923 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002924 default:
2925 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002926 }
2927 return SkToBool(w);
2928}
2929