blob: 95cd9fe93895e1cfc84505025a73b20a0792a98f [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
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000603bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000604 SkDEBUGCODE(this->validate();)
605 int currVerb = 0;
606 const SkPoint* pts = fPathRef->points();
607 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000608 Direction testDirs[2];
609 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000610 return false;
611 }
612 const SkPoint* last = pts;
613 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000614 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000615 testRects[0].set(first, SkToS32(last - first));
616 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000617 if (testRects[0].contains(testRects[1])) {
618 if (rects) {
619 rects[0] = testRects[0];
620 rects[1] = testRects[1];
621 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000622 if (dirs) {
623 dirs[0] = testDirs[0];
624 dirs[1] = testDirs[1];
625 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000626 return true;
627 }
628 if (testRects[1].contains(testRects[0])) {
629 if (rects) {
630 rects[0] = testRects[1];
631 rects[1] = testRects[0];
632 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000633 if (dirs) {
634 dirs[0] = testDirs[1];
635 dirs[1] = testDirs[0];
636 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000637 return true;
638 }
639 }
640 return false;
641}
642
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000643int SkPath::countPoints() const {
644 return fPathRef->countPoints();
645}
646
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000647int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648 SkDEBUGCODE(this->validate();)
649
650 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000651 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000652 int count = SkMin32(max, fPathRef->countPoints());
653 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
654 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000655}
656
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000657SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000658 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
659 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000660 }
661 return SkPoint::Make(0, 0);
662}
663
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000664int SkPath::countVerbs() const {
665 return fPathRef->countVerbs();
666}
667
668static inline void copy_verbs_reverse(uint8_t* inorderDst,
669 const uint8_t* reversedSrc,
670 int count) {
671 for (int i = 0; i < count; ++i) {
672 inorderDst[i] = reversedSrc[~i];
673 }
674}
675
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000676int SkPath::getVerbs(uint8_t dst[], int max) const {
677 SkDEBUGCODE(this->validate();)
678
679 SkASSERT(max >= 0);
680 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000681 int count = SkMin32(max, fPathRef->countVerbs());
682 copy_verbs_reverse(dst, fPathRef->verbs(), count);
683 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000684}
685
reed@google.com294dd7b2011-10-11 11:58:32 +0000686bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000687 SkDEBUGCODE(this->validate();)
688
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000689 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000690 if (count > 0) {
691 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000692 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000694 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000695 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000696 if (lastPt) {
697 lastPt->set(0, 0);
698 }
699 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
702void SkPath::setLastPt(SkScalar x, SkScalar y) {
703 SkDEBUGCODE(this->validate();)
704
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000705 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000706 if (count == 0) {
707 this->moveTo(x, y);
708 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000709 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000710 SkPathRef::Editor ed(&fPathRef);
711 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000712 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000713 }
714}
715
reed@android.comd252db02009-04-01 18:31:44 +0000716void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000717 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000718 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000719
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000720 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000721 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000722}
723
reed@google.com04863fa2011-05-15 04:08:24 +0000724void SkPath::setConvexity(Convexity c) {
725 if (fConvexity != c) {
726 fConvexity = c;
727 GEN_ID_INC;
728 }
729}
730
reed@android.com8a1c16f2008-12-17 15:59:43 +0000731//////////////////////////////////////////////////////////////////////////////
732// Construction methods
733
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000734#define DIRTY_AFTER_EDIT \
735 do { \
736 fBoundsIsDirty = true; \
737 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000738 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000739 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000740 } while (0)
741
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000742#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
743 do { \
744 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000745 } while (0)
746
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747void SkPath::incReserve(U16CPU inc) {
748 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000749 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000750 SkDEBUGCODE(this->validate();)
751}
752
753void SkPath::moveTo(SkScalar x, SkScalar y) {
754 SkDEBUGCODE(this->validate();)
755
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000756 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000757
reed@google.comd335d1d2012-01-12 18:17:11 +0000758 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000759 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000760
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000761 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000762
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000763 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000764 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000765}
766
767void SkPath::rMoveTo(SkScalar x, SkScalar y) {
768 SkPoint pt;
769 this->getLastPt(&pt);
770 this->moveTo(pt.fX + x, pt.fY + y);
771}
772
reed@google.comd335d1d2012-01-12 18:17:11 +0000773void SkPath::injectMoveToIfNeeded() {
774 if (fLastMoveToIndex < 0) {
775 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000776 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000777 x = y = 0;
778 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000779 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000780 x = pt.fX;
781 y = pt.fY;
782 }
783 this->moveTo(x, y);
784 }
785}
786
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787void SkPath::lineTo(SkScalar x, SkScalar y) {
788 SkDEBUGCODE(this->validate();)
789
reed@google.comd335d1d2012-01-12 18:17:11 +0000790 this->injectMoveToIfNeeded();
791
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000792 SkPathRef::Editor ed(&fPathRef);
793 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000794 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000795
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000796 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000797 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798}
799
800void SkPath::rLineTo(SkScalar x, SkScalar y) {
801 SkPoint pt;
802 this->getLastPt(&pt);
803 this->lineTo(pt.fX + x, pt.fY + y);
804}
805
806void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
807 SkDEBUGCODE(this->validate();)
808
reed@google.comd335d1d2012-01-12 18:17:11 +0000809 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000810
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000811 SkPathRef::Editor ed(&fPathRef);
812 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000813 pts[0].set(x1, y1);
814 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000815 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000817 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000818 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000819}
820
821void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
822 SkPoint pt;
823 this->getLastPt(&pt);
824 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
825}
826
827void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
828 SkScalar x3, SkScalar y3) {
829 SkDEBUGCODE(this->validate();)
830
reed@google.comd335d1d2012-01-12 18:17:11 +0000831 this->injectMoveToIfNeeded();
832
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000833 SkPathRef::Editor ed(&fPathRef);
834 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000835 pts[0].set(x1, y1);
836 pts[1].set(x2, y2);
837 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000838 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000839
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000840 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000841 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000842}
843
844void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
845 SkScalar x3, SkScalar y3) {
846 SkPoint pt;
847 this->getLastPt(&pt);
848 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
849 pt.fX + x3, pt.fY + y3);
850}
851
852void SkPath::close() {
853 SkDEBUGCODE(this->validate();)
854
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000855 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000856 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000857 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000858 case kLine_Verb:
859 case kQuad_Verb:
860 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000861 case kMove_Verb: {
862 SkPathRef::Editor ed(&fPathRef);
863 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000864 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000865 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000866 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000867 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000868 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000869 break;
870 }
871 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000872
873 // signal that we need a moveTo to follow us (unless we're done)
874#if 0
875 if (fLastMoveToIndex >= 0) {
876 fLastMoveToIndex = ~fLastMoveToIndex;
877 }
878#else
879 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
880#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000881}
882
883///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000884
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000885static void assert_known_direction(int dir) {
886 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
887}
888
reed@android.com8a1c16f2008-12-17 15:59:43 +0000889void SkPath::addRect(const SkRect& rect, Direction dir) {
890 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
891}
892
893void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
894 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000895 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000896 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
897 SkAutoDisableDirectionCheck addc(this);
898
reed@android.com8a1c16f2008-12-17 15:59:43 +0000899 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
900
901 this->incReserve(5);
902
903 this->moveTo(left, top);
904 if (dir == kCCW_Direction) {
905 this->lineTo(left, bottom);
906 this->lineTo(right, bottom);
907 this->lineTo(right, top);
908 } else {
909 this->lineTo(right, top);
910 this->lineTo(right, bottom);
911 this->lineTo(left, bottom);
912 }
913 this->close();
914}
915
reed@google.com744faba2012-05-29 19:54:52 +0000916void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
917 SkDEBUGCODE(this->validate();)
918 if (count <= 0) {
919 return;
920 }
921
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000922 SkPathRef::Editor ed(&fPathRef);
923 fLastMoveToIndex = ed.pathRef()->countPoints();
924 uint8_t* vb;
925 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000926 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000927 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000928
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000929 memcpy(p, pts, count * sizeof(SkPoint));
930 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000931 if (count > 1) {
932 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
933 // be 0, the compiler will remove the test/branch entirely.
934 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000935 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000936 } else {
937 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000938 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000939 }
940 }
941 fSegmentMask |= kLine_SegmentMask;
942 }
943 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000944 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000945 }
946
947 GEN_ID_INC;
948 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000949 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000950}
951
reed@android.com8a1c16f2008-12-17 15:59:43 +0000952static void add_corner_arc(SkPath* path, const SkRect& rect,
953 SkScalar rx, SkScalar ry, int startAngle,
954 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000955 // These two asserts are not sufficient, since really we want to know
956 // that the pair of radii (e.g. left and right, or top and bottom) sum
957 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000958 // these conservative asserts.
959 SkASSERT(0 <= rx && rx <= rect.width());
960 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000961
reed@android.com8a1c16f2008-12-17 15:59:43 +0000962 SkRect r;
963 r.set(-rx, -ry, rx, ry);
964
965 switch (startAngle) {
966 case 0:
967 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
968 break;
969 case 90:
970 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
971 break;
972 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
973 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000974 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975 }
reed@google.comabf15c12011-01-18 20:35:51 +0000976
reed@android.com8a1c16f2008-12-17 15:59:43 +0000977 SkScalar start = SkIntToScalar(startAngle);
978 SkScalar sweep = SkIntToScalar(90);
979 if (SkPath::kCCW_Direction == dir) {
980 start += sweep;
981 sweep = -sweep;
982 }
reed@google.comabf15c12011-01-18 20:35:51 +0000983
reed@android.com8a1c16f2008-12-17 15:59:43 +0000984 path->arcTo(r, start, sweep, forceMoveTo);
985}
986
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000987void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000988 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000989 SkRRect rrect;
990 rrect.setRectRadii(rect, (const SkVector*) radii);
991 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000992}
993
reed@google.com4ed0fb72012-12-12 20:48:18 +0000994void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000995 assert_known_direction(dir);
996
997 if (rrect.isEmpty()) {
998 return;
999 }
1000
reed@google.com4ed0fb72012-12-12 20:48:18 +00001001 const SkRect& bounds = rrect.getBounds();
1002
1003 if (rrect.isRect()) {
1004 this->addRect(bounds, dir);
1005 } else if (rrect.isOval()) {
1006 this->addOval(bounds, dir);
1007 } else if (rrect.isSimple()) {
1008 const SkVector& rad = rrect.getSimpleRadii();
1009 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1010 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001011 SkAutoPathBoundsUpdate apbu(this, bounds);
1012
1013 if (kCW_Direction == dir) {
1014 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1015 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1016 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1017 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1018 } else {
1019 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1020 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1021 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1022 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1023 }
1024 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001025 }
1026}
1027
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001028bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001029 int count = fPathRef->countVerbs();
1030 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1031 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001032 if (*verbs == kLine_Verb ||
1033 *verbs == kQuad_Verb ||
1034 *verbs == kCubic_Verb) {
1035 return false;
1036 }
1037 ++verbs;
1038 }
1039 return true;
1040}
1041
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001042#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
1043
1044void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1045 Direction dir) {
1046 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001047
humper@google.com75e3ca12013-04-08 21:44:11 +00001048 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001049 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001050 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001051 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001052 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1053 return;
1054 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001055
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001056 SkScalar w = rect.width();
1057 SkScalar halfW = SkScalarHalf(w);
1058 SkScalar h = rect.height();
1059 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001060
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001061 if (halfW <= 0 || halfH <= 0) {
1062 return;
1063 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001064
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001065 bool skip_hori = rx >= halfW;
1066 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001067
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001068 if (skip_hori && skip_vert) {
1069 this->addOval(rect, dir);
1070 return;
1071 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001072
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001073 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001074
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001075 SkAutoPathBoundsUpdate apbu(this, rect);
1076 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001077
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001078 if (skip_hori) {
1079 rx = halfW;
1080 } else if (skip_vert) {
1081 ry = halfH;
1082 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001083
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001084 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1085 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001086
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001087 this->incReserve(17);
1088 this->moveTo(rect.fRight - rx, rect.fTop);
1089 if (dir == kCCW_Direction) {
1090 if (!skip_hori) {
1091 this->lineTo(rect.fLeft + rx, rect.fTop); // top
1092 }
1093 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1094 rect.fLeft, rect.fTop + ry - sy,
1095 rect.fLeft, rect.fTop + ry); // top-left
1096 if (!skip_vert) {
1097 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1098 }
1099 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1100 rect.fLeft + rx - sx, rect.fBottom,
1101 rect.fLeft + rx, rect.fBottom); // bot-left
1102 if (!skip_hori) {
1103 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
1104 }
1105 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1106 rect.fRight, rect.fBottom - ry + sy,
1107 rect.fRight, rect.fBottom - ry); // bot-right
1108 if (!skip_vert) {
1109 this->lineTo(rect.fRight, rect.fTop + ry);
1110 }
1111 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1112 rect.fRight - rx + sx, rect.fTop,
1113 rect.fRight - rx, rect.fTop); // top-right
1114 } else {
1115 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1116 rect.fRight, rect.fTop + ry - sy,
1117 rect.fRight, rect.fTop + ry); // top-right
1118 if (!skip_vert) {
1119 this->lineTo(rect.fRight, rect.fBottom - ry);
1120 }
1121 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1122 rect.fRight - rx + sx, rect.fBottom,
1123 rect.fRight - rx, rect.fBottom); // bot-right
1124 if (!skip_hori) {
1125 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1126 }
1127 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1128 rect.fLeft, rect.fBottom - ry + sy,
1129 rect.fLeft, rect.fBottom - ry); // bot-left
1130 if (!skip_vert) {
1131 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1132 }
1133 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1134 rect.fLeft + rx - sx, rect.fTop,
1135 rect.fLeft + rx, rect.fTop); // top-left
1136 if (!skip_hori) {
1137 this->lineTo(rect.fRight - rx, rect.fTop); // top
1138 }
1139 }
1140 this->close();
1141}
1142
reed@android.com8a1c16f2008-12-17 15:59:43 +00001143void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001144 assert_known_direction(dir);
1145
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001146 /* If addOval() is called after previous moveTo(),
1147 this path is still marked as an oval. This is used to
1148 fit into WebKit's calling sequences.
1149 We can't simply check isEmpty() in this case, as additional
1150 moveTo() would mark the path non empty.
1151 */
1152 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001153 if (fIsOval) {
1154 fDirection = dir;
1155 } else {
1156 fDirection = kUnknown_Direction;
1157 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001158
1159 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001160 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001161
reed@android.com8a1c16f2008-12-17 15:59:43 +00001162 SkAutoPathBoundsUpdate apbu(this, oval);
1163
1164 SkScalar cx = oval.centerX();
1165 SkScalar cy = oval.centerY();
1166 SkScalar rx = SkScalarHalf(oval.width());
1167 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001168
reed@android.com8a1c16f2008-12-17 15:59:43 +00001169 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1170 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1171 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1172 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1173
1174 /*
1175 To handle imprecision in computing the center and radii, we revert to
1176 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1177 to ensure that we don't exceed the oval's bounds *ever*, since we want
1178 to use oval for our fast-bounds, rather than have to recompute it.
1179 */
1180 const SkScalar L = oval.fLeft; // cx - rx
1181 const SkScalar T = oval.fTop; // cy - ry
1182 const SkScalar R = oval.fRight; // cx + rx
1183 const SkScalar B = oval.fBottom; // cy + ry
1184
1185 this->incReserve(17); // 8 quads + close
1186 this->moveTo(R, cy);
1187 if (dir == kCCW_Direction) {
1188 this->quadTo( R, cy - sy, cx + mx, cy - my);
1189 this->quadTo(cx + sx, T, cx , T);
1190 this->quadTo(cx - sx, T, 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, B, cx , B);
1194 this->quadTo(cx + sx, B, cx + mx, cy + my);
1195 this->quadTo( R, cy + sy, R, cy );
1196 } else {
1197 this->quadTo( R, cy + sy, cx + mx, cy + my);
1198 this->quadTo(cx + sx, B, cx , B);
1199 this->quadTo(cx - sx, B, cx - mx, cy + my);
1200 this->quadTo( L, cy + sy, L, cy );
1201 this->quadTo( L, cy - sy, cx - mx, cy - my);
1202 this->quadTo(cx - sx, T, cx , T);
1203 this->quadTo(cx + sx, T, cx + mx, cy - my);
1204 this->quadTo( R, cy - sy, R, cy );
1205 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001206 this->close();
1207}
1208
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001209bool SkPath::isOval(SkRect* rect) const {
1210 if (fIsOval && rect) {
1211 *rect = getBounds();
1212 }
1213
1214 return fIsOval;
1215}
1216
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1218 if (r > 0) {
1219 SkRect rect;
1220 rect.set(x - r, y - r, x + r, y + r);
1221 this->addOval(rect, dir);
1222 }
1223}
1224
1225#include "SkGeometry.h"
1226
1227static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1228 SkScalar sweepAngle,
1229 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001230
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001231 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001232 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1233 // Chrome uses this path to move into and out of ovals. If not
1234 // treated as a special case the moves can distort the oval's
1235 // bounding box (and break the circle special case).
1236 pts[0].set(oval.fRight, oval.centerY());
1237 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001238 } else if (0 == oval.width() && 0 == oval.height()) {
1239 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001240 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001241 // a rect.
1242 // TODO: optimizing the case where only one of width or height is zero
1243 // should also be considered. This case, however, doesn't seem to be
1244 // as common as the single point case.
1245 pts[0].set(oval.fRight, oval.fTop);
1246 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001247 }
1248
reed@android.com8a1c16f2008-12-17 15:59:43 +00001249 SkVector start, stop;
1250
1251 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1252 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1253 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001254
1255 /* If the sweep angle is nearly (but less than) 360, then due to precision
1256 loss in radians-conversion and/or sin/cos, we may end up with coincident
1257 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1258 of drawing a nearly complete circle (good).
1259 e.g. canvas.drawArc(0, 359.99, ...)
1260 -vs- canvas.drawArc(0, 359.9, ...)
1261 We try to detect this edge case, and tweak the stop vector
1262 */
1263 if (start == stop) {
1264 SkScalar sw = SkScalarAbs(sweepAngle);
1265 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1266 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1267 // make a guess at a tiny angle (in radians) to tweak by
1268 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1269 // not sure how much will be enough, so we use a loop
1270 do {
1271 stopRad -= deltaRad;
1272 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1273 } while (start == stop);
1274 }
1275 }
1276
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001278
reed@android.com8a1c16f2008-12-17 15:59:43 +00001279 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1280 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001281
reed@android.com8a1c16f2008-12-17 15:59:43 +00001282 return SkBuildQuadArc(start, stop,
1283 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1284 &matrix, pts);
1285}
1286
1287void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1288 bool forceMoveTo) {
1289 if (oval.width() < 0 || oval.height() < 0) {
1290 return;
1291 }
1292
1293 SkPoint pts[kSkBuildQuadArcStorage];
1294 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1295 SkASSERT((count & 1) == 1);
1296
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001297 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001298 forceMoveTo = true;
1299 }
1300 this->incReserve(count);
1301 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1302 for (int i = 1; i < count; i += 2) {
1303 this->quadTo(pts[i], pts[i+1]);
1304 }
1305}
1306
1307void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1308 SkScalar sweepAngle) {
1309 if (oval.isEmpty() || 0 == sweepAngle) {
1310 return;
1311 }
1312
1313 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1314
1315 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1316 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1317 return;
1318 }
1319
1320 SkPoint pts[kSkBuildQuadArcStorage];
1321 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1322
1323 this->incReserve(count);
1324 this->moveTo(pts[0]);
1325 for (int i = 1; i < count; i += 2) {
1326 this->quadTo(pts[i], pts[i+1]);
1327 }
1328}
1329
1330/*
1331 Need to handle the case when the angle is sharp, and our computed end-points
1332 for the arc go behind pt1 and/or p2...
1333*/
1334void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1335 SkScalar radius) {
1336 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001337
reed@android.com8a1c16f2008-12-17 15:59:43 +00001338 // need to know our prev pt so we can construct tangent vectors
1339 {
1340 SkPoint start;
1341 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001342 // Handle degenerate cases by adding a line to the first point and
1343 // bailing out.
1344 if ((x1 == start.fX && y1 == start.fY) ||
1345 (x1 == x2 && y1 == y2) ||
1346 radius == 0) {
1347 this->lineTo(x1, y1);
1348 return;
1349 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001350 before.setNormalize(x1 - start.fX, y1 - start.fY);
1351 after.setNormalize(x2 - x1, y2 - y1);
1352 }
reed@google.comabf15c12011-01-18 20:35:51 +00001353
reed@android.com8a1c16f2008-12-17 15:59:43 +00001354 SkScalar cosh = SkPoint::DotProduct(before, after);
1355 SkScalar sinh = SkPoint::CrossProduct(before, after);
1356
1357 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001358 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001359 return;
1360 }
reed@google.comabf15c12011-01-18 20:35:51 +00001361
reed@android.com8a1c16f2008-12-17 15:59:43 +00001362 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1363 if (dist < 0) {
1364 dist = -dist;
1365 }
1366
1367 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1368 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1369 SkRotationDirection arcDir;
1370
1371 // now turn before/after into normals
1372 if (sinh > 0) {
1373 before.rotateCCW();
1374 after.rotateCCW();
1375 arcDir = kCW_SkRotationDirection;
1376 } else {
1377 before.rotateCW();
1378 after.rotateCW();
1379 arcDir = kCCW_SkRotationDirection;
1380 }
1381
1382 SkMatrix matrix;
1383 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001384
reed@android.com8a1c16f2008-12-17 15:59:43 +00001385 matrix.setScale(radius, radius);
1386 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1387 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001388
reed@android.com8a1c16f2008-12-17 15:59:43 +00001389 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001390
reed@android.com8a1c16f2008-12-17 15:59:43 +00001391 this->incReserve(count);
1392 // [xx,yy] == pts[0]
1393 this->lineTo(xx, yy);
1394 for (int i = 1; i < count; i += 2) {
1395 this->quadTo(pts[i], pts[i+1]);
1396 }
1397}
1398
1399///////////////////////////////////////////////////////////////////////////////
1400
1401void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1402 SkMatrix matrix;
1403
1404 matrix.setTranslate(dx, dy);
1405 this->addPath(path, matrix);
1406}
1407
1408void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001409 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001410
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001411 fIsOval = false;
1412
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001413 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001414 SkPoint pts[4];
1415 Verb verb;
1416
1417 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1418
1419 while ((verb = iter.next(pts)) != kDone_Verb) {
1420 switch (verb) {
1421 case kMove_Verb:
1422 proc(matrix, &pts[0], &pts[0], 1);
1423 this->moveTo(pts[0]);
1424 break;
1425 case kLine_Verb:
1426 proc(matrix, &pts[1], &pts[1], 1);
1427 this->lineTo(pts[1]);
1428 break;
1429 case kQuad_Verb:
1430 proc(matrix, &pts[1], &pts[1], 2);
1431 this->quadTo(pts[1], pts[2]);
1432 break;
1433 case kCubic_Verb:
1434 proc(matrix, &pts[1], &pts[1], 3);
1435 this->cubicTo(pts[1], pts[2], pts[3]);
1436 break;
1437 case kClose_Verb:
1438 this->close();
1439 break;
1440 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001441 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001442 }
1443 }
1444}
1445
1446///////////////////////////////////////////////////////////////////////////////
1447
1448static const uint8_t gPtsInVerb[] = {
1449 1, // kMove
1450 1, // kLine
1451 2, // kQuad
1452 3, // kCubic
1453 0, // kClose
1454 0 // kDone
1455};
1456
1457// ignore the initial moveto, and stop when the 1st contour ends
1458void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001459 int i, vcount = path.fPathRef->countVerbs();
1460 // exit early if the path is empty, or just has a moveTo.
1461 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001462 return;
1463 }
1464
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001465 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001467 fIsOval = false;
1468
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001469 const uint8_t* verbs = path.fPathRef->verbs();
1470 // skip the initial moveTo
1471 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001472
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001473 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001474 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001475 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001476 case kLine_Verb:
1477 this->lineTo(pts[0].fX, pts[0].fY);
1478 break;
1479 case kQuad_Verb:
1480 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1481 break;
1482 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001483 this->cubicTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001484 break;
1485 case kClose_Verb:
1486 return;
1487 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001488 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001489 }
1490}
1491
1492// ignore the last point of the 1st contour
1493void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001494 int i, vcount = path.fPathRef->countVerbs();
1495 // exit early if the path is empty, or just has a moveTo.
1496 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001497 return;
1498 }
1499
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001500 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001501
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001502 fIsOval = false;
1503
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001504 const uint8_t* verbs = path.fPathRef->verbs();
1505 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001506
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001507 SkASSERT(verbs[~0] == kMove_Verb);
1508 for (i = 1; i < vcount; ++i) {
1509 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001510 if (n == 0) {
1511 break;
1512 }
1513 pts += n;
1514 }
1515
1516 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001517 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001518 case kLine_Verb:
1519 this->lineTo(pts[-1].fX, pts[-1].fY);
1520 break;
1521 case kQuad_Verb:
1522 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1523 break;
1524 case kCubic_Verb:
1525 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1526 pts[-3].fX, pts[-3].fY);
1527 break;
1528 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001529 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530 break;
1531 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001532 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001533 }
1534}
1535
reed@google.com63d73742012-01-10 15:33:12 +00001536void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001537 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001538
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001539 const SkPoint* pts = src.fPathRef->pointsEnd();
1540 // we will iterator through src's verbs backwards
1541 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1542 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001543
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001544 fIsOval = false;
1545
reed@google.com63d73742012-01-10 15:33:12 +00001546 bool needMove = true;
1547 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001548 while (verbs < verbsEnd) {
1549 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001550 int n = gPtsInVerb[v];
1551
1552 if (needMove) {
1553 --pts;
1554 this->moveTo(pts->fX, pts->fY);
1555 needMove = false;
1556 }
1557 pts -= n;
1558 switch (v) {
1559 case kMove_Verb:
1560 if (needClose) {
1561 this->close();
1562 needClose = false;
1563 }
1564 needMove = true;
1565 pts += 1; // so we see the point in "if (needMove)" above
1566 break;
1567 case kLine_Verb:
1568 this->lineTo(pts[0]);
1569 break;
1570 case kQuad_Verb:
1571 this->quadTo(pts[1], pts[0]);
1572 break;
1573 case kCubic_Verb:
1574 this->cubicTo(pts[2], pts[1], pts[0]);
1575 break;
1576 case kClose_Verb:
1577 needClose = true;
1578 break;
1579 default:
1580 SkASSERT(!"unexpected verb");
1581 }
1582 }
1583}
1584
reed@android.com8a1c16f2008-12-17 15:59:43 +00001585///////////////////////////////////////////////////////////////////////////////
1586
1587void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1588 SkMatrix matrix;
1589
1590 matrix.setTranslate(dx, dy);
1591 this->transform(matrix, dst);
1592}
1593
1594#include "SkGeometry.h"
1595
1596static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1597 int level = 2) {
1598 if (--level >= 0) {
1599 SkPoint tmp[5];
1600
1601 SkChopQuadAtHalf(pts, tmp);
1602 subdivide_quad_to(path, &tmp[0], level);
1603 subdivide_quad_to(path, &tmp[2], level);
1604 } else {
1605 path->quadTo(pts[1], pts[2]);
1606 }
1607}
1608
1609static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1610 int level = 2) {
1611 if (--level >= 0) {
1612 SkPoint tmp[7];
1613
1614 SkChopCubicAtHalf(pts, tmp);
1615 subdivide_cubic_to(path, &tmp[0], level);
1616 subdivide_cubic_to(path, &tmp[3], level);
1617 } else {
1618 path->cubicTo(pts[1], pts[2], pts[3]);
1619 }
1620}
1621
1622void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1623 SkDEBUGCODE(this->validate();)
1624 if (dst == NULL) {
1625 dst = (SkPath*)this;
1626 }
1627
tomhudson@google.com8d430182011-06-06 19:11:19 +00001628 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001629 SkPath tmp;
1630 tmp.fFillType = fFillType;
1631
1632 SkPath::Iter iter(*this, false);
1633 SkPoint pts[4];
1634 SkPath::Verb verb;
1635
reed@google.com4a3b7142012-05-16 17:16:46 +00001636 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001637 switch (verb) {
1638 case kMove_Verb:
1639 tmp.moveTo(pts[0]);
1640 break;
1641 case kLine_Verb:
1642 tmp.lineTo(pts[1]);
1643 break;
1644 case kQuad_Verb:
1645 subdivide_quad_to(&tmp, pts);
1646 break;
1647 case kCubic_Verb:
1648 subdivide_cubic_to(&tmp, pts);
1649 break;
1650 case kClose_Verb:
1651 tmp.close();
1652 break;
1653 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001654 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001655 break;
1656 }
1657 }
1658
1659 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001660 SkPathRef::Editor ed(&dst->fPathRef);
1661 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001662 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001663 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001664 /*
1665 * If we're not in perspective, we can transform all of the points at
1666 * once.
1667 *
1668 * Here we also want to optimize bounds, by noting if the bounds are
1669 * already known, and if so, we just transform those as well and mark
1670 * them as "known", rather than force the transformed path to have to
1671 * recompute them.
1672 *
1673 * Special gotchas if the path is effectively empty (<= 1 point) or
1674 * if it is non-finite. In those cases bounds need to stay empty,
1675 * regardless of the matrix.
1676 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001677 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001678 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001679 if (fIsFinite) {
1680 matrix.mapRect(&dst->fBounds, fBounds);
1681 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1682 dst->fBounds.setEmpty();
1683 }
1684 } else {
1685 dst->fIsFinite = false;
1686 dst->fBounds.setEmpty();
1687 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001689 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001690 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 }
1692
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001693 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1694
reed@android.com8a1c16f2008-12-17 15:59:43 +00001695 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001696 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001697 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001698 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001699 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001700
djsollen@google.com4bd2bdb2013-03-08 18:35:13 +00001701#ifdef SK_BUILD_FOR_ANDROID
1702 if (!matrix.isIdentity()) {
1703 GEN_ID_PTR_INC(dst);
1704 }
1705#endif
1706
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001707 if (kUnknown_Direction == fDirection) {
1708 dst->fDirection = kUnknown_Direction;
1709 } else {
1710 SkScalar det2x2 =
1711 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1712 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1713 if (det2x2 < 0) {
1714 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1715 } else if (det2x2 > 0) {
1716 dst->fDirection = fDirection;
1717 } else {
1718 dst->fDirection = kUnknown_Direction;
1719 }
1720 }
1721
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001722 // It's an oval only if it stays a rect.
1723 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001724
reed@android.com8a1c16f2008-12-17 15:59:43 +00001725 SkDEBUGCODE(dst->validate();)
1726 }
1727}
1728
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729///////////////////////////////////////////////////////////////////////////////
1730///////////////////////////////////////////////////////////////////////////////
1731
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001732enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001733 kEmptyContour_SegmentState, // The current contour is empty. We may be
1734 // starting processing or we may have just
1735 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001736 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1737 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1738 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001739};
1740
1741SkPath::Iter::Iter() {
1742#ifdef SK_DEBUG
1743 fPts = NULL;
1744 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001745 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001746 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001747#endif
1748 // need to init enough to make next() harmlessly return kDone_Verb
1749 fVerbs = NULL;
1750 fVerbStop = NULL;
1751 fNeedClose = false;
1752}
1753
1754SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1755 this->setPath(path, forceClose);
1756}
1757
1758void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001759 fPts = path.fPathRef->points();
1760 fVerbs = path.fPathRef->verbs();
1761 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001762 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001763 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001764 fForceClose = SkToU8(forceClose);
1765 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001766 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001767}
1768
1769bool SkPath::Iter::isClosedContour() const {
1770 if (fVerbs == NULL || fVerbs == fVerbStop) {
1771 return false;
1772 }
1773 if (fForceClose) {
1774 return true;
1775 }
1776
1777 const uint8_t* verbs = fVerbs;
1778 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001779
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001780 if (kMove_Verb == *(verbs - 1)) {
1781 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001782 }
1783
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001784 while (verbs > stop) {
1785 // verbs points one beyond the current verb, decrement first.
1786 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787 if (kMove_Verb == v) {
1788 break;
1789 }
1790 if (kClose_Verb == v) {
1791 return true;
1792 }
1793 }
1794 return false;
1795}
1796
1797SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001798 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001799 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001800 // A special case: if both points are NaN, SkPoint::operation== returns
1801 // false, but the iterator expects that they are treated as the same.
1802 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001803 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1804 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001805 return kClose_Verb;
1806 }
1807
reed@google.com9e25dbf2012-05-15 17:05:38 +00001808 pts[0] = fLastPt;
1809 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001810 fLastPt = fMoveTo;
1811 fCloseLine = true;
1812 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001813 } else {
1814 pts[0] = fMoveTo;
1815 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001817}
1818
reed@google.com9e25dbf2012-05-15 17:05:38 +00001819const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 if (fSegmentState == kAfterMove_SegmentState) {
1821 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001822 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001823 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001824 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001825 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1826 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001827 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001828 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001829}
1830
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001831void SkPath::Iter::consumeDegenerateSegments() {
1832 // We need to step over anything that will not move the current draw point
1833 // forward before the next move is seen
1834 const uint8_t* lastMoveVerb = 0;
1835 const SkPoint* lastMovePt = 0;
1836 SkPoint lastPt = fLastPt;
1837 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001838 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001839 switch (verb) {
1840 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001841 // Keep a record of this most recent move
1842 lastMoveVerb = fVerbs;
1843 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001844 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001845 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001846 fPts++;
1847 break;
1848
1849 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001850 // A close when we are in a segment is always valid except when it
1851 // follows a move which follows a segment.
1852 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001853 return;
1854 }
1855 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001856 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001857 break;
1858
1859 case kLine_Verb:
1860 if (!IsLineDegenerate(lastPt, fPts[0])) {
1861 if (lastMoveVerb) {
1862 fVerbs = lastMoveVerb;
1863 fPts = lastMovePt;
1864 return;
1865 }
1866 return;
1867 }
1868 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001869 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001870 fPts++;
1871 break;
1872
1873 case kQuad_Verb:
1874 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1875 if (lastMoveVerb) {
1876 fVerbs = lastMoveVerb;
1877 fPts = lastMovePt;
1878 return;
1879 }
1880 return;
1881 }
1882 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001883 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001884 fPts += 2;
1885 break;
1886
1887 case kCubic_Verb:
1888 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1889 if (lastMoveVerb) {
1890 fVerbs = lastMoveVerb;
1891 fPts = lastMovePt;
1892 return;
1893 }
1894 return;
1895 }
1896 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001897 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001898 fPts += 3;
1899 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001900
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001901 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001902 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001903 }
1904 }
1905}
1906
reed@google.com4a3b7142012-05-16 17:16:46 +00001907SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001908 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909
reed@android.com8a1c16f2008-12-17 15:59:43 +00001910 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 // Close the curve if requested and if there is some curve to close
1912 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001913 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001914 return kLine_Verb;
1915 }
1916 fNeedClose = false;
1917 return kClose_Verb;
1918 }
1919 return kDone_Verb;
1920 }
1921
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001922 // fVerbs is one beyond the current verb, decrement first
1923 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001924 const SkPoint* SK_RESTRICT srcPts = fPts;
1925 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001926
1927 switch (verb) {
1928 case kMove_Verb:
1929 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001930 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001931 verb = this->autoClose(pts);
1932 if (verb == kClose_Verb) {
1933 fNeedClose = false;
1934 }
1935 return (Verb)verb;
1936 }
1937 if (fVerbs == fVerbStop) { // might be a trailing moveto
1938 return kDone_Verb;
1939 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001940 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001941 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001942 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001943 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001944 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001945 fNeedClose = fForceClose;
1946 break;
1947 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001948 pts[0] = this->cons_moveTo();
1949 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001950 fLastPt = srcPts[0];
1951 fCloseLine = false;
1952 srcPts += 1;
1953 break;
1954 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001955 pts[0] = this->cons_moveTo();
1956 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957 fLastPt = srcPts[1];
1958 srcPts += 2;
1959 break;
1960 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001961 pts[0] = this->cons_moveTo();
1962 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001963 fLastPt = srcPts[2];
1964 srcPts += 3;
1965 break;
1966 case kClose_Verb:
1967 verb = this->autoClose(pts);
1968 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001969 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001970 } else {
1971 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001972 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001973 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001974 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001975 break;
1976 }
1977 fPts = srcPts;
1978 return (Verb)verb;
1979}
1980
1981///////////////////////////////////////////////////////////////////////////////
1982
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001983SkPath::RawIter::RawIter() {
1984#ifdef SK_DEBUG
1985 fPts = NULL;
1986 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1987#endif
1988 // need to init enough to make next() harmlessly return kDone_Verb
1989 fVerbs = NULL;
1990 fVerbStop = NULL;
1991}
1992
1993SkPath::RawIter::RawIter(const SkPath& path) {
1994 this->setPath(path);
1995}
1996
1997void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001998 fPts = path.fPathRef->points();
1999 fVerbs = path.fPathRef->verbs();
2000 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002001 fMoveTo.fX = fMoveTo.fY = 0;
2002 fLastPt.fX = fLastPt.fY = 0;
2003}
2004
2005SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002006 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002007 if (fVerbs == fVerbStop) {
2008 return kDone_Verb;
2009 }
2010
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002011 // fVerbs points one beyond next verb so decrement first.
2012 unsigned verb = *(--fVerbs);
2013 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002014
2015 switch (verb) {
2016 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002017 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002018 fMoveTo = srcPts[0];
2019 fLastPt = fMoveTo;
2020 srcPts += 1;
2021 break;
2022 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002023 pts[0] = fLastPt;
2024 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002025 fLastPt = srcPts[0];
2026 srcPts += 1;
2027 break;
2028 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002029 pts[0] = fLastPt;
2030 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002031 fLastPt = srcPts[1];
2032 srcPts += 2;
2033 break;
2034 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002035 pts[0] = fLastPt;
2036 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002037 fLastPt = srcPts[2];
2038 srcPts += 3;
2039 break;
2040 case kClose_Verb:
2041 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002042 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002043 break;
2044 }
2045 fPts = srcPts;
2046 return (Verb)verb;
2047}
2048
2049///////////////////////////////////////////////////////////////////////////////
2050
reed@android.com8a1c16f2008-12-17 15:59:43 +00002051/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002052 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002053*/
2054
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002055uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002056 SkDEBUGCODE(this->validate();)
2057
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002058 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002059 const int byteCount = sizeof(int32_t)
2060#if NEW_PICTURE_FORMAT
2061 + fPathRef->writeSize()
2062#else
2063 + 2 * sizeof(int32_t)
2064 + sizeof(SkPoint) * fPathRef->countPoints()
2065 + sizeof(uint8_t) * fPathRef->countVerbs()
2066#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002067 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002068 return SkAlign4(byteCount);
2069 }
2070
2071 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002072#if !NEW_PICTURE_FORMAT
2073 buffer.write32(fPathRef->countPoints());
2074 buffer.write32(fPathRef->countVerbs());
2075#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002076
2077 // Call getBounds() to ensure (as a side-effect) that fBounds
2078 // and fIsFinite are computed.
2079 const SkRect& bounds = this->getBounds();
2080 SkASSERT(!fBoundsIsDirty);
2081
2082 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2083 ((fIsOval & 1) << kIsOval_SerializationShift) |
2084 (fConvexity << kConvexity_SerializationShift) |
2085 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002086 (fSegmentMask << kSegmentMask_SerializationShift) |
2087 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002088
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002089 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002090
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002091 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002092
2093 buffer.write(&bounds, sizeof(bounds));
2094
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002095 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002096 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002097}
2098
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002099uint32_t SkPath::readFromMemory(const void* storage) {
2100 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002101#if !NEW_PICTURE_FORMAT
2102 int32_t pcount = buffer.readS32();
2103 int32_t vcount = buffer.readS32();
2104#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002105
reed@google.com98b11f12011-09-21 18:40:27 +00002106 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002107 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2108 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2109 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2110 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002111 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
2112 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002113
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002114#if NEW_PICTURE_FORMAT
2115 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
2116#else
2117 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
2118#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002119
2120 buffer.read(&fBounds, sizeof(fBounds));
2121 fBoundsIsDirty = false;
2122
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002123 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002124
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002125 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002126
2127 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002128 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002129}
2130
2131///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002132
reed@google.com51bbe752013-01-17 22:07:50 +00002133#include "SkString.h"
2134
2135static void append_scalar(SkString* str, SkScalar value) {
2136 SkString tmp;
2137 tmp.printf("%g", value);
2138 if (tmp.contains('.')) {
2139 tmp.appendUnichar('f');
2140 }
2141 str->append(tmp);
2142}
2143
2144static void append_params(SkString* str, const char label[], const SkPoint pts[],
2145 int count) {
2146 str->append(label);
2147 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002148
reed@google.com51bbe752013-01-17 22:07:50 +00002149 const SkScalar* values = &pts[0].fX;
2150 count *= 2;
2151
2152 for (int i = 0; i < count; ++i) {
2153 append_scalar(str, values[i]);
2154 if (i < count - 1) {
2155 str->append(", ");
2156 }
2157 }
2158 str->append(");\n");
2159}
2160
reed@android.com8a1c16f2008-12-17 15:59:43 +00002161void SkPath::dump(bool forceClose, const char title[]) const {
2162 Iter iter(*this, forceClose);
2163 SkPoint pts[4];
2164 Verb verb;
2165
2166 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2167 title ? title : "");
2168
reed@google.com51bbe752013-01-17 22:07:50 +00002169 SkString builder;
2170
reed@google.com4a3b7142012-05-16 17:16:46 +00002171 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002172 switch (verb) {
2173 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002174 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002175 break;
2176 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002177 append_params(&builder, "path.lineTo", &pts[1], 1);
2178 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002179 break;
2180 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002181 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002182 break;
2183 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002184 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002185 break;
2186 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002187 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002188 break;
2189 default:
2190 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2191 verb = kDone_Verb; // stop the loop
2192 break;
2193 }
2194 }
reed@google.com51bbe752013-01-17 22:07:50 +00002195 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002196}
2197
reed@android.come522ca52009-11-23 20:10:41 +00002198void SkPath::dump() const {
2199 this->dump(false);
2200}
2201
2202#ifdef SK_DEBUG
2203void SkPath::validate() const {
2204 SkASSERT(this != NULL);
2205 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002206
djsollen@google.com077348c2012-10-22 20:23:32 +00002207#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002208 if (!fBoundsIsDirty) {
2209 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002210
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002211 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002212 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002213
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002214 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002215 // if we're empty, fBounds may be empty but translated, so we can't
2216 // necessarily compare to bounds directly
2217 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2218 // be [2, 2, 2, 2]
2219 SkASSERT(bounds.isEmpty());
2220 SkASSERT(fBounds.isEmpty());
2221 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002222 if (bounds.isEmpty()) {
2223 SkASSERT(fBounds.isEmpty());
2224 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002225 if (!fBounds.isEmpty()) {
2226 SkASSERT(fBounds.contains(bounds));
2227 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002228 }
reed@android.come522ca52009-11-23 20:10:41 +00002229 }
2230 }
reed@google.com10296cc2011-09-21 12:29:05 +00002231
2232 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002233 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2234 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2235 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002236 case kLine_Verb:
2237 mask |= kLine_SegmentMask;
2238 break;
2239 case kQuad_Verb:
2240 mask |= kQuad_SegmentMask;
2241 break;
2242 case kCubic_Verb:
2243 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002244 case kMove_Verb: // these verbs aren't included in the segment mask.
2245 case kClose_Verb:
2246 break;
2247 case kDone_Verb:
2248 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2249 break;
2250 default:
2251 SkDEBUGFAIL("Unknown Verb");
2252 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002253 }
2254 }
2255 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002256#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002257}
djsollen@google.com077348c2012-10-22 20:23:32 +00002258#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002259
reed@google.com04863fa2011-05-15 04:08:24 +00002260///////////////////////////////////////////////////////////////////////////////
2261
reed@google.com0b7b9822011-05-16 12:29:27 +00002262static int sign(SkScalar x) { return x < 0; }
2263#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002264
reed@google.com04863fa2011-05-15 04:08:24 +00002265static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002266 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002267}
2268
2269// only valid for a single contour
2270struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002271 Convexicator()
2272 : fPtCount(0)
2273 , fConvexity(SkPath::kConvex_Convexity)
2274 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002275 fSign = 0;
2276 // warnings
2277 fCurrPt.set(0, 0);
2278 fVec0.set(0, 0);
2279 fVec1.set(0, 0);
2280 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002281
2282 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002283 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002284 }
2285
2286 SkPath::Convexity getConvexity() const { return fConvexity; }
2287
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002288 /** The direction returned is only valid if the path is determined convex */
2289 SkPath::Direction getDirection() const { return fDirection; }
2290
reed@google.com04863fa2011-05-15 04:08:24 +00002291 void addPt(const SkPoint& pt) {
2292 if (SkPath::kConcave_Convexity == fConvexity) {
2293 return;
2294 }
2295
2296 if (0 == fPtCount) {
2297 fCurrPt = pt;
2298 ++fPtCount;
2299 } else {
2300 SkVector vec = pt - fCurrPt;
2301 if (vec.fX || vec.fY) {
2302 fCurrPt = pt;
2303 if (++fPtCount == 2) {
2304 fFirstVec = fVec1 = vec;
2305 } else {
2306 SkASSERT(fPtCount > 2);
2307 this->addVec(vec);
2308 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002309
reed@google.com85b6e392011-05-15 20:25:17 +00002310 int sx = sign(vec.fX);
2311 int sy = sign(vec.fY);
2312 fDx += (sx != fSx);
2313 fDy += (sy != fSy);
2314 fSx = sx;
2315 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002316
reed@google.com85b6e392011-05-15 20:25:17 +00002317 if (fDx > 3 || fDy > 3) {
2318 fConvexity = SkPath::kConcave_Convexity;
2319 }
reed@google.com04863fa2011-05-15 04:08:24 +00002320 }
2321 }
2322 }
2323
2324 void close() {
2325 if (fPtCount > 2) {
2326 this->addVec(fFirstVec);
2327 }
2328 }
2329
2330private:
2331 void addVec(const SkVector& vec) {
2332 SkASSERT(vec.fX || vec.fY);
2333 fVec0 = fVec1;
2334 fVec1 = vec;
2335 int sign = CrossProductSign(fVec0, fVec1);
2336 if (0 == fSign) {
2337 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002338 if (1 == sign) {
2339 fDirection = SkPath::kCW_Direction;
2340 } else if (-1 == sign) {
2341 fDirection = SkPath::kCCW_Direction;
2342 }
reed@google.com04863fa2011-05-15 04:08:24 +00002343 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002344 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002345 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002346 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002347 }
2348 }
2349 }
2350
2351 SkPoint fCurrPt;
2352 SkVector fVec0, fVec1, fFirstVec;
2353 int fPtCount; // non-degenerate points
2354 int fSign;
2355 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002356 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002357 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002358};
2359
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002360SkPath::Convexity SkPath::internalGetConvexity() const {
2361 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002362 SkPoint pts[4];
2363 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002364 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002365
2366 int contourCount = 0;
2367 int count;
2368 Convexicator state;
2369
2370 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2371 switch (verb) {
2372 case kMove_Verb:
2373 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002374 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002375 return kConcave_Convexity;
2376 }
2377 pts[1] = pts[0];
2378 count = 1;
2379 break;
2380 case kLine_Verb: count = 1; break;
2381 case kQuad_Verb: count = 2; break;
2382 case kCubic_Verb: count = 3; break;
2383 case kClose_Verb:
2384 state.close();
2385 count = 0;
2386 break;
2387 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002388 SkDEBUGFAIL("bad verb");
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
2393 for (int i = 1; i <= count; i++) {
2394 state.addPt(pts[i]);
2395 }
2396 // early exit
2397 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002398 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002399 return kConcave_Convexity;
2400 }
2401 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002402 fConvexity = state.getConvexity();
2403 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2404 fDirection = state.getDirection();
2405 }
2406 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002407}
reed@google.com69a99432012-01-10 18:00:10 +00002408
2409///////////////////////////////////////////////////////////////////////////////
2410
2411class ContourIter {
2412public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002413 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002414
2415 bool done() const { return fDone; }
2416 // if !done() then these may be called
2417 int count() const { return fCurrPtCount; }
2418 const SkPoint* pts() const { return fCurrPt; }
2419 void next();
2420
2421private:
2422 int fCurrPtCount;
2423 const SkPoint* fCurrPt;
2424 const uint8_t* fCurrVerb;
2425 const uint8_t* fStopVerbs;
2426 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002427 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002428};
2429
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002430ContourIter::ContourIter(const SkPathRef& pathRef) {
2431 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002432 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002433 fCurrPt = pathRef.points();
2434 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002435 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002436 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002437 this->next();
2438}
2439
2440void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002441 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002442 fDone = true;
2443 }
2444 if (fDone) {
2445 return;
2446 }
2447
2448 // skip pts of prev contour
2449 fCurrPt += fCurrPtCount;
2450
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002451 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002452 int ptCount = 1; // moveTo
2453 const uint8_t* verbs = fCurrVerb;
2454
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002455 for (--verbs; verbs > fStopVerbs; --verbs) {
2456 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002457 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002458 goto CONTOUR_END;
2459 case SkPath::kLine_Verb:
2460 ptCount += 1;
2461 break;
2462 case SkPath::kQuad_Verb:
2463 ptCount += 2;
2464 break;
2465 case SkPath::kCubic_Verb:
2466 ptCount += 3;
2467 break;
2468 default: // kClose_Verb, just keep going
2469 break;
2470 }
2471 }
2472CONTOUR_END:
2473 fCurrPtCount = ptCount;
2474 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002475 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002476}
2477
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002478// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002479static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002480 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2481 // We may get 0 when the above subtracts underflow. We expect this to be
2482 // very rare and lazily promote to double.
2483 if (0 == cross) {
2484 double p0x = SkScalarToDouble(p0.fX);
2485 double p0y = SkScalarToDouble(p0.fY);
2486
2487 double p1x = SkScalarToDouble(p1.fX);
2488 double p1y = SkScalarToDouble(p1.fY);
2489
2490 double p2x = SkScalarToDouble(p2.fX);
2491 double p2y = SkScalarToDouble(p2.fY);
2492
2493 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2494 (p1y - p0y) * (p2x - p0x));
2495
2496 }
2497 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002498}
2499
reed@google.comc1ea60a2012-01-31 15:15:36 +00002500// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002501static int find_max_y(const SkPoint pts[], int count) {
2502 SkASSERT(count > 0);
2503 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002504 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002505 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002506 SkScalar y = pts[i].fY;
2507 if (y > max) {
2508 max = y;
2509 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002510 }
2511 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002512 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002513}
2514
reed@google.comcabaf1d2012-01-11 21:03:05 +00002515static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2516 int i = index;
2517 for (;;) {
2518 i = (i + inc) % n;
2519 if (i == index) { // we wrapped around, so abort
2520 break;
2521 }
2522 if (pts[index] != pts[i]) { // found a different point, success!
2523 break;
2524 }
2525 }
2526 return i;
2527}
2528
reed@google.comc1ea60a2012-01-31 15:15:36 +00002529/**
2530 * Starting at index, and moving forward (incrementing), find the xmin and
2531 * xmax of the contiguous points that have the same Y.
2532 */
2533static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2534 int* maxIndexPtr) {
2535 const SkScalar y = pts[index].fY;
2536 SkScalar min = pts[index].fX;
2537 SkScalar max = min;
2538 int minIndex = index;
2539 int maxIndex = index;
2540 for (int i = index + 1; i < n; ++i) {
2541 if (pts[i].fY != y) {
2542 break;
2543 }
2544 SkScalar x = pts[i].fX;
2545 if (x < min) {
2546 min = x;
2547 minIndex = i;
2548 } else if (x > max) {
2549 max = x;
2550 maxIndex = i;
2551 }
2552 }
2553 *maxIndexPtr = maxIndex;
2554 return minIndex;
2555}
2556
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002557static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002558 if (dir) {
2559 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2560 }
reed@google.comac8543f2012-01-30 20:51:25 +00002561}
2562
reed@google.comc1ea60a2012-01-31 15:15:36 +00002563#if 0
2564#include "SkString.h"
2565#include "../utils/SkParsePath.h"
2566static void dumpPath(const SkPath& path) {
2567 SkString str;
2568 SkParsePath::ToSVGString(path, &str);
2569 SkDebugf("%s\n", str.c_str());
2570}
2571#endif
2572
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002573namespace {
2574// for use with convex_dir_test
2575double mul(double a, double b) { return a * b; }
2576SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2577double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2578SkScalar toScalar(SkScalar a) { return a; }
2579
2580// determines the winding direction of a convex polygon with the precision
2581// of T. CAST_SCALAR casts an SkScalar to T.
2582template <typename T, T (CAST_SCALAR)(SkScalar)>
2583bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2584 // we find the first three points that form a non-degenerate
2585 // triangle. If there are no such points then the path is
2586 // degenerate. The first is always point 0. Now we find the second
2587 // point.
2588 int i = 0;
2589 enum { kX = 0, kY = 1 };
2590 T v0[2];
2591 while (1) {
2592 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2593 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2594 if (v0[kX] || v0[kY]) {
2595 break;
2596 }
2597 if (++i == n - 1) {
2598 return false;
2599 }
2600 }
2601 // now find a third point that is not colinear with the first two
2602 // points and check the orientation of the triangle (which will be
2603 // the same as the orientation of the path).
2604 for (++i; i < n; ++i) {
2605 T v1[2];
2606 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2607 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2608 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2609 if (0 != cross) {
2610 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2611 return true;
2612 }
2613 }
2614 return false;
2615}
2616}
2617
reed@google.comac8543f2012-01-30 20:51:25 +00002618/*
2619 * We loop through all contours, and keep the computed cross-product of the
2620 * contour that contained the global y-max. If we just look at the first
2621 * contour, we may find one that is wound the opposite way (correctly) since
2622 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2623 * that is outer most (or at least has the global y-max) before we can consider
2624 * its cross product.
2625 */
reed@google.com69a99432012-01-10 18:00:10 +00002626bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002627// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002628 // don't want to pay the cost for computing this if it
2629 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002630
2631 if (kUnknown_Direction != fDirection) {
2632 *dir = static_cast<Direction>(fDirection);
2633 return true;
2634 }
reed@google.com69a99432012-01-10 18:00:10 +00002635 const Convexity conv = this->getConvexityOrUnknown();
2636
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002637 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002638
reed@google.comac8543f2012-01-30 20:51:25 +00002639 // initialize with our logical y-min
2640 SkScalar ymax = this->getBounds().fTop;
2641 SkScalar ymaxCross = 0;
2642
reed@google.com69a99432012-01-10 18:00:10 +00002643 for (; !iter.done(); iter.next()) {
2644 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002645 if (n < 3) {
2646 continue;
2647 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002648
reed@google.comcabaf1d2012-01-11 21:03:05 +00002649 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002650 SkScalar cross = 0;
2651 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002652 // We try first at scalar precision, and then again at double
2653 // precision. This is because the vectors computed between distant
2654 // points may lose too much precision.
2655 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002656 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002657 return true;
2658 }
2659 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002660 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002661 return true;
2662 } else {
2663 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002664 }
2665 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002666 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002667 if (pts[index].fY < ymax) {
2668 continue;
2669 }
2670
reed@google.comc1ea60a2012-01-31 15:15:36 +00002671 // If there is more than 1 distinct point at the y-max, we take the
2672 // x-min and x-max of them and just subtract to compute the dir.
2673 if (pts[(index + 1) % n].fY == pts[index].fY) {
2674 int maxIndex;
2675 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002676 if (minIndex == maxIndex) {
2677 goto TRY_CROSSPROD;
2678 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002679 SkASSERT(pts[minIndex].fY == pts[index].fY);
2680 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2681 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2682 // we just subtract the indices, and let that auto-convert to
2683 // SkScalar, since we just want - or + to signal the direction.
2684 cross = minIndex - maxIndex;
2685 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002686 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002687 // Find a next and prev index to use for the cross-product test,
2688 // but we try to find pts that form non-zero vectors from pts[index]
2689 //
2690 // Its possible that we can't find two non-degenerate vectors, so
2691 // we have to guard our search (e.g. all the pts could be in the
2692 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002693
reed@google.comc1ea60a2012-01-31 15:15:36 +00002694 // we pass n - 1 instead of -1 so we don't foul up % operator by
2695 // passing it a negative LH argument.
2696 int prev = find_diff_pt(pts, index, n, n - 1);
2697 if (prev == index) {
2698 // completely degenerate, skip to next contour
2699 continue;
2700 }
2701 int next = find_diff_pt(pts, index, n, 1);
2702 SkASSERT(next != index);
2703 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002704 // 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 +00002705 // x-direction. We really should continue to walk away from the degeneracy until
2706 // there is a divergence.
2707 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002708 // construct the subtract so we get the correct Direction below
2709 cross = pts[index].fX - pts[next].fX;
2710 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002711 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002712
reed@google.comac8543f2012-01-30 20:51:25 +00002713 if (cross) {
2714 // record our best guess so far
2715 ymax = pts[index].fY;
2716 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002717 }
reed@google.com69a99432012-01-10 18:00:10 +00002718 }
2719 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002720 if (ymaxCross) {
2721 crossToDir(ymaxCross, dir);
2722 fDirection = *dir;
2723 return true;
2724 } else {
2725 return false;
2726 }
reed@google.comac8543f2012-01-30 20:51:25 +00002727}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002728
2729///////////////////////////////////////////////////////////////////////////////
2730
2731static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2732 SkScalar D, SkScalar t) {
2733 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2734}
2735
2736static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2737 SkScalar t) {
2738 SkScalar A = c3 + 3*(c1 - c2) - c0;
2739 SkScalar B = 3*(c2 - c1 - c1 + c0);
2740 SkScalar C = 3*(c1 - c0);
2741 SkScalar D = c0;
2742 return eval_cubic_coeff(A, B, C, D, t);
2743}
2744
2745/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2746 t value such that cubic(t) = target
2747 */
2748static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2749 SkScalar target, SkScalar* t) {
2750 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2751 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002752
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002753 SkScalar D = c0 - target;
2754 SkScalar A = c3 + 3*(c1 - c2) - c0;
2755 SkScalar B = 3*(c2 - c1 - c1 + c0);
2756 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002757
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002758 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2759 SkScalar minT = 0;
2760 SkScalar maxT = SK_Scalar1;
2761 SkScalar mid;
2762 int i;
2763 for (i = 0; i < 16; i++) {
2764 mid = SkScalarAve(minT, maxT);
2765 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2766 if (delta < 0) {
2767 minT = mid;
2768 delta = -delta;
2769 } else {
2770 maxT = mid;
2771 }
2772 if (delta < TOLERANCE) {
2773 break;
2774 }
2775 }
2776 *t = mid;
2777 return true;
2778}
2779
2780template <size_t N> static void find_minmax(const SkPoint pts[],
2781 SkScalar* minPtr, SkScalar* maxPtr) {
2782 SkScalar min, max;
2783 min = max = pts[0].fX;
2784 for (size_t i = 1; i < N; ++i) {
2785 min = SkMinScalar(min, pts[i].fX);
2786 max = SkMaxScalar(max, pts[i].fX);
2787 }
2788 *minPtr = min;
2789 *maxPtr = max;
2790}
2791
2792static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2793 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002794
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002795 int dir = 1;
2796 if (pts[0].fY > pts[3].fY) {
2797 storage[0] = pts[3];
2798 storage[1] = pts[2];
2799 storage[2] = pts[1];
2800 storage[3] = pts[0];
2801 pts = storage;
2802 dir = -1;
2803 }
2804 if (y < pts[0].fY || y >= pts[3].fY) {
2805 return 0;
2806 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002807
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002808 // quickreject or quickaccept
2809 SkScalar min, max;
2810 find_minmax<4>(pts, &min, &max);
2811 if (x < min) {
2812 return 0;
2813 }
2814 if (x > max) {
2815 return dir;
2816 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002817
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002818 // compute the actual x(t) value
2819 SkScalar t, xt;
2820 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2821 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2822 } else {
2823 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2824 xt = y < mid ? pts[0].fX : pts[3].fX;
2825 }
2826 return xt < x ? dir : 0;
2827}
2828
2829static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2830 SkPoint dst[10];
2831 int n = SkChopCubicAtYExtrema(pts, dst);
2832 int w = 0;
2833 for (int i = 0; i <= n; ++i) {
2834 w += winding_mono_cubic(&dst[i * 3], x, y);
2835 }
2836 return w;
2837}
2838
2839static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2840 SkScalar y0 = pts[0].fY;
2841 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002842
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002843 int dir = 1;
2844 if (y0 > y2) {
2845 SkTSwap(y0, y2);
2846 dir = -1;
2847 }
2848 if (y < y0 || y >= y2) {
2849 return 0;
2850 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002851
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002852 // bounds check on X (not required. is it faster?)
2853#if 0
2854 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2855 return 0;
2856 }
2857#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002858
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002859 SkScalar roots[2];
2860 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2861 2 * (pts[1].fY - pts[0].fY),
2862 pts[0].fY - y,
2863 roots);
2864 SkASSERT(n <= 1);
2865 SkScalar xt;
2866 if (0 == n) {
2867 SkScalar mid = SkScalarAve(y0, y2);
2868 // Need [0] and [2] if dir == 1
2869 // and [2] and [0] if dir == -1
2870 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2871 } else {
2872 SkScalar t = roots[0];
2873 SkScalar C = pts[0].fX;
2874 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2875 SkScalar B = 2 * (pts[1].fX - C);
2876 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2877 }
2878 return xt < x ? dir : 0;
2879}
2880
2881static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2882 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2883 if (y0 == y1) {
2884 return true;
2885 }
2886 if (y0 < y1) {
2887 return y1 <= y2;
2888 } else {
2889 return y1 >= y2;
2890 }
2891}
2892
2893static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2894 SkPoint dst[5];
2895 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002896
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002897 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2898 n = SkChopQuadAtYExtrema(pts, dst);
2899 pts = dst;
2900 }
2901 int w = winding_mono_quad(pts, x, y);
2902 if (n > 0) {
2903 w += winding_mono_quad(&pts[2], x, y);
2904 }
2905 return w;
2906}
2907
2908static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2909 SkScalar x0 = pts[0].fX;
2910 SkScalar y0 = pts[0].fY;
2911 SkScalar x1 = pts[1].fX;
2912 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002913
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002914 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002915
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002916 int dir = 1;
2917 if (y0 > y1) {
2918 SkTSwap(y0, y1);
2919 dir = -1;
2920 }
2921 if (y < y0 || y >= y1) {
2922 return 0;
2923 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002924
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002925 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2926 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002927
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002928 if (SkScalarSignAsInt(cross) == dir) {
2929 dir = 0;
2930 }
2931 return dir;
2932}
2933
2934bool SkPath::contains(SkScalar x, SkScalar y) const {
2935 bool isInverse = this->isInverseFillType();
2936 if (this->isEmpty()) {
2937 return isInverse;
2938 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002939
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002940 const SkRect& bounds = this->getBounds();
2941 if (!bounds.contains(x, y)) {
2942 return isInverse;
2943 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002944
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002945 SkPath::Iter iter(*this, true);
2946 bool done = false;
2947 int w = 0;
2948 do {
2949 SkPoint pts[4];
2950 switch (iter.next(pts, false)) {
2951 case SkPath::kMove_Verb:
2952 case SkPath::kClose_Verb:
2953 break;
2954 case SkPath::kLine_Verb:
2955 w += winding_line(pts, x, y);
2956 break;
2957 case SkPath::kQuad_Verb:
2958 w += winding_quad(pts, x, y);
2959 break;
2960 case SkPath::kCubic_Verb:
2961 w += winding_cubic(pts, x, y);
2962 break;
2963 case SkPath::kDone_Verb:
2964 done = true;
2965 break;
2966 }
2967 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002968
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002969 switch (this->getFillType()) {
2970 case SkPath::kEvenOdd_FillType:
2971 case SkPath::kInverseEvenOdd_FillType:
2972 w &= 1;
2973 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002974 default:
2975 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002976 }
2977 return SkToBool(w);
2978}