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