blob: 7ea102884f367be025ebb25016c7d75066050352 [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
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000018////////////////////////////////////////////////////////////////////////////
19
20#if SK_DEBUG_PATH_REF
21
22SkPath::PathRefDebugRef::PathRefDebugRef(SkPath* owner) : fOwner(owner) {}
23
24SkPath::PathRefDebugRef::PathRefDebugRef(SkPathRef* pr, SkPath* owner)
25: fPathRef(pr)
26, fOwner(owner) {
27 pr->addOwner(owner);
28}
29
30SkPath::PathRefDebugRef::~PathRefDebugRef() {
31 fPathRef->removeOwner(fOwner);
32}
33
34void SkPath::PathRefDebugRef::reset(SkPathRef* ref) {
35 bool diff = (ref != fPathRef.get());
36 if (diff && NULL != fPathRef.get()) {
37 fPathRef.get()->removeOwner(fOwner);
38 }
39 fPathRef.reset(ref);
40 if (diff && NULL != fPathRef.get()) {
41 fPathRef.get()->addOwner(fOwner);
42 }
43}
44
45void SkPath::PathRefDebugRef::swap(SkPath::PathRefDebugRef* other) {
46 if (other->fPathRef.get() != fPathRef.get()) {
47 other->fPathRef->removeOwner(other->fOwner);
48 other->fPathRef->addOwner(fOwner);
49
50 fPathRef->removeOwner(fOwner);
51 fPathRef->addOwner(other->fOwner);
52 }
53
54 fPathRef.swap(&other->fPathRef);
55}
56
57SkPathRef* SkPath::PathRefDebugRef::get() const { return fPathRef.get(); }
58
59SkAutoTUnref<SkPathRef>::BlockRefType *SkPath::PathRefDebugRef::operator->() const {
60 return fPathRef.operator->();
61}
62
63SkPath::PathRefDebugRef::operator SkPathRef*() {
64 return fPathRef.operator SkPathRef *();
65}
66
67#endif
skia.committer@gmail.com7cc7f492012-10-04 02:01:34 +000068
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000069////////////////////////////////////////////////////////////////////////////
70
71
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000072SK_DEFINE_INST_COUNT(SkPath);
73
reed@google.com744faba2012-05-29 19:54:52 +000074// This value is just made-up for now. When count is 4, calling memset was much
75// slower than just writing the loop. This seems odd, and hopefully in the
76// future this we appear to have been a fluke...
77#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
78
reed@android.com8a1c16f2008-12-17 15:59:43 +000079////////////////////////////////////////////////////////////////////////////
80
reed@google.com3563c9e2011-11-14 19:34:57 +000081/**
82 * Path.bounds is defined to be the bounds of all the control points.
83 * If we called bounds.join(r) we would skip r if r was empty, which breaks
84 * our promise. Hence we have a custom joiner that doesn't look at emptiness
85 */
86static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
87 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
88 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
89 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
90 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
91}
92
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000093static bool is_degenerate(const SkPath& path) {
94 SkPath::Iter iter(path, false);
95 SkPoint pts[4];
96 return SkPath::kDone_Verb == iter.next(pts);
97}
98
bsalomon@google.com6aa29652012-04-18 13:29:52 +000099class SkAutoDisableOvalCheck {
100public:
101 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
102 fSaved = fPath->fIsOval;
103 }
104
105 ~SkAutoDisableOvalCheck() {
106 fPath->fIsOval = fSaved;
107 }
108
109private:
110 SkPath* fPath;
111 bool fSaved;
112};
113
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000114class SkAutoDisableDirectionCheck {
115public:
116 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
117 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
118 }
119
120 ~SkAutoDisableDirectionCheck() {
121 fPath->fDirection = fSaved;
122 }
123
124private:
125 SkPath* fPath;
126 SkPath::Direction fSaved;
127};
128
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129/* This guy's constructor/destructor bracket a path editing operation. It is
130 used when we know the bounds of the amount we are going to add to the path
131 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +0000132
reed@android.com8a1c16f2008-12-17 15:59:43 +0000133 It captures some state about the path up front (i.e. if it already has a
134 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +0000135 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +0000136
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000137 It also notes if the path was originally degenerate, and if so, sets
138 isConvex to true. Thus it can only be used if the contour being added is
139 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000140 */
141class SkAutoPathBoundsUpdate {
142public:
143 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
144 this->init(path);
145 }
146
147 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
148 SkScalar right, SkScalar bottom) {
149 fRect.set(left, top, right, bottom);
150 this->init(path);
151 }
reed@google.comabf15c12011-01-18 20:35:51 +0000152
reed@android.com8a1c16f2008-12-17 15:59:43 +0000153 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000154 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000155 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +0000156 fPath->fBounds = fRect;
157 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000158 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000159 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +0000160 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +0000161 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000162 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000163 }
164 }
reed@google.comabf15c12011-01-18 20:35:51 +0000165
reed@android.com8a1c16f2008-12-17 15:59:43 +0000166private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000167 SkPath* fPath;
168 SkRect fRect;
169 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000170 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000171 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000172
reed@android.com8a1c16f2008-12-17 15:59:43 +0000173 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000174 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000175 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000176 // Mark the path's bounds as dirty if (1) they are, or (2) the path
177 // is non-finite, and therefore its bounds are not meaningful
178 fDirty = SkToBool(path->fBoundsIsDirty) || !path->fIsFinite;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000179 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000180 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000181 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000182 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000183 }
184};
185
reed@google.com0bb18bb2012-07-26 15:20:36 +0000186// Return true if the computed bounds are finite.
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000187static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
188 int count = ref.countPoints();
reed@google.com0bb18bb2012-07-26 15:20:36 +0000189 if (count <= 1) { // we ignore just 1 point (moveto)
190 bounds->setEmpty();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000191 return count ? ref.points()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000193 return bounds->setBoundsCheck(ref.points(), count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000194 }
195}
196
197////////////////////////////////////////////////////////////////////////////
198
199/*
200 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000201 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000202 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
203
204 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000205 1. If we encounter degenerate segments, remove them
206 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
207 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
208 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000209*/
210
211////////////////////////////////////////////////////////////////////////////
212
reed@google.comd335d1d2012-01-12 18:17:11 +0000213// flag to require a moveTo if we begin with something else, like lineTo etc.
214#define INITIAL_LASTMOVETOINDEX_VALUE ~0
215
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000216SkPath::SkPath()
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000217#if SK_DEBUG_PATH_REF
218 : fPathRef(SkPathRef::CreateEmpty(), this)
219#else
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000220 : fPathRef(SkPathRef::CreateEmpty())
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000221#endif
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000222 , fFillType(kWinding_FillType)
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000223 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000224 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000225 fDirection = kUnknown_Direction;
reed@google.com10296cc2011-09-21 12:29:05 +0000226 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000227 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000228 fIsOval = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000229 fIsFinite = false; // gets computed when we know our bounds
djsollen@google.com56c69772011-11-08 19:00:26 +0000230#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000231 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000232 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000233#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000234}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000235
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000236SkPath::SkPath(const SkPath& src)
237#if SK_DEBUG_PATH_REF
238 : fPathRef(this)
239#endif
240{
reed@android.com8a1c16f2008-12-17 15:59:43 +0000241 SkDEBUGCODE(src.validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000242 src.fPathRef.get()->ref();
243 fPathRef.reset(src.fPathRef.get());
244 fBounds = src.fBounds;
245 fFillType = src.fFillType;
246 fBoundsIsDirty = src.fBoundsIsDirty;
247 fConvexity = src.fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000248 fDirection = src.fDirection;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000249 fIsFinite = src.fIsFinite;
250 fSegmentMask = src.fSegmentMask;
251 fLastMoveToIndex = src.fLastMoveToIndex;
252 fIsOval = src.fIsOval;
djsollen@google.com56c69772011-11-08 19:00:26 +0000253#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000254 fGenerationID = src.fGenerationID;
255 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000256#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000257}
258
259SkPath::~SkPath() {
260 SkDEBUGCODE(this->validate();)
261}
262
263SkPath& SkPath::operator=(const SkPath& src) {
264 SkDEBUGCODE(src.validate();)
265
266 if (this != &src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000267 src.fPathRef.get()->ref();
268 fPathRef.reset(src.fPathRef.get());
reed@android.comd252db02009-04-01 18:31:44 +0000269 fBounds = src.fBounds;
reed@android.comd252db02009-04-01 18:31:44 +0000270 fFillType = src.fFillType;
271 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000272 fConvexity = src.fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000273 fDirection = src.fDirection;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000274 fIsFinite = src.fIsFinite;
reed@google.com10296cc2011-09-21 12:29:05 +0000275 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000276 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000277 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000278 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000279 }
280 SkDEBUGCODE(this->validate();)
281 return *this;
282}
283
wjmaclean@chromium.org22023be2012-09-06 18:42:03 +0000284SK_API bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000285 // note: don't need to look at isConvex or bounds, since just comparing the
286 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000287
288 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
289 // since it is only a cache of info in the fVerbs, but its a fast way to
290 // notice a difference
291
reed@android.com3abec1d2009-03-02 05:36:20 +0000292 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000293 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000294 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000295}
296
reed@android.com8a1c16f2008-12-17 15:59:43 +0000297void SkPath::swap(SkPath& other) {
298 SkASSERT(&other != NULL);
299
300 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000301 SkTSwap<SkRect>(fBounds, other.fBounds);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000302 fPathRef.swap(&other.fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000303 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000304 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000305 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000306 SkTSwap<uint8_t>(fDirection, other.fDirection);
reed@google.com10296cc2011-09-21 12:29:05 +0000307 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000308 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000309 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
reed@google.com0bb18bb2012-07-26 15:20:36 +0000310 SkTSwap<SkBool8>(fIsFinite, other.fIsFinite);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000311 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000312 }
313}
314
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000315static inline bool check_edge_against_rect(const SkPoint& p0,
316 const SkPoint& p1,
317 const SkRect& rect,
318 SkPath::Direction dir) {
319 const SkPoint* edgeBegin;
320 SkVector v;
321 if (SkPath::kCW_Direction == dir) {
322 v = p1 - p0;
323 edgeBegin = &p0;
324 } else {
325 v = p0 - p1;
326 edgeBegin = &p1;
327 }
328 if (v.fX || v.fY) {
329 // check the cross product of v with the vec from edgeBegin to each rect corner
330 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
331 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
332 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
333 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
334 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
335 return false;
336 }
337 }
338 return true;
339}
340
341bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
342 // This only handles non-degenerate convex paths currently.
343 if (kConvex_Convexity != this->getConvexity()) {
344 return false;
345 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000346
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000347 Direction direction;
348 if (!this->cheapComputeDirection(&direction)) {
349 return false;
350 }
351
352 SkPoint firstPt;
353 SkPoint prevPt;
354 RawIter iter(*this);
355 SkPath::Verb verb;
356 SkPoint pts[4];
357 SkDEBUGCODE(int moveCnt = 0;)
358
359 while ((verb = iter.next(pts)) != kDone_Verb) {
360 int nextPt = -1;
361 switch (verb) {
362 case kMove_Verb:
363 SkASSERT(!moveCnt);
364 SkDEBUGCODE(++moveCnt);
365 firstPt = prevPt = pts[0];
366 break;
367 case kLine_Verb:
368 nextPt = 1;
369 SkASSERT(moveCnt);
370 break;
371 case kQuad_Verb:
372 SkASSERT(moveCnt);
373 nextPt = 2;
374 break;
375 case kCubic_Verb:
376 SkASSERT(moveCnt);
377 nextPt = 3;
378 break;
379 case kClose_Verb:
380 break;
381 default:
382 SkDEBUGFAIL("unknown verb");
383 }
384 if (-1 != nextPt) {
385 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
386 return false;
387 }
388 prevPt = pts[nextPt];
389 }
390 }
391
392 return check_edge_against_rect(prevPt, firstPt, rect, direction);
393}
394
djsollen@google.com56c69772011-11-08 19:00:26 +0000395#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000396uint32_t SkPath::getGenerationID() const {
397 return fGenerationID;
398}
djsollen@google.come63793a2012-03-21 15:39:03 +0000399
400const SkPath* SkPath::getSourcePath() const {
401 return fSourcePath;
402}
403
404void SkPath::setSourcePath(const SkPath* path) {
405 fSourcePath = path;
406}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000407#endif
408
reed@android.com8a1c16f2008-12-17 15:59:43 +0000409void SkPath::reset() {
410 SkDEBUGCODE(this->validate();)
411
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000412 fPathRef.reset(SkPathRef::CreateEmpty());
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000413 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000414 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000415 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000416 fDirection = kUnknown_Direction;
reed@google.com10296cc2011-09-21 12:29:05 +0000417 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000418 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000419 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000420}
421
422void SkPath::rewind() {
423 SkDEBUGCODE(this->validate();)
424
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000425 SkPathRef::Rewind(&fPathRef);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000426 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000427 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000428 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000429 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000430 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000431 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000432}
433
434bool SkPath::isEmpty() const {
435 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000436 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000437}
438
reed@google.com7e6c4d12012-05-10 14:05:43 +0000439bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000440 int verbCount = fPathRef->countVerbs();
441 int ptCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000442
reed@google.com7e6c4d12012-05-10 14:05:43 +0000443 if (2 == verbCount && 2 == ptCount) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000444 if (kMove_Verb == fPathRef->atVerb(0) &&
445 kLine_Verb == fPathRef->atVerb(1)) {
reed@google.com7e6c4d12012-05-10 14:05:43 +0000446 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000447 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000448 line[0] = pts[0];
449 line[1] = pts[1];
450 }
451 return true;
452 }
453 }
454 return false;
455}
456
caryclark@google.comf1316942011-07-26 19:54:45 +0000457/*
458 Determines if path is a rect by keeping track of changes in direction
459 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000460
caryclark@google.comf1316942011-07-26 19:54:45 +0000461 The direction is computed such that:
462 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000463 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000464 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000465 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000466
caryclark@google.comf1316942011-07-26 19:54:45 +0000467A rectangle cycles up/right/down/left or up/left/down/right.
468
469The test fails if:
470 The path is closed, and followed by a line.
471 A second move creates a new endpoint.
472 A diagonal line is parsed.
473 There's more than four changes of direction.
474 There's a discontinuity on the line (e.g., a move in the middle)
475 The line reverses direction.
476 The rectangle doesn't complete a cycle.
477 The path contains a quadratic or cubic.
478 The path contains fewer than four points.
479 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000480
caryclark@google.comf1316942011-07-26 19:54:45 +0000481It's OK if the path has:
482 Several colinear line segments composing a rectangle side.
483 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000484
caryclark@google.comf1316942011-07-26 19:54:45 +0000485The direction takes advantage of the corners found since opposite sides
486must travel in opposite directions.
487
488FIXME: Allow colinear quads and cubics to be treated like lines.
489FIXME: If the API passes fill-only, return true if the filled stroke
490 is a rectangle, though the caller failed to close the path.
491 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000492bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
493 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000494 int corners = 0;
495 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000496 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000497 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000498 first.set(0, 0);
499 last.set(0, 0);
500 int firstDirection = 0;
501 int lastDirection = 0;
502 int nextDirection = 0;
503 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000504 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000505 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000506 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
507 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000508 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000509 savePts = pts;
510 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000511 autoClose = true;
512 case kLine_Verb: {
513 SkScalar left = last.fX;
514 SkScalar top = last.fY;
515 SkScalar right = pts->fX;
516 SkScalar bottom = pts->fY;
517 ++pts;
518 if (left != right && top != bottom) {
519 return false; // diagonal
520 }
521 if (left == right && top == bottom) {
522 break; // single point on side OK
523 }
524 nextDirection = (left != right) << 0 |
525 (left < right || top < bottom) << 1;
526 if (0 == corners) {
527 firstDirection = nextDirection;
528 first = last;
529 last = pts[-1];
530 corners = 1;
531 closedOrMoved = false;
532 break;
533 }
534 if (closedOrMoved) {
535 return false; // closed followed by a line
536 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000537 if (autoClose && nextDirection == firstDirection) {
538 break; // colinear with first
539 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000540 closedOrMoved = autoClose;
541 if (lastDirection != nextDirection) {
542 if (++corners > 4) {
543 return false; // too many direction changes
544 }
545 }
546 last = pts[-1];
547 if (lastDirection == nextDirection) {
548 break; // colinear segment
549 }
550 // Possible values for corners are 2, 3, and 4.
551 // When corners == 3, nextDirection opposes firstDirection.
552 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000553 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000554 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
555 if ((directionCycle ^ turn) != nextDirection) {
556 return false; // direction didn't follow cycle
557 }
558 break;
559 }
560 case kQuad_Verb:
561 case kCubic_Verb:
562 return false; // quadratic, cubic not allowed
563 case kMove_Verb:
564 last = *pts++;
565 closedOrMoved = true;
566 break;
567 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000568 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000569 lastDirection = nextDirection;
570 }
571 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000572 bool result = 4 == corners && (first == last || autoClose);
573 if (savePts) {
574 *ptsPtr = savePts;
575 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000576 if (result && isClosed) {
577 *isClosed = autoClose;
578 }
579 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000580 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000581 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000582 return result;
583}
584
585bool SkPath::isRect(SkRect* rect) const {
586 SkDEBUGCODE(this->validate();)
587 int currVerb = 0;
588 const SkPoint* pts = fPathRef->points();
caryclark@google.comf68154a2012-11-21 15:18:06 +0000589 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
caryclark@google.comf1316942011-07-26 19:54:45 +0000590 if (result && rect) {
591 *rect = getBounds();
592 }
593 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000594}
595
caryclark@google.comf68154a2012-11-21 15:18:06 +0000596bool SkPath::isRect(bool* isClosed, Direction* direction) const {
597 SkDEBUGCODE(this->validate();)
598 int currVerb = 0;
599 const SkPoint* pts = fPathRef->points();
600 return isRectContour(false, &currVerb, &pts, isClosed, direction);
601}
602
caryclark@google.com56f233a2012-11-19 13:06:06 +0000603bool SkPath::isNestedRects(SkRect rects[2]) const {
604 SkDEBUGCODE(this->validate();)
605 int currVerb = 0;
606 const SkPoint* pts = fPathRef->points();
607 const SkPoint* first = pts;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000608 if (!isRectContour(true, &currVerb, &pts, NULL, NULL)) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000609 return false;
610 }
611 const SkPoint* last = pts;
612 SkRect testRects[2];
caryclark@google.comf68154a2012-11-21 15:18:06 +0000613 if (isRectContour(false, &currVerb, &pts, NULL, NULL)) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000614 testRects[0].set(first, last - first);
615 testRects[1].set(last, pts - last);
616 if (testRects[0].contains(testRects[1])) {
617 if (rects) {
618 rects[0] = testRects[0];
619 rects[1] = testRects[1];
620 }
621 return true;
622 }
623 if (testRects[1].contains(testRects[0])) {
624 if (rects) {
625 rects[0] = testRects[1];
626 rects[1] = testRects[0];
627 }
628 return true;
629 }
630 }
631 return false;
632}
633
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000634int SkPath::countPoints() const {
635 return fPathRef->countPoints();
636}
637
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000638int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000639 SkDEBUGCODE(this->validate();)
640
641 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000642 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643 int count = SkMin32(max, fPathRef->countPoints());
644 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
645 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000646}
647
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000648SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000649 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
650 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000651 }
652 return SkPoint::Make(0, 0);
653}
654
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000655int SkPath::countVerbs() const {
656 return fPathRef->countVerbs();
657}
658
659static inline void copy_verbs_reverse(uint8_t* inorderDst,
660 const uint8_t* reversedSrc,
661 int count) {
662 for (int i = 0; i < count; ++i) {
663 inorderDst[i] = reversedSrc[~i];
664 }
665}
666
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000667int SkPath::getVerbs(uint8_t dst[], int max) const {
668 SkDEBUGCODE(this->validate();)
669
670 SkASSERT(max >= 0);
671 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000672 int count = SkMin32(max, fPathRef->countVerbs());
673 copy_verbs_reverse(dst, fPathRef->verbs(), count);
674 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000675}
676
reed@google.com294dd7b2011-10-11 11:58:32 +0000677bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000678 SkDEBUGCODE(this->validate();)
679
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000680 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000681 if (count > 0) {
682 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000683 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000684 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000685 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000686 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000687 if (lastPt) {
688 lastPt->set(0, 0);
689 }
690 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000691}
692
693void SkPath::setLastPt(SkScalar x, SkScalar y) {
694 SkDEBUGCODE(this->validate();)
695
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000696 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000697 if (count == 0) {
698 this->moveTo(x, y);
699 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000700 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000701 SkPathRef::Editor ed(&fPathRef);
702 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000703 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704 }
705}
706
reed@android.comd252db02009-04-01 18:31:44 +0000707void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000708 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000709 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000710
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000711 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000712 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713}
714
reed@google.com04863fa2011-05-15 04:08:24 +0000715void SkPath::setConvexity(Convexity c) {
716 if (fConvexity != c) {
717 fConvexity = c;
718 GEN_ID_INC;
719 }
720}
721
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722//////////////////////////////////////////////////////////////////////////////
723// Construction methods
724
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000725#define DIRTY_AFTER_EDIT \
726 do { \
727 fBoundsIsDirty = true; \
728 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000729 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000730 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000731 } while (0)
732
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000733#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
734 do { \
735 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000736 } while (0)
737
reed@android.com8a1c16f2008-12-17 15:59:43 +0000738void SkPath::incReserve(U16CPU inc) {
739 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000740 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000741 SkDEBUGCODE(this->validate();)
742}
743
744void SkPath::moveTo(SkScalar x, SkScalar y) {
745 SkDEBUGCODE(this->validate();)
746
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000747 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000748
reed@google.comd335d1d2012-01-12 18:17:11 +0000749 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000750 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000751
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000752 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000753
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000754 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000755 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000756}
757
758void SkPath::rMoveTo(SkScalar x, SkScalar y) {
759 SkPoint pt;
760 this->getLastPt(&pt);
761 this->moveTo(pt.fX + x, pt.fY + y);
762}
763
reed@google.comd335d1d2012-01-12 18:17:11 +0000764void SkPath::injectMoveToIfNeeded() {
765 if (fLastMoveToIndex < 0) {
766 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000767 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000768 x = y = 0;
769 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000770 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000771 x = pt.fX;
772 y = pt.fY;
773 }
774 this->moveTo(x, y);
775 }
776}
777
reed@android.com8a1c16f2008-12-17 15:59:43 +0000778void SkPath::lineTo(SkScalar x, SkScalar y) {
779 SkDEBUGCODE(this->validate();)
780
reed@google.comd335d1d2012-01-12 18:17:11 +0000781 this->injectMoveToIfNeeded();
782
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000783 SkPathRef::Editor ed(&fPathRef);
784 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000785 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000786
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000787 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000788 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789}
790
791void SkPath::rLineTo(SkScalar x, SkScalar y) {
792 SkPoint pt;
793 this->getLastPt(&pt);
794 this->lineTo(pt.fX + x, pt.fY + y);
795}
796
797void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
798 SkDEBUGCODE(this->validate();)
799
reed@google.comd335d1d2012-01-12 18:17:11 +0000800 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000802 SkPathRef::Editor ed(&fPathRef);
803 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000804 pts[0].set(x1, y1);
805 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000806 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000807
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000808 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000809 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810}
811
812void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
813 SkPoint pt;
814 this->getLastPt(&pt);
815 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
816}
817
818void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
819 SkScalar x3, SkScalar y3) {
820 SkDEBUGCODE(this->validate();)
821
reed@google.comd335d1d2012-01-12 18:17:11 +0000822 this->injectMoveToIfNeeded();
823
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000824 SkPathRef::Editor ed(&fPathRef);
825 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000826 pts[0].set(x1, y1);
827 pts[1].set(x2, y2);
828 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000829 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000830
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000831 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000832 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000833}
834
835void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
836 SkScalar x3, SkScalar y3) {
837 SkPoint pt;
838 this->getLastPt(&pt);
839 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
840 pt.fX + x3, pt.fY + y3);
841}
842
843void SkPath::close() {
844 SkDEBUGCODE(this->validate();)
845
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000846 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000847 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000848 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000849 case kLine_Verb:
850 case kQuad_Verb:
851 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000852 case kMove_Verb: {
853 SkPathRef::Editor ed(&fPathRef);
854 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000855 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000857 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000859 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000860 break;
861 }
862 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000863
864 // signal that we need a moveTo to follow us (unless we're done)
865#if 0
866 if (fLastMoveToIndex >= 0) {
867 fLastMoveToIndex = ~fLastMoveToIndex;
868 }
869#else
870 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
871#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000872}
873
874///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000875
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000876static void assert_known_direction(int dir) {
877 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
878}
879
reed@android.com8a1c16f2008-12-17 15:59:43 +0000880void SkPath::addRect(const SkRect& rect, Direction dir) {
881 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
882}
883
884void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
885 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000886 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000887 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
888 SkAutoDisableDirectionCheck addc(this);
889
reed@android.com8a1c16f2008-12-17 15:59:43 +0000890 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
891
892 this->incReserve(5);
893
894 this->moveTo(left, top);
895 if (dir == kCCW_Direction) {
896 this->lineTo(left, bottom);
897 this->lineTo(right, bottom);
898 this->lineTo(right, top);
899 } else {
900 this->lineTo(right, top);
901 this->lineTo(right, bottom);
902 this->lineTo(left, bottom);
903 }
904 this->close();
905}
906
reed@google.com744faba2012-05-29 19:54:52 +0000907void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
908 SkDEBUGCODE(this->validate();)
909 if (count <= 0) {
910 return;
911 }
912
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000913 SkPathRef::Editor ed(&fPathRef);
914 fLastMoveToIndex = ed.pathRef()->countPoints();
915 uint8_t* vb;
916 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000917 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000918 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000919
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000920 memcpy(p, pts, count * sizeof(SkPoint));
921 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000922 if (count > 1) {
923 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
924 // be 0, the compiler will remove the test/branch entirely.
925 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000926 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000927 } else {
928 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000929 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000930 }
931 }
932 fSegmentMask |= kLine_SegmentMask;
933 }
934 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000935 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000936 }
937
938 GEN_ID_INC;
939 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000940 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000941}
942
reed@android.com8a1c16f2008-12-17 15:59:43 +0000943static void add_corner_arc(SkPath* path, const SkRect& rect,
944 SkScalar rx, SkScalar ry, int startAngle,
945 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000946 // These two asserts are not sufficient, since really we want to know
947 // that the pair of radii (e.g. left and right, or top and bottom) sum
948 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000949 // these conservative asserts.
950 SkASSERT(0 <= rx && rx <= rect.width());
951 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000952
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953 SkRect r;
954 r.set(-rx, -ry, rx, ry);
955
956 switch (startAngle) {
957 case 0:
958 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
959 break;
960 case 90:
961 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
962 break;
963 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
964 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000965 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000966 }
reed@google.comabf15c12011-01-18 20:35:51 +0000967
reed@android.com8a1c16f2008-12-17 15:59:43 +0000968 SkScalar start = SkIntToScalar(startAngle);
969 SkScalar sweep = SkIntToScalar(90);
970 if (SkPath::kCCW_Direction == dir) {
971 start += sweep;
972 sweep = -sweep;
973 }
reed@google.comabf15c12011-01-18 20:35:51 +0000974
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975 path->arcTo(r, start, sweep, forceMoveTo);
976}
977
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000978void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000979 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000980 SkRRect rrect;
981 rrect.setRectRadii(rect, (const SkVector*) radii);
982 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000983}
984
reed@google.com4ed0fb72012-12-12 20:48:18 +0000985void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000986 assert_known_direction(dir);
987
988 if (rrect.isEmpty()) {
989 return;
990 }
991
reed@google.com4ed0fb72012-12-12 20:48:18 +0000992 const SkRect& bounds = rrect.getBounds();
993
994 if (rrect.isRect()) {
995 this->addRect(bounds, dir);
996 } else if (rrect.isOval()) {
997 this->addOval(bounds, dir);
998 } else if (rrect.isSimple()) {
999 const SkVector& rad = rrect.getSimpleRadii();
1000 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1001 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001002 SkAutoPathBoundsUpdate apbu(this, bounds);
1003
1004 if (kCW_Direction == dir) {
1005 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1006 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1007 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1008 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1009 } else {
1010 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1011 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1012 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1013 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1014 }
1015 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001016 }
1017}
1018
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001019bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001020 int count = fPathRef->countVerbs();
1021 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1022 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001023 if (*verbs == kLine_Verb ||
1024 *verbs == kQuad_Verb ||
1025 *verbs == kCubic_Verb) {
1026 return false;
1027 }
1028 ++verbs;
1029 }
1030 return true;
1031}
1032
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001033#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
1034
1035void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1036 Direction dir) {
1037 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001038
humper@google.com75e3ca12013-04-08 21:44:11 +00001039 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001040 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001041 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001042 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001043 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1044 return;
1045 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001046
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001047 SkScalar w = rect.width();
1048 SkScalar halfW = SkScalarHalf(w);
1049 SkScalar h = rect.height();
1050 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001051
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001052 if (halfW <= 0 || halfH <= 0) {
1053 return;
1054 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001055
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001056 bool skip_hori = rx >= halfW;
1057 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001058
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001059 if (skip_hori && skip_vert) {
1060 this->addOval(rect, dir);
1061 return;
1062 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001063
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001064 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001065
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001066 SkAutoPathBoundsUpdate apbu(this, rect);
1067 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001068
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001069 if (skip_hori) {
1070 rx = halfW;
1071 } else if (skip_vert) {
1072 ry = halfH;
1073 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001074
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001075 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1076 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001077
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001078 this->incReserve(17);
1079 this->moveTo(rect.fRight - rx, rect.fTop);
1080 if (dir == kCCW_Direction) {
1081 if (!skip_hori) {
1082 this->lineTo(rect.fLeft + rx, rect.fTop); // top
1083 }
1084 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1085 rect.fLeft, rect.fTop + ry - sy,
1086 rect.fLeft, rect.fTop + ry); // top-left
1087 if (!skip_vert) {
1088 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1089 }
1090 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1091 rect.fLeft + rx - sx, rect.fBottom,
1092 rect.fLeft + rx, rect.fBottom); // bot-left
1093 if (!skip_hori) {
1094 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
1095 }
1096 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1097 rect.fRight, rect.fBottom - ry + sy,
1098 rect.fRight, rect.fBottom - ry); // bot-right
1099 if (!skip_vert) {
1100 this->lineTo(rect.fRight, rect.fTop + ry);
1101 }
1102 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1103 rect.fRight - rx + sx, rect.fTop,
1104 rect.fRight - rx, rect.fTop); // top-right
1105 } else {
1106 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1107 rect.fRight, rect.fTop + ry - sy,
1108 rect.fRight, rect.fTop + ry); // top-right
1109 if (!skip_vert) {
1110 this->lineTo(rect.fRight, rect.fBottom - ry);
1111 }
1112 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1113 rect.fRight - rx + sx, rect.fBottom,
1114 rect.fRight - rx, rect.fBottom); // bot-right
1115 if (!skip_hori) {
1116 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1117 }
1118 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1119 rect.fLeft, rect.fBottom - ry + sy,
1120 rect.fLeft, rect.fBottom - ry); // bot-left
1121 if (!skip_vert) {
1122 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1123 }
1124 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1125 rect.fLeft + rx - sx, rect.fTop,
1126 rect.fLeft + rx, rect.fTop); // top-left
1127 if (!skip_hori) {
1128 this->lineTo(rect.fRight - rx, rect.fTop); // top
1129 }
1130 }
1131 this->close();
1132}
1133
reed@android.com8a1c16f2008-12-17 15:59:43 +00001134void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001135 assert_known_direction(dir);
1136
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001137 /* If addOval() is called after previous moveTo(),
1138 this path is still marked as an oval. This is used to
1139 fit into WebKit's calling sequences.
1140 We can't simply check isEmpty() in this case, as additional
1141 moveTo() would mark the path non empty.
1142 */
1143 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001144 if (fIsOval) {
1145 fDirection = dir;
1146 } else {
1147 fDirection = kUnknown_Direction;
1148 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001149
1150 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001151 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001152
reed@android.com8a1c16f2008-12-17 15:59:43 +00001153 SkAutoPathBoundsUpdate apbu(this, oval);
1154
1155 SkScalar cx = oval.centerX();
1156 SkScalar cy = oval.centerY();
1157 SkScalar rx = SkScalarHalf(oval.width());
1158 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001159
reed@android.com8a1c16f2008-12-17 15:59:43 +00001160 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1161 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1162 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1163 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1164
1165 /*
1166 To handle imprecision in computing the center and radii, we revert to
1167 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1168 to ensure that we don't exceed the oval's bounds *ever*, since we want
1169 to use oval for our fast-bounds, rather than have to recompute it.
1170 */
1171 const SkScalar L = oval.fLeft; // cx - rx
1172 const SkScalar T = oval.fTop; // cy - ry
1173 const SkScalar R = oval.fRight; // cx + rx
1174 const SkScalar B = oval.fBottom; // cy + ry
1175
1176 this->incReserve(17); // 8 quads + close
1177 this->moveTo(R, cy);
1178 if (dir == kCCW_Direction) {
1179 this->quadTo( R, cy - sy, cx + mx, cy - my);
1180 this->quadTo(cx + sx, T, cx , T);
1181 this->quadTo(cx - sx, T, 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, B, cx , B);
1185 this->quadTo(cx + sx, B, cx + mx, cy + my);
1186 this->quadTo( R, cy + sy, R, cy );
1187 } else {
1188 this->quadTo( R, cy + sy, cx + mx, cy + my);
1189 this->quadTo(cx + sx, B, cx , B);
1190 this->quadTo(cx - sx, B, cx - mx, cy + my);
1191 this->quadTo( L, cy + sy, L, cy );
1192 this->quadTo( L, cy - sy, cx - mx, cy - my);
1193 this->quadTo(cx - sx, T, cx , T);
1194 this->quadTo(cx + sx, T, cx + mx, cy - my);
1195 this->quadTo( R, cy - sy, R, cy );
1196 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001197 this->close();
1198}
1199
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001200bool SkPath::isOval(SkRect* rect) const {
1201 if (fIsOval && rect) {
1202 *rect = getBounds();
1203 }
1204
1205 return fIsOval;
1206}
1207
reed@android.com8a1c16f2008-12-17 15:59:43 +00001208void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1209 if (r > 0) {
1210 SkRect rect;
1211 rect.set(x - r, y - r, x + r, y + r);
1212 this->addOval(rect, dir);
1213 }
1214}
1215
1216#include "SkGeometry.h"
1217
1218static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1219 SkScalar sweepAngle,
1220 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001221
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001222 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001223 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1224 // Chrome uses this path to move into and out of ovals. If not
1225 // treated as a special case the moves can distort the oval's
1226 // bounding box (and break the circle special case).
1227 pts[0].set(oval.fRight, oval.centerY());
1228 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001229 } else if (0 == oval.width() && 0 == oval.height()) {
1230 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001231 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001232 // a rect.
1233 // TODO: optimizing the case where only one of width or height is zero
1234 // should also be considered. This case, however, doesn't seem to be
1235 // as common as the single point case.
1236 pts[0].set(oval.fRight, oval.fTop);
1237 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001238 }
1239
reed@android.com8a1c16f2008-12-17 15:59:43 +00001240 SkVector start, stop;
1241
1242 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1243 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1244 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001245
1246 /* If the sweep angle is nearly (but less than) 360, then due to precision
1247 loss in radians-conversion and/or sin/cos, we may end up with coincident
1248 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1249 of drawing a nearly complete circle (good).
1250 e.g. canvas.drawArc(0, 359.99, ...)
1251 -vs- canvas.drawArc(0, 359.9, ...)
1252 We try to detect this edge case, and tweak the stop vector
1253 */
1254 if (start == stop) {
1255 SkScalar sw = SkScalarAbs(sweepAngle);
1256 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1257 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1258 // make a guess at a tiny angle (in radians) to tweak by
1259 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1260 // not sure how much will be enough, so we use a loop
1261 do {
1262 stopRad -= deltaRad;
1263 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1264 } while (start == stop);
1265 }
1266 }
1267
reed@android.com8a1c16f2008-12-17 15:59:43 +00001268 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001269
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1271 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001272
reed@android.com8a1c16f2008-12-17 15:59:43 +00001273 return SkBuildQuadArc(start, stop,
1274 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1275 &matrix, pts);
1276}
1277
1278void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1279 bool forceMoveTo) {
1280 if (oval.width() < 0 || oval.height() < 0) {
1281 return;
1282 }
1283
1284 SkPoint pts[kSkBuildQuadArcStorage];
1285 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1286 SkASSERT((count & 1) == 1);
1287
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001288 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001289 forceMoveTo = true;
1290 }
1291 this->incReserve(count);
1292 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1293 for (int i = 1; i < count; i += 2) {
1294 this->quadTo(pts[i], pts[i+1]);
1295 }
1296}
1297
1298void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1299 SkScalar sweepAngle) {
1300 if (oval.isEmpty() || 0 == sweepAngle) {
1301 return;
1302 }
1303
1304 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1305
1306 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1307 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1308 return;
1309 }
1310
1311 SkPoint pts[kSkBuildQuadArcStorage];
1312 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1313
1314 this->incReserve(count);
1315 this->moveTo(pts[0]);
1316 for (int i = 1; i < count; i += 2) {
1317 this->quadTo(pts[i], pts[i+1]);
1318 }
1319}
1320
1321/*
1322 Need to handle the case when the angle is sharp, and our computed end-points
1323 for the arc go behind pt1 and/or p2...
1324*/
1325void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1326 SkScalar radius) {
1327 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001328
reed@android.com8a1c16f2008-12-17 15:59:43 +00001329 // need to know our prev pt so we can construct tangent vectors
1330 {
1331 SkPoint start;
1332 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001333 // Handle degenerate cases by adding a line to the first point and
1334 // bailing out.
1335 if ((x1 == start.fX && y1 == start.fY) ||
1336 (x1 == x2 && y1 == y2) ||
1337 radius == 0) {
1338 this->lineTo(x1, y1);
1339 return;
1340 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001341 before.setNormalize(x1 - start.fX, y1 - start.fY);
1342 after.setNormalize(x2 - x1, y2 - y1);
1343 }
reed@google.comabf15c12011-01-18 20:35:51 +00001344
reed@android.com8a1c16f2008-12-17 15:59:43 +00001345 SkScalar cosh = SkPoint::DotProduct(before, after);
1346 SkScalar sinh = SkPoint::CrossProduct(before, after);
1347
1348 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001349 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350 return;
1351 }
reed@google.comabf15c12011-01-18 20:35:51 +00001352
reed@android.com8a1c16f2008-12-17 15:59:43 +00001353 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1354 if (dist < 0) {
1355 dist = -dist;
1356 }
1357
1358 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1359 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1360 SkRotationDirection arcDir;
1361
1362 // now turn before/after into normals
1363 if (sinh > 0) {
1364 before.rotateCCW();
1365 after.rotateCCW();
1366 arcDir = kCW_SkRotationDirection;
1367 } else {
1368 before.rotateCW();
1369 after.rotateCW();
1370 arcDir = kCCW_SkRotationDirection;
1371 }
1372
1373 SkMatrix matrix;
1374 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001375
reed@android.com8a1c16f2008-12-17 15:59:43 +00001376 matrix.setScale(radius, radius);
1377 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1378 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001379
reed@android.com8a1c16f2008-12-17 15:59:43 +00001380 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001381
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382 this->incReserve(count);
1383 // [xx,yy] == pts[0]
1384 this->lineTo(xx, yy);
1385 for (int i = 1; i < count; i += 2) {
1386 this->quadTo(pts[i], pts[i+1]);
1387 }
1388}
1389
1390///////////////////////////////////////////////////////////////////////////////
1391
1392void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1393 SkMatrix matrix;
1394
1395 matrix.setTranslate(dx, dy);
1396 this->addPath(path, matrix);
1397}
1398
1399void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001400 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001401
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001402 fIsOval = false;
1403
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001404 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001405 SkPoint pts[4];
1406 Verb verb;
1407
1408 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1409
1410 while ((verb = iter.next(pts)) != kDone_Verb) {
1411 switch (verb) {
1412 case kMove_Verb:
1413 proc(matrix, &pts[0], &pts[0], 1);
1414 this->moveTo(pts[0]);
1415 break;
1416 case kLine_Verb:
1417 proc(matrix, &pts[1], &pts[1], 1);
1418 this->lineTo(pts[1]);
1419 break;
1420 case kQuad_Verb:
1421 proc(matrix, &pts[1], &pts[1], 2);
1422 this->quadTo(pts[1], pts[2]);
1423 break;
1424 case kCubic_Verb:
1425 proc(matrix, &pts[1], &pts[1], 3);
1426 this->cubicTo(pts[1], pts[2], pts[3]);
1427 break;
1428 case kClose_Verb:
1429 this->close();
1430 break;
1431 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001432 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001433 }
1434 }
1435}
1436
1437///////////////////////////////////////////////////////////////////////////////
1438
1439static const uint8_t gPtsInVerb[] = {
1440 1, // kMove
1441 1, // kLine
1442 2, // kQuad
1443 3, // kCubic
1444 0, // kClose
1445 0 // kDone
1446};
1447
1448// ignore the initial moveto, and stop when the 1st contour ends
1449void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001450 int i, vcount = path.fPathRef->countVerbs();
1451 // exit early if the path is empty, or just has a moveTo.
1452 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001453 return;
1454 }
1455
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001456 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001458 fIsOval = false;
1459
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001460 const uint8_t* verbs = path.fPathRef->verbs();
1461 // skip the initial moveTo
1462 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001463
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001464 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001465 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001466 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001467 case kLine_Verb:
1468 this->lineTo(pts[0].fX, pts[0].fY);
1469 break;
1470 case kQuad_Verb:
1471 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1472 break;
1473 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001474 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 +00001475 break;
1476 case kClose_Verb:
1477 return;
1478 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001479 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001480 }
1481}
1482
1483// ignore the last point of the 1st contour
1484void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001485 int i, vcount = path.fPathRef->countVerbs();
1486 // exit early if the path is empty, or just has a moveTo.
1487 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001488 return;
1489 }
1490
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001491 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001492
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001493 fIsOval = false;
1494
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001495 const uint8_t* verbs = path.fPathRef->verbs();
1496 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001497
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001498 SkASSERT(verbs[~0] == kMove_Verb);
1499 for (i = 1; i < vcount; ++i) {
1500 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501 if (n == 0) {
1502 break;
1503 }
1504 pts += n;
1505 }
1506
1507 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001508 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001509 case kLine_Verb:
1510 this->lineTo(pts[-1].fX, pts[-1].fY);
1511 break;
1512 case kQuad_Verb:
1513 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1514 break;
1515 case kCubic_Verb:
1516 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1517 pts[-3].fX, pts[-3].fY);
1518 break;
1519 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001520 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521 break;
1522 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001523 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001524 }
1525}
1526
reed@google.com63d73742012-01-10 15:33:12 +00001527void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001528 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001529
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001530 const SkPoint* pts = src.fPathRef->pointsEnd();
1531 // we will iterator through src's verbs backwards
1532 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1533 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001534
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001535 fIsOval = false;
1536
reed@google.com63d73742012-01-10 15:33:12 +00001537 bool needMove = true;
1538 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001539 while (verbs < verbsEnd) {
1540 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001541 int n = gPtsInVerb[v];
1542
1543 if (needMove) {
1544 --pts;
1545 this->moveTo(pts->fX, pts->fY);
1546 needMove = false;
1547 }
1548 pts -= n;
1549 switch (v) {
1550 case kMove_Verb:
1551 if (needClose) {
1552 this->close();
1553 needClose = false;
1554 }
1555 needMove = true;
1556 pts += 1; // so we see the point in "if (needMove)" above
1557 break;
1558 case kLine_Verb:
1559 this->lineTo(pts[0]);
1560 break;
1561 case kQuad_Verb:
1562 this->quadTo(pts[1], pts[0]);
1563 break;
1564 case kCubic_Verb:
1565 this->cubicTo(pts[2], pts[1], pts[0]);
1566 break;
1567 case kClose_Verb:
1568 needClose = true;
1569 break;
1570 default:
1571 SkASSERT(!"unexpected verb");
1572 }
1573 }
1574}
1575
reed@android.com8a1c16f2008-12-17 15:59:43 +00001576///////////////////////////////////////////////////////////////////////////////
1577
1578void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1579 SkMatrix matrix;
1580
1581 matrix.setTranslate(dx, dy);
1582 this->transform(matrix, dst);
1583}
1584
1585#include "SkGeometry.h"
1586
1587static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1588 int level = 2) {
1589 if (--level >= 0) {
1590 SkPoint tmp[5];
1591
1592 SkChopQuadAtHalf(pts, tmp);
1593 subdivide_quad_to(path, &tmp[0], level);
1594 subdivide_quad_to(path, &tmp[2], level);
1595 } else {
1596 path->quadTo(pts[1], pts[2]);
1597 }
1598}
1599
1600static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1601 int level = 2) {
1602 if (--level >= 0) {
1603 SkPoint tmp[7];
1604
1605 SkChopCubicAtHalf(pts, tmp);
1606 subdivide_cubic_to(path, &tmp[0], level);
1607 subdivide_cubic_to(path, &tmp[3], level);
1608 } else {
1609 path->cubicTo(pts[1], pts[2], pts[3]);
1610 }
1611}
1612
1613void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1614 SkDEBUGCODE(this->validate();)
1615 if (dst == NULL) {
1616 dst = (SkPath*)this;
1617 }
1618
tomhudson@google.com8d430182011-06-06 19:11:19 +00001619 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001620 SkPath tmp;
1621 tmp.fFillType = fFillType;
1622
1623 SkPath::Iter iter(*this, false);
1624 SkPoint pts[4];
1625 SkPath::Verb verb;
1626
reed@google.com4a3b7142012-05-16 17:16:46 +00001627 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001628 switch (verb) {
1629 case kMove_Verb:
1630 tmp.moveTo(pts[0]);
1631 break;
1632 case kLine_Verb:
1633 tmp.lineTo(pts[1]);
1634 break;
1635 case kQuad_Verb:
1636 subdivide_quad_to(&tmp, pts);
1637 break;
1638 case kCubic_Verb:
1639 subdivide_cubic_to(&tmp, pts);
1640 break;
1641 case kClose_Verb:
1642 tmp.close();
1643 break;
1644 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001645 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001646 break;
1647 }
1648 }
1649
1650 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001651 SkPathRef::Editor ed(&dst->fPathRef);
1652 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001653 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001655 /*
1656 * If we're not in perspective, we can transform all of the points at
1657 * once.
1658 *
1659 * Here we also want to optimize bounds, by noting if the bounds are
1660 * already known, and if so, we just transform those as well and mark
1661 * them as "known", rather than force the transformed path to have to
1662 * recompute them.
1663 *
1664 * Special gotchas if the path is effectively empty (<= 1 point) or
1665 * if it is non-finite. In those cases bounds need to stay empty,
1666 * regardless of the matrix.
1667 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001668 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001669 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001670 if (fIsFinite) {
1671 matrix.mapRect(&dst->fBounds, fBounds);
1672 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1673 dst->fBounds.setEmpty();
1674 }
1675 } else {
1676 dst->fIsFinite = false;
1677 dst->fBounds.setEmpty();
1678 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001679 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001680 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001681 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001682 }
1683
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001684 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1685
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001687 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001688 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001689 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001690 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001691
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001692#ifdef SK_BUILD_FOR_ANDROID
1693 if (!matrix.isIdentity()) {
1694 GEN_ID_PTR_INC(dst);
1695 }
1696#endif
1697
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001698 if (kUnknown_Direction == fDirection) {
1699 dst->fDirection = kUnknown_Direction;
1700 } else {
1701 SkScalar det2x2 =
1702 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1703 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1704 if (det2x2 < 0) {
1705 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1706 } else if (det2x2 > 0) {
1707 dst->fDirection = fDirection;
1708 } else {
1709 dst->fDirection = kUnknown_Direction;
1710 }
1711 }
1712
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001713 // It's an oval only if it stays a rect.
1714 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001715
reed@android.com8a1c16f2008-12-17 15:59:43 +00001716 SkDEBUGCODE(dst->validate();)
1717 }
1718}
1719
reed@android.com8a1c16f2008-12-17 15:59:43 +00001720///////////////////////////////////////////////////////////////////////////////
1721///////////////////////////////////////////////////////////////////////////////
1722
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001723enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001724 kEmptyContour_SegmentState, // The current contour is empty. We may be
1725 // starting processing or we may have just
1726 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001727 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1728 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1729 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730};
1731
1732SkPath::Iter::Iter() {
1733#ifdef SK_DEBUG
1734 fPts = NULL;
1735 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001736 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001737 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001738#endif
1739 // need to init enough to make next() harmlessly return kDone_Verb
1740 fVerbs = NULL;
1741 fVerbStop = NULL;
1742 fNeedClose = false;
1743}
1744
1745SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1746 this->setPath(path, forceClose);
1747}
1748
1749void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001750 fPts = path.fPathRef->points();
1751 fVerbs = path.fPathRef->verbs();
1752 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001753 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001754 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001755 fForceClose = SkToU8(forceClose);
1756 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001757 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001758}
1759
1760bool SkPath::Iter::isClosedContour() const {
1761 if (fVerbs == NULL || fVerbs == fVerbStop) {
1762 return false;
1763 }
1764 if (fForceClose) {
1765 return true;
1766 }
1767
1768 const uint8_t* verbs = fVerbs;
1769 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001770
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001771 if (kMove_Verb == *(verbs - 1)) {
1772 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 }
1774
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001775 while (verbs > stop) {
1776 // verbs points one beyond the current verb, decrement first.
1777 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001778 if (kMove_Verb == v) {
1779 break;
1780 }
1781 if (kClose_Verb == v) {
1782 return true;
1783 }
1784 }
1785 return false;
1786}
1787
1788SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001789 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001790 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001791 // A special case: if both points are NaN, SkPoint::operation== returns
1792 // false, but the iterator expects that they are treated as the same.
1793 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001794 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1795 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001796 return kClose_Verb;
1797 }
1798
reed@google.com9e25dbf2012-05-15 17:05:38 +00001799 pts[0] = fLastPt;
1800 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001801 fLastPt = fMoveTo;
1802 fCloseLine = true;
1803 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001804 } else {
1805 pts[0] = fMoveTo;
1806 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001808}
1809
reed@google.com9e25dbf2012-05-15 17:05:38 +00001810const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001811 if (fSegmentState == kAfterMove_SegmentState) {
1812 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001813 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001814 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001815 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001816 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1817 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001818 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001819 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001820}
1821
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001822void SkPath::Iter::consumeDegenerateSegments() {
1823 // We need to step over anything that will not move the current draw point
1824 // forward before the next move is seen
1825 const uint8_t* lastMoveVerb = 0;
1826 const SkPoint* lastMovePt = 0;
1827 SkPoint lastPt = fLastPt;
1828 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001829 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001830 switch (verb) {
1831 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001832 // Keep a record of this most recent move
1833 lastMoveVerb = fVerbs;
1834 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001835 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001836 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001837 fPts++;
1838 break;
1839
1840 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001841 // A close when we are in a segment is always valid except when it
1842 // follows a move which follows a segment.
1843 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001844 return;
1845 }
1846 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001847 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001848 break;
1849
1850 case kLine_Verb:
1851 if (!IsLineDegenerate(lastPt, fPts[0])) {
1852 if (lastMoveVerb) {
1853 fVerbs = lastMoveVerb;
1854 fPts = lastMovePt;
1855 return;
1856 }
1857 return;
1858 }
1859 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001860 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001861 fPts++;
1862 break;
1863
1864 case kQuad_Verb:
1865 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1866 if (lastMoveVerb) {
1867 fVerbs = lastMoveVerb;
1868 fPts = lastMovePt;
1869 return;
1870 }
1871 return;
1872 }
1873 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001874 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001875 fPts += 2;
1876 break;
1877
1878 case kCubic_Verb:
1879 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1880 if (lastMoveVerb) {
1881 fVerbs = lastMoveVerb;
1882 fPts = lastMovePt;
1883 return;
1884 }
1885 return;
1886 }
1887 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001888 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001889 fPts += 3;
1890 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001891
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001892 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001893 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001894 }
1895 }
1896}
1897
reed@google.com4a3b7142012-05-16 17:16:46 +00001898SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001899 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001900
reed@android.com8a1c16f2008-12-17 15:59:43 +00001901 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001902 // Close the curve if requested and if there is some curve to close
1903 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001904 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001905 return kLine_Verb;
1906 }
1907 fNeedClose = false;
1908 return kClose_Verb;
1909 }
1910 return kDone_Verb;
1911 }
1912
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001913 // fVerbs is one beyond the current verb, decrement first
1914 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001915 const SkPoint* SK_RESTRICT srcPts = fPts;
1916 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001917
1918 switch (verb) {
1919 case kMove_Verb:
1920 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001921 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001922 verb = this->autoClose(pts);
1923 if (verb == kClose_Verb) {
1924 fNeedClose = false;
1925 }
1926 return (Verb)verb;
1927 }
1928 if (fVerbs == fVerbStop) { // might be a trailing moveto
1929 return kDone_Verb;
1930 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001931 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001932 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001934 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001935 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001936 fNeedClose = fForceClose;
1937 break;
1938 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001939 pts[0] = this->cons_moveTo();
1940 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001941 fLastPt = srcPts[0];
1942 fCloseLine = false;
1943 srcPts += 1;
1944 break;
1945 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001946 pts[0] = this->cons_moveTo();
1947 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001948 fLastPt = srcPts[1];
1949 srcPts += 2;
1950 break;
1951 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001952 pts[0] = this->cons_moveTo();
1953 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001954 fLastPt = srcPts[2];
1955 srcPts += 3;
1956 break;
1957 case kClose_Verb:
1958 verb = this->autoClose(pts);
1959 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001960 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001961 } else {
1962 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001963 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001964 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001965 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001966 break;
1967 }
1968 fPts = srcPts;
1969 return (Verb)verb;
1970}
1971
1972///////////////////////////////////////////////////////////////////////////////
1973
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001974SkPath::RawIter::RawIter() {
1975#ifdef SK_DEBUG
1976 fPts = NULL;
1977 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1978#endif
1979 // need to init enough to make next() harmlessly return kDone_Verb
1980 fVerbs = NULL;
1981 fVerbStop = NULL;
1982}
1983
1984SkPath::RawIter::RawIter(const SkPath& path) {
1985 this->setPath(path);
1986}
1987
1988void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001989 fPts = path.fPathRef->points();
1990 fVerbs = path.fPathRef->verbs();
1991 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001992 fMoveTo.fX = fMoveTo.fY = 0;
1993 fLastPt.fX = fLastPt.fY = 0;
1994}
1995
1996SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001997 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001998 if (fVerbs == fVerbStop) {
1999 return kDone_Verb;
2000 }
2001
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002002 // fVerbs points one beyond next verb so decrement first.
2003 unsigned verb = *(--fVerbs);
2004 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002005
2006 switch (verb) {
2007 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002008 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002009 fMoveTo = srcPts[0];
2010 fLastPt = fMoveTo;
2011 srcPts += 1;
2012 break;
2013 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002014 pts[0] = fLastPt;
2015 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002016 fLastPt = srcPts[0];
2017 srcPts += 1;
2018 break;
2019 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002020 pts[0] = fLastPt;
2021 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002022 fLastPt = srcPts[1];
2023 srcPts += 2;
2024 break;
2025 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002026 pts[0] = fLastPt;
2027 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002028 fLastPt = srcPts[2];
2029 srcPts += 3;
2030 break;
2031 case kClose_Verb:
2032 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002033 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002034 break;
2035 }
2036 fPts = srcPts;
2037 return (Verb)verb;
2038}
2039
2040///////////////////////////////////////////////////////////////////////////////
2041
reed@android.com8a1c16f2008-12-17 15:59:43 +00002042/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002043 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002044*/
2045
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002046uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002047 SkDEBUGCODE(this->validate();)
2048
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002049 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002050 const int byteCount = sizeof(int32_t)
2051#if NEW_PICTURE_FORMAT
2052 + fPathRef->writeSize()
2053#else
2054 + 2 * sizeof(int32_t)
2055 + sizeof(SkPoint) * fPathRef->countPoints()
2056 + sizeof(uint8_t) * fPathRef->countVerbs()
2057#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002058 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002059 return SkAlign4(byteCount);
2060 }
2061
2062 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002063#if !NEW_PICTURE_FORMAT
2064 buffer.write32(fPathRef->countPoints());
2065 buffer.write32(fPathRef->countVerbs());
2066#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002067
2068 // Call getBounds() to ensure (as a side-effect) that fBounds
2069 // and fIsFinite are computed.
2070 const SkRect& bounds = this->getBounds();
2071 SkASSERT(!fBoundsIsDirty);
2072
2073 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2074 ((fIsOval & 1) << kIsOval_SerializationShift) |
2075 (fConvexity << kConvexity_SerializationShift) |
2076 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002077 (fSegmentMask << kSegmentMask_SerializationShift) |
2078 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002079
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002080 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002081
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002082 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002083
2084 buffer.write(&bounds, sizeof(bounds));
2085
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002086 buffer.padToAlign4();
2087 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002088}
2089
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002090uint32_t SkPath::readFromMemory(const void* storage) {
2091 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002092#if !NEW_PICTURE_FORMAT
2093 int32_t pcount = buffer.readS32();
2094 int32_t vcount = buffer.readS32();
2095#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002096
reed@google.com98b11f12011-09-21 18:40:27 +00002097 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002098 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2099 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2100 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2101 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002102 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
2103 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002104
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002105#if NEW_PICTURE_FORMAT
2106 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
2107#else
2108 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
2109#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002110
2111 buffer.read(&fBounds, sizeof(fBounds));
2112 fBoundsIsDirty = false;
2113
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002114 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002115
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002116 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002117
2118 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002119 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120}
2121
2122///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002123
reed@google.com51bbe752013-01-17 22:07:50 +00002124#include "SkString.h"
2125
2126static void append_scalar(SkString* str, SkScalar value) {
2127 SkString tmp;
2128 tmp.printf("%g", value);
2129 if (tmp.contains('.')) {
2130 tmp.appendUnichar('f');
2131 }
2132 str->append(tmp);
2133}
2134
2135static void append_params(SkString* str, const char label[], const SkPoint pts[],
2136 int count) {
2137 str->append(label);
2138 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002139
reed@google.com51bbe752013-01-17 22:07:50 +00002140 const SkScalar* values = &pts[0].fX;
2141 count *= 2;
2142
2143 for (int i = 0; i < count; ++i) {
2144 append_scalar(str, values[i]);
2145 if (i < count - 1) {
2146 str->append(", ");
2147 }
2148 }
2149 str->append(");\n");
2150}
2151
reed@android.com8a1c16f2008-12-17 15:59:43 +00002152void SkPath::dump(bool forceClose, const char title[]) const {
2153 Iter iter(*this, forceClose);
2154 SkPoint pts[4];
2155 Verb verb;
2156
2157 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2158 title ? title : "");
2159
reed@google.com51bbe752013-01-17 22:07:50 +00002160 SkString builder;
2161
reed@google.com4a3b7142012-05-16 17:16:46 +00002162 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002163 switch (verb) {
2164 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002165 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002166 break;
2167 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002168 append_params(&builder, "path.lineTo", &pts[1], 1);
2169 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002170 break;
2171 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002172 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002173 break;
2174 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002175 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002176 break;
2177 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002178 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002179 break;
2180 default:
2181 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2182 verb = kDone_Verb; // stop the loop
2183 break;
2184 }
2185 }
reed@google.com51bbe752013-01-17 22:07:50 +00002186 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002187}
2188
reed@android.come522ca52009-11-23 20:10:41 +00002189void SkPath::dump() const {
2190 this->dump(false);
2191}
2192
2193#ifdef SK_DEBUG
2194void SkPath::validate() const {
2195 SkASSERT(this != NULL);
2196 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002197
djsollen@google.com077348c2012-10-22 20:23:32 +00002198#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002199 if (!fBoundsIsDirty) {
2200 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002201
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002202 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002203 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002204
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002205 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002206 // if we're empty, fBounds may be empty but translated, so we can't
2207 // necessarily compare to bounds directly
2208 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2209 // be [2, 2, 2, 2]
2210 SkASSERT(bounds.isEmpty());
2211 SkASSERT(fBounds.isEmpty());
2212 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002213 if (bounds.isEmpty()) {
2214 SkASSERT(fBounds.isEmpty());
2215 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002216 if (!fBounds.isEmpty()) {
2217 SkASSERT(fBounds.contains(bounds));
2218 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002219 }
reed@android.come522ca52009-11-23 20:10:41 +00002220 }
2221 }
reed@google.com10296cc2011-09-21 12:29:05 +00002222
2223 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002224 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2225 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2226 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002227 case kLine_Verb:
2228 mask |= kLine_SegmentMask;
2229 break;
2230 case kQuad_Verb:
2231 mask |= kQuad_SegmentMask;
2232 break;
2233 case kCubic_Verb:
2234 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002235 case kMove_Verb: // these verbs aren't included in the segment mask.
2236 case kClose_Verb:
2237 break;
2238 case kDone_Verb:
2239 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2240 break;
2241 default:
2242 SkDEBUGFAIL("Unknown Verb");
2243 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002244 }
2245 }
2246 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002247#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002248}
djsollen@google.com077348c2012-10-22 20:23:32 +00002249#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002250
reed@google.com04863fa2011-05-15 04:08:24 +00002251///////////////////////////////////////////////////////////////////////////////
2252
reed@google.com0b7b9822011-05-16 12:29:27 +00002253static int sign(SkScalar x) { return x < 0; }
2254#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002255
reed@google.com04863fa2011-05-15 04:08:24 +00002256static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002257 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002258}
2259
2260// only valid for a single contour
2261struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002262 Convexicator()
2263 : fPtCount(0)
2264 , fConvexity(SkPath::kConvex_Convexity)
2265 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002266 fSign = 0;
2267 // warnings
2268 fCurrPt.set(0, 0);
2269 fVec0.set(0, 0);
2270 fVec1.set(0, 0);
2271 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002272
2273 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002274 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002275 }
2276
2277 SkPath::Convexity getConvexity() const { return fConvexity; }
2278
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002279 /** The direction returned is only valid if the path is determined convex */
2280 SkPath::Direction getDirection() const { return fDirection; }
2281
reed@google.com04863fa2011-05-15 04:08:24 +00002282 void addPt(const SkPoint& pt) {
2283 if (SkPath::kConcave_Convexity == fConvexity) {
2284 return;
2285 }
2286
2287 if (0 == fPtCount) {
2288 fCurrPt = pt;
2289 ++fPtCount;
2290 } else {
2291 SkVector vec = pt - fCurrPt;
2292 if (vec.fX || vec.fY) {
2293 fCurrPt = pt;
2294 if (++fPtCount == 2) {
2295 fFirstVec = fVec1 = vec;
2296 } else {
2297 SkASSERT(fPtCount > 2);
2298 this->addVec(vec);
2299 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002300
reed@google.com85b6e392011-05-15 20:25:17 +00002301 int sx = sign(vec.fX);
2302 int sy = sign(vec.fY);
2303 fDx += (sx != fSx);
2304 fDy += (sy != fSy);
2305 fSx = sx;
2306 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002307
reed@google.com85b6e392011-05-15 20:25:17 +00002308 if (fDx > 3 || fDy > 3) {
2309 fConvexity = SkPath::kConcave_Convexity;
2310 }
reed@google.com04863fa2011-05-15 04:08:24 +00002311 }
2312 }
2313 }
2314
2315 void close() {
2316 if (fPtCount > 2) {
2317 this->addVec(fFirstVec);
2318 }
2319 }
2320
2321private:
2322 void addVec(const SkVector& vec) {
2323 SkASSERT(vec.fX || vec.fY);
2324 fVec0 = fVec1;
2325 fVec1 = vec;
2326 int sign = CrossProductSign(fVec0, fVec1);
2327 if (0 == fSign) {
2328 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002329 if (1 == sign) {
2330 fDirection = SkPath::kCW_Direction;
2331 } else if (-1 == sign) {
2332 fDirection = SkPath::kCCW_Direction;
2333 }
reed@google.com04863fa2011-05-15 04:08:24 +00002334 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002335 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002336 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002337 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002338 }
2339 }
2340 }
2341
2342 SkPoint fCurrPt;
2343 SkVector fVec0, fVec1, fFirstVec;
2344 int fPtCount; // non-degenerate points
2345 int fSign;
2346 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002347 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002348 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002349};
2350
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002351SkPath::Convexity SkPath::internalGetConvexity() const {
2352 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002353 SkPoint pts[4];
2354 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002355 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002356
2357 int contourCount = 0;
2358 int count;
2359 Convexicator state;
2360
2361 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2362 switch (verb) {
2363 case kMove_Verb:
2364 if (++contourCount > 1) {
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 pts[1] = pts[0];
2369 count = 1;
2370 break;
2371 case kLine_Verb: count = 1; break;
2372 case kQuad_Verb: count = 2; break;
2373 case kCubic_Verb: count = 3; break;
2374 case kClose_Verb:
2375 state.close();
2376 count = 0;
2377 break;
2378 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002379 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002380 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002381 return kConcave_Convexity;
2382 }
2383
2384 for (int i = 1; i <= count; i++) {
2385 state.addPt(pts[i]);
2386 }
2387 // early exit
2388 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002389 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002390 return kConcave_Convexity;
2391 }
2392 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002393 fConvexity = state.getConvexity();
2394 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2395 fDirection = state.getDirection();
2396 }
2397 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002398}
reed@google.com69a99432012-01-10 18:00:10 +00002399
2400///////////////////////////////////////////////////////////////////////////////
2401
2402class ContourIter {
2403public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002404 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002405
2406 bool done() const { return fDone; }
2407 // if !done() then these may be called
2408 int count() const { return fCurrPtCount; }
2409 const SkPoint* pts() const { return fCurrPt; }
2410 void next();
2411
2412private:
2413 int fCurrPtCount;
2414 const SkPoint* fCurrPt;
2415 const uint8_t* fCurrVerb;
2416 const uint8_t* fStopVerbs;
2417 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002418 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002419};
2420
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002421ContourIter::ContourIter(const SkPathRef& pathRef) {
2422 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002423 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002424 fCurrPt = pathRef.points();
2425 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002426 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002427 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002428 this->next();
2429}
2430
2431void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002432 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002433 fDone = true;
2434 }
2435 if (fDone) {
2436 return;
2437 }
2438
2439 // skip pts of prev contour
2440 fCurrPt += fCurrPtCount;
2441
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002442 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002443 int ptCount = 1; // moveTo
2444 const uint8_t* verbs = fCurrVerb;
2445
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002446 for (--verbs; verbs > fStopVerbs; --verbs) {
2447 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002448 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002449 goto CONTOUR_END;
2450 case SkPath::kLine_Verb:
2451 ptCount += 1;
2452 break;
2453 case SkPath::kQuad_Verb:
2454 ptCount += 2;
2455 break;
2456 case SkPath::kCubic_Verb:
2457 ptCount += 3;
2458 break;
2459 default: // kClose_Verb, just keep going
2460 break;
2461 }
2462 }
2463CONTOUR_END:
2464 fCurrPtCount = ptCount;
2465 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002466 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002467}
2468
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002469// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002470static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002471 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2472 // We may get 0 when the above subtracts underflow. We expect this to be
2473 // very rare and lazily promote to double.
2474 if (0 == cross) {
2475 double p0x = SkScalarToDouble(p0.fX);
2476 double p0y = SkScalarToDouble(p0.fY);
2477
2478 double p1x = SkScalarToDouble(p1.fX);
2479 double p1y = SkScalarToDouble(p1.fY);
2480
2481 double p2x = SkScalarToDouble(p2.fX);
2482 double p2y = SkScalarToDouble(p2.fY);
2483
2484 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2485 (p1y - p0y) * (p2x - p0x));
2486
2487 }
2488 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002489}
2490
reed@google.comc1ea60a2012-01-31 15:15:36 +00002491// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002492static int find_max_y(const SkPoint pts[], int count) {
2493 SkASSERT(count > 0);
2494 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002495 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002496 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002497 SkScalar y = pts[i].fY;
2498 if (y > max) {
2499 max = y;
2500 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002501 }
2502 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002503 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002504}
2505
reed@google.comcabaf1d2012-01-11 21:03:05 +00002506static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2507 int i = index;
2508 for (;;) {
2509 i = (i + inc) % n;
2510 if (i == index) { // we wrapped around, so abort
2511 break;
2512 }
2513 if (pts[index] != pts[i]) { // found a different point, success!
2514 break;
2515 }
2516 }
2517 return i;
2518}
2519
reed@google.comc1ea60a2012-01-31 15:15:36 +00002520/**
2521 * Starting at index, and moving forward (incrementing), find the xmin and
2522 * xmax of the contiguous points that have the same Y.
2523 */
2524static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2525 int* maxIndexPtr) {
2526 const SkScalar y = pts[index].fY;
2527 SkScalar min = pts[index].fX;
2528 SkScalar max = min;
2529 int minIndex = index;
2530 int maxIndex = index;
2531 for (int i = index + 1; i < n; ++i) {
2532 if (pts[i].fY != y) {
2533 break;
2534 }
2535 SkScalar x = pts[i].fX;
2536 if (x < min) {
2537 min = x;
2538 minIndex = i;
2539 } else if (x > max) {
2540 max = x;
2541 maxIndex = i;
2542 }
2543 }
2544 *maxIndexPtr = maxIndex;
2545 return minIndex;
2546}
2547
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002548static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002549 if (dir) {
2550 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2551 }
reed@google.comac8543f2012-01-30 20:51:25 +00002552}
2553
reed@google.comc1ea60a2012-01-31 15:15:36 +00002554#if 0
2555#include "SkString.h"
2556#include "../utils/SkParsePath.h"
2557static void dumpPath(const SkPath& path) {
2558 SkString str;
2559 SkParsePath::ToSVGString(path, &str);
2560 SkDebugf("%s\n", str.c_str());
2561}
2562#endif
2563
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002564namespace {
2565// for use with convex_dir_test
2566double mul(double a, double b) { return a * b; }
2567SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2568double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2569SkScalar toScalar(SkScalar a) { return a; }
2570
2571// determines the winding direction of a convex polygon with the precision
2572// of T. CAST_SCALAR casts an SkScalar to T.
2573template <typename T, T (CAST_SCALAR)(SkScalar)>
2574bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2575 // we find the first three points that form a non-degenerate
2576 // triangle. If there are no such points then the path is
2577 // degenerate. The first is always point 0. Now we find the second
2578 // point.
2579 int i = 0;
2580 enum { kX = 0, kY = 1 };
2581 T v0[2];
2582 while (1) {
2583 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2584 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2585 if (v0[kX] || v0[kY]) {
2586 break;
2587 }
2588 if (++i == n - 1) {
2589 return false;
2590 }
2591 }
2592 // now find a third point that is not colinear with the first two
2593 // points and check the orientation of the triangle (which will be
2594 // the same as the orientation of the path).
2595 for (++i; i < n; ++i) {
2596 T v1[2];
2597 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2598 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2599 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2600 if (0 != cross) {
2601 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2602 return true;
2603 }
2604 }
2605 return false;
2606}
2607}
2608
reed@google.comac8543f2012-01-30 20:51:25 +00002609/*
2610 * We loop through all contours, and keep the computed cross-product of the
2611 * contour that contained the global y-max. If we just look at the first
2612 * contour, we may find one that is wound the opposite way (correctly) since
2613 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2614 * that is outer most (or at least has the global y-max) before we can consider
2615 * its cross product.
2616 */
reed@google.com69a99432012-01-10 18:00:10 +00002617bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002618// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002619 // don't want to pay the cost for computing this if it
2620 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002621
2622 if (kUnknown_Direction != fDirection) {
2623 *dir = static_cast<Direction>(fDirection);
2624 return true;
2625 }
reed@google.com69a99432012-01-10 18:00:10 +00002626 const Convexity conv = this->getConvexityOrUnknown();
2627
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002628 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002629
reed@google.comac8543f2012-01-30 20:51:25 +00002630 // initialize with our logical y-min
2631 SkScalar ymax = this->getBounds().fTop;
2632 SkScalar ymaxCross = 0;
2633
reed@google.com69a99432012-01-10 18:00:10 +00002634 for (; !iter.done(); iter.next()) {
2635 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002636 if (n < 3) {
2637 continue;
2638 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002639
reed@google.comcabaf1d2012-01-11 21:03:05 +00002640 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002641 SkScalar cross = 0;
2642 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002643 // We try first at scalar precision, and then again at double
2644 // precision. This is because the vectors computed between distant
2645 // points may lose too much precision.
2646 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002647 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002648 return true;
2649 }
2650 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002651 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002652 return true;
2653 } else {
2654 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002655 }
2656 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002657 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002658 if (pts[index].fY < ymax) {
2659 continue;
2660 }
2661
reed@google.comc1ea60a2012-01-31 15:15:36 +00002662 // If there is more than 1 distinct point at the y-max, we take the
2663 // x-min and x-max of them and just subtract to compute the dir.
2664 if (pts[(index + 1) % n].fY == pts[index].fY) {
2665 int maxIndex;
2666 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002667 if (minIndex == maxIndex) {
2668 goto TRY_CROSSPROD;
2669 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002670 SkASSERT(pts[minIndex].fY == pts[index].fY);
2671 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2672 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2673 // we just subtract the indices, and let that auto-convert to
2674 // SkScalar, since we just want - or + to signal the direction.
2675 cross = minIndex - maxIndex;
2676 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002677 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002678 // Find a next and prev index to use for the cross-product test,
2679 // but we try to find pts that form non-zero vectors from pts[index]
2680 //
2681 // Its possible that we can't find two non-degenerate vectors, so
2682 // we have to guard our search (e.g. all the pts could be in the
2683 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002684
reed@google.comc1ea60a2012-01-31 15:15:36 +00002685 // we pass n - 1 instead of -1 so we don't foul up % operator by
2686 // passing it a negative LH argument.
2687 int prev = find_diff_pt(pts, index, n, n - 1);
2688 if (prev == index) {
2689 // completely degenerate, skip to next contour
2690 continue;
2691 }
2692 int next = find_diff_pt(pts, index, n, 1);
2693 SkASSERT(next != index);
2694 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002695 // 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 +00002696 // x-direction. We really should continue to walk away from the degeneracy until
2697 // there is a divergence.
2698 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002699 // construct the subtract so we get the correct Direction below
2700 cross = pts[index].fX - pts[next].fX;
2701 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002702 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002703
reed@google.comac8543f2012-01-30 20:51:25 +00002704 if (cross) {
2705 // record our best guess so far
2706 ymax = pts[index].fY;
2707 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002708 }
reed@google.com69a99432012-01-10 18:00:10 +00002709 }
2710 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002711 if (ymaxCross) {
2712 crossToDir(ymaxCross, dir);
2713 fDirection = *dir;
2714 return true;
2715 } else {
2716 return false;
2717 }
reed@google.comac8543f2012-01-30 20:51:25 +00002718}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002719
2720///////////////////////////////////////////////////////////////////////////////
2721
2722static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2723 SkScalar D, SkScalar t) {
2724 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2725}
2726
2727static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2728 SkScalar t) {
2729 SkScalar A = c3 + 3*(c1 - c2) - c0;
2730 SkScalar B = 3*(c2 - c1 - c1 + c0);
2731 SkScalar C = 3*(c1 - c0);
2732 SkScalar D = c0;
2733 return eval_cubic_coeff(A, B, C, D, t);
2734}
2735
2736/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2737 t value such that cubic(t) = target
2738 */
2739static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2740 SkScalar target, SkScalar* t) {
2741 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2742 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002743
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002744 SkScalar D = c0 - target;
2745 SkScalar A = c3 + 3*(c1 - c2) - c0;
2746 SkScalar B = 3*(c2 - c1 - c1 + c0);
2747 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002748
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002749 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2750 SkScalar minT = 0;
2751 SkScalar maxT = SK_Scalar1;
2752 SkScalar mid;
2753 int i;
2754 for (i = 0; i < 16; i++) {
2755 mid = SkScalarAve(minT, maxT);
2756 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2757 if (delta < 0) {
2758 minT = mid;
2759 delta = -delta;
2760 } else {
2761 maxT = mid;
2762 }
2763 if (delta < TOLERANCE) {
2764 break;
2765 }
2766 }
2767 *t = mid;
2768 return true;
2769}
2770
2771template <size_t N> static void find_minmax(const SkPoint pts[],
2772 SkScalar* minPtr, SkScalar* maxPtr) {
2773 SkScalar min, max;
2774 min = max = pts[0].fX;
2775 for (size_t i = 1; i < N; ++i) {
2776 min = SkMinScalar(min, pts[i].fX);
2777 max = SkMaxScalar(max, pts[i].fX);
2778 }
2779 *minPtr = min;
2780 *maxPtr = max;
2781}
2782
2783static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2784 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002785
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002786 int dir = 1;
2787 if (pts[0].fY > pts[3].fY) {
2788 storage[0] = pts[3];
2789 storage[1] = pts[2];
2790 storage[2] = pts[1];
2791 storage[3] = pts[0];
2792 pts = storage;
2793 dir = -1;
2794 }
2795 if (y < pts[0].fY || y >= pts[3].fY) {
2796 return 0;
2797 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002798
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002799 // quickreject or quickaccept
2800 SkScalar min, max;
2801 find_minmax<4>(pts, &min, &max);
2802 if (x < min) {
2803 return 0;
2804 }
2805 if (x > max) {
2806 return dir;
2807 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002808
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002809 // compute the actual x(t) value
2810 SkScalar t, xt;
2811 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2812 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2813 } else {
2814 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2815 xt = y < mid ? pts[0].fX : pts[3].fX;
2816 }
2817 return xt < x ? dir : 0;
2818}
2819
2820static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2821 SkPoint dst[10];
2822 int n = SkChopCubicAtYExtrema(pts, dst);
2823 int w = 0;
2824 for (int i = 0; i <= n; ++i) {
2825 w += winding_mono_cubic(&dst[i * 3], x, y);
2826 }
2827 return w;
2828}
2829
2830static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2831 SkScalar y0 = pts[0].fY;
2832 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002833
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002834 int dir = 1;
2835 if (y0 > y2) {
2836 SkTSwap(y0, y2);
2837 dir = -1;
2838 }
2839 if (y < y0 || y >= y2) {
2840 return 0;
2841 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002842
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002843 // bounds check on X (not required. is it faster?)
2844#if 0
2845 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2846 return 0;
2847 }
2848#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002849
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002850 SkScalar roots[2];
2851 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2852 2 * (pts[1].fY - pts[0].fY),
2853 pts[0].fY - y,
2854 roots);
2855 SkASSERT(n <= 1);
2856 SkScalar xt;
2857 if (0 == n) {
2858 SkScalar mid = SkScalarAve(y0, y2);
2859 // Need [0] and [2] if dir == 1
2860 // and [2] and [0] if dir == -1
2861 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2862 } else {
2863 SkScalar t = roots[0];
2864 SkScalar C = pts[0].fX;
2865 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2866 SkScalar B = 2 * (pts[1].fX - C);
2867 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2868 }
2869 return xt < x ? dir : 0;
2870}
2871
2872static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2873 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2874 if (y0 == y1) {
2875 return true;
2876 }
2877 if (y0 < y1) {
2878 return y1 <= y2;
2879 } else {
2880 return y1 >= y2;
2881 }
2882}
2883
2884static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2885 SkPoint dst[5];
2886 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002887
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002888 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2889 n = SkChopQuadAtYExtrema(pts, dst);
2890 pts = dst;
2891 }
2892 int w = winding_mono_quad(pts, x, y);
2893 if (n > 0) {
2894 w += winding_mono_quad(&pts[2], x, y);
2895 }
2896 return w;
2897}
2898
2899static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2900 SkScalar x0 = pts[0].fX;
2901 SkScalar y0 = pts[0].fY;
2902 SkScalar x1 = pts[1].fX;
2903 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002904
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002905 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002906
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002907 int dir = 1;
2908 if (y0 > y1) {
2909 SkTSwap(y0, y1);
2910 dir = -1;
2911 }
2912 if (y < y0 || y >= y1) {
2913 return 0;
2914 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002915
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002916 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2917 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002918
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002919 if (SkScalarSignAsInt(cross) == dir) {
2920 dir = 0;
2921 }
2922 return dir;
2923}
2924
2925bool SkPath::contains(SkScalar x, SkScalar y) const {
2926 bool isInverse = this->isInverseFillType();
2927 if (this->isEmpty()) {
2928 return isInverse;
2929 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002930
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002931 const SkRect& bounds = this->getBounds();
2932 if (!bounds.contains(x, y)) {
2933 return isInverse;
2934 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002935
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002936 SkPath::Iter iter(*this, true);
2937 bool done = false;
2938 int w = 0;
2939 do {
2940 SkPoint pts[4];
2941 switch (iter.next(pts, false)) {
2942 case SkPath::kMove_Verb:
2943 case SkPath::kClose_Verb:
2944 break;
2945 case SkPath::kLine_Verb:
2946 w += winding_line(pts, x, y);
2947 break;
2948 case SkPath::kQuad_Verb:
2949 w += winding_quad(pts, x, y);
2950 break;
2951 case SkPath::kCubic_Verb:
2952 w += winding_cubic(pts, x, y);
2953 break;
2954 case SkPath::kDone_Verb:
2955 done = true;
2956 break;
2957 }
2958 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002959
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002960 switch (this->getFillType()) {
2961 case SkPath::kEvenOdd_FillType:
2962 case SkPath::kInverseEvenOdd_FillType:
2963 w &= 1;
2964 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002965 default:
2966 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002967 }
2968 return SkToBool(w);
2969}