blob: c3aa44e117c41edc67720afe0170287141db097e [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
10#include "SkPath.h"
djsollen@google.com94e75ee2012-06-08 18:30:46 +000011#include "SkBuffer.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000013#include "SkPathRef.h"
14#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000015
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000016
17////////////////////////////////////////////////////////////////////////////
18
19#if SK_DEBUG_PATH_REF
20
21SkPath::PathRefDebugRef::PathRefDebugRef(SkPath* owner) : fOwner(owner) {}
22
23SkPath::PathRefDebugRef::PathRefDebugRef(SkPathRef* pr, SkPath* owner)
24: fPathRef(pr)
25, fOwner(owner) {
26 pr->addOwner(owner);
27}
28
29SkPath::PathRefDebugRef::~PathRefDebugRef() {
30 fPathRef->removeOwner(fOwner);
31}
32
33void SkPath::PathRefDebugRef::reset(SkPathRef* ref) {
34 bool diff = (ref != fPathRef.get());
35 if (diff && NULL != fPathRef.get()) {
36 fPathRef.get()->removeOwner(fOwner);
37 }
38 fPathRef.reset(ref);
39 if (diff && NULL != fPathRef.get()) {
40 fPathRef.get()->addOwner(fOwner);
41 }
42}
43
44void SkPath::PathRefDebugRef::swap(SkPath::PathRefDebugRef* other) {
45 if (other->fPathRef.get() != fPathRef.get()) {
46 other->fPathRef->removeOwner(other->fOwner);
47 other->fPathRef->addOwner(fOwner);
48
49 fPathRef->removeOwner(fOwner);
50 fPathRef->addOwner(other->fOwner);
51 }
52
53 fPathRef.swap(&other->fPathRef);
54}
55
56SkPathRef* SkPath::PathRefDebugRef::get() const { return fPathRef.get(); }
57
58SkAutoTUnref<SkPathRef>::BlockRefType *SkPath::PathRefDebugRef::operator->() const {
59 return fPathRef.operator->();
60}
61
62SkPath::PathRefDebugRef::operator SkPathRef*() {
63 return fPathRef.operator SkPathRef *();
64}
65
66#endif
skia.committer@gmail.com7cc7f492012-10-04 02:01:34 +000067
bsalomon@google.comae09f2d2012-10-03 19:57:01 +000068////////////////////////////////////////////////////////////////////////////
69
70
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000071SK_DEFINE_INST_COUNT(SkPath);
72
reed@google.com744faba2012-05-29 19:54:52 +000073// This value is just made-up for now. When count is 4, calling memset was much
74// slower than just writing the loop. This seems odd, and hopefully in the
75// future this we appear to have been a fluke...
76#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
77
reed@android.com8a1c16f2008-12-17 15:59:43 +000078////////////////////////////////////////////////////////////////////////////
79
reed@google.com3563c9e2011-11-14 19:34:57 +000080/**
81 * Path.bounds is defined to be the bounds of all the control points.
82 * If we called bounds.join(r) we would skip r if r was empty, which breaks
83 * our promise. Hence we have a custom joiner that doesn't look at emptiness
84 */
85static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
86 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
87 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
88 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
89 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
90}
91
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000092static bool is_degenerate(const SkPath& path) {
93 SkPath::Iter iter(path, false);
94 SkPoint pts[4];
95 return SkPath::kDone_Verb == iter.next(pts);
96}
97
bsalomon@google.com6aa29652012-04-18 13:29:52 +000098class SkAutoDisableOvalCheck {
99public:
100 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
101 fSaved = fPath->fIsOval;
102 }
103
104 ~SkAutoDisableOvalCheck() {
105 fPath->fIsOval = fSaved;
106 }
107
108private:
109 SkPath* fPath;
110 bool fSaved;
111};
112
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000113class SkAutoDisableDirectionCheck {
114public:
115 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
116 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
117 }
118
119 ~SkAutoDisableDirectionCheck() {
120 fPath->fDirection = fSaved;
121 }
122
123private:
124 SkPath* fPath;
125 SkPath::Direction fSaved;
126};
127
reed@android.com8a1c16f2008-12-17 15:59:43 +0000128/* This guy's constructor/destructor bracket a path editing operation. It is
129 used when we know the bounds of the amount we are going to add to the path
130 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +0000131
reed@android.com8a1c16f2008-12-17 15:59:43 +0000132 It captures some state about the path up front (i.e. if it already has a
133 cached bounds), and the if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +0000134 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +0000135
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000136 It also notes if the path was originally degenerate, and if so, sets
137 isConvex to true. Thus it can only be used if the contour being added is
138 convex.
reed@android.com8a1c16f2008-12-17 15:59:43 +0000139 */
140class SkAutoPathBoundsUpdate {
141public:
142 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
143 this->init(path);
144 }
145
146 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
147 SkScalar right, SkScalar bottom) {
148 fRect.set(left, top, right, bottom);
149 this->init(path);
150 }
reed@google.comabf15c12011-01-18 20:35:51 +0000151
reed@android.com8a1c16f2008-12-17 15:59:43 +0000152 ~SkAutoPathBoundsUpdate() {
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000153 fPath->setIsConvex(fDegenerate);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000154 if (fEmpty) {
reed@android.comd252db02009-04-01 18:31:44 +0000155 fPath->fBounds = fRect;
156 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000157 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000158 } else if (!fDirty) {
reed@google.com3563c9e2011-11-14 19:34:57 +0000159 joinNoEmptyChecks(&fPath->fBounds, fRect);
reed@android.comd252db02009-04-01 18:31:44 +0000160 fPath->fBoundsIsDirty = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000161 fPath->fIsFinite = fPath->fBounds.isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000162 }
163 }
reed@google.comabf15c12011-01-18 20:35:51 +0000164
reed@android.com8a1c16f2008-12-17 15:59:43 +0000165private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000166 SkPath* fPath;
167 SkRect fRect;
168 bool fDirty;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000169 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000170 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000171
reed@android.com8a1c16f2008-12-17 15:59:43 +0000172 // returns true if we should proceed
reed@android.com6b82d1a2009-06-03 02:35:01 +0000173 void init(SkPath* path) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000174 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000175 // Mark the path's bounds as dirty if (1) they are, or (2) the path
176 // is non-finite, and therefore its bounds are not meaningful
177 fDirty = SkToBool(path->fBoundsIsDirty) || !path->fIsFinite;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000178 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000179 fEmpty = path->isEmpty();
reed@android.com6c14b432009-03-23 20:11:11 +0000180 // Cannot use fRect for our bounds unless we know it is sorted
reed@android.com49f0ff22009-03-19 21:52:42 +0000181 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000182 }
183};
184
reed@google.com0bb18bb2012-07-26 15:20:36 +0000185// Return true if the computed bounds are finite.
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000186static bool compute_pt_bounds(SkRect* bounds, const SkPathRef& ref) {
187 int count = ref.countPoints();
reed@google.com0bb18bb2012-07-26 15:20:36 +0000188 if (count <= 1) { // we ignore just 1 point (moveto)
189 bounds->setEmpty();
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000190 return count ? ref.points()->isFinite() : true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000191 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000192 return bounds->setBoundsCheck(ref.points(), count);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000193 }
194}
195
196////////////////////////////////////////////////////////////////////////////
197
198/*
199 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000200 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000201 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
202
203 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000204 1. If we encounter degenerate segments, remove them
205 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
206 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
207 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000208*/
209
210////////////////////////////////////////////////////////////////////////////
211
reed@google.comd335d1d2012-01-12 18:17:11 +0000212// flag to require a moveTo if we begin with something else, like lineTo etc.
213#define INITIAL_LASTMOVETOINDEX_VALUE ~0
214
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000215SkPath::SkPath()
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000216#if SK_DEBUG_PATH_REF
217 : fPathRef(SkPathRef::CreateEmpty(), this)
218#else
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000219 : fPathRef(SkPathRef::CreateEmpty())
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000220#endif
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000221 , fFillType(kWinding_FillType)
bsalomon@google.com2ec72802011-09-21 21:46:03 +0000222 , fBoundsIsDirty(true) {
reed@google.com04863fa2011-05-15 04:08:24 +0000223 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000224 fDirection = kUnknown_Direction;
reed@google.com10296cc2011-09-21 12:29:05 +0000225 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000226 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000227 fIsOval = false;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000228 fIsFinite = false; // gets computed when we know our bounds
djsollen@google.com56c69772011-11-08 19:00:26 +0000229#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000230 fGenerationID = 0;
djsollen@google.come63793a2012-03-21 15:39:03 +0000231 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000232#endif
reed@android.com6b82d1a2009-06-03 02:35:01 +0000233}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234
bsalomon@google.comae09f2d2012-10-03 19:57:01 +0000235SkPath::SkPath(const SkPath& src)
236#if SK_DEBUG_PATH_REF
237 : fPathRef(this)
238#endif
239{
reed@android.com8a1c16f2008-12-17 15:59:43 +0000240 SkDEBUGCODE(src.validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000241 src.fPathRef.get()->ref();
242 fPathRef.reset(src.fPathRef.get());
243 fBounds = src.fBounds;
244 fFillType = src.fFillType;
245 fBoundsIsDirty = src.fBoundsIsDirty;
246 fConvexity = src.fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000247 fDirection = src.fDirection;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000248 fIsFinite = src.fIsFinite;
249 fSegmentMask = src.fSegmentMask;
250 fLastMoveToIndex = src.fLastMoveToIndex;
251 fIsOval = src.fIsOval;
djsollen@google.com56c69772011-11-08 19:00:26 +0000252#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000253 fGenerationID = src.fGenerationID;
254 fSourcePath = NULL;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000255#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000256}
257
258SkPath::~SkPath() {
259 SkDEBUGCODE(this->validate();)
260}
261
262SkPath& SkPath::operator=(const SkPath& src) {
263 SkDEBUGCODE(src.validate();)
264
265 if (this != &src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000266 src.fPathRef.get()->ref();
267 fPathRef.reset(src.fPathRef.get());
reed@android.comd252db02009-04-01 18:31:44 +0000268 fBounds = src.fBounds;
reed@android.comd252db02009-04-01 18:31:44 +0000269 fFillType = src.fFillType;
270 fBoundsIsDirty = src.fBoundsIsDirty;
reed@google.com04863fa2011-05-15 04:08:24 +0000271 fConvexity = src.fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000272 fDirection = src.fDirection;
reed@google.com0bb18bb2012-07-26 15:20:36 +0000273 fIsFinite = src.fIsFinite;
reed@google.com10296cc2011-09-21 12:29:05 +0000274 fSegmentMask = src.fSegmentMask;
reed@google.comd335d1d2012-01-12 18:17:11 +0000275 fLastMoveToIndex = src.fLastMoveToIndex;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000276 fIsOval = src.fIsOval;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000277 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000278 }
279 SkDEBUGCODE(this->validate();)
280 return *this;
281}
282
wjmaclean@chromium.org22023be2012-09-06 18:42:03 +0000283SK_API bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000284 // note: don't need to look at isConvex or bounds, since just comparing the
285 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000286
287 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
288 // since it is only a cache of info in the fVerbs, but its a fast way to
289 // notice a difference
290
reed@android.com3abec1d2009-03-02 05:36:20 +0000291 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000292 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000293 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000294}
295
reed@android.com8a1c16f2008-12-17 15:59:43 +0000296void SkPath::swap(SkPath& other) {
297 SkASSERT(&other != NULL);
298
299 if (this != &other) {
reed@android.comd252db02009-04-01 18:31:44 +0000300 SkTSwap<SkRect>(fBounds, other.fBounds);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000301 fPathRef.swap(&other.fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000302 SkTSwap<uint8_t>(fFillType, other.fFillType);
reed@android.comd252db02009-04-01 18:31:44 +0000303 SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
reed@google.com04863fa2011-05-15 04:08:24 +0000304 SkTSwap<uint8_t>(fConvexity, other.fConvexity);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000305 SkTSwap<uint8_t>(fDirection, other.fDirection);
reed@google.com10296cc2011-09-21 12:29:05 +0000306 SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
reed@google.comd335d1d2012-01-12 18:17:11 +0000307 SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000308 SkTSwap<SkBool8>(fIsOval, other.fIsOval);
reed@google.com0bb18bb2012-07-26 15:20:36 +0000309 SkTSwap<SkBool8>(fIsFinite, other.fIsFinite);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000310 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000311 }
312}
313
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000314static inline bool check_edge_against_rect(const SkPoint& p0,
315 const SkPoint& p1,
316 const SkRect& rect,
317 SkPath::Direction dir) {
318 const SkPoint* edgeBegin;
319 SkVector v;
320 if (SkPath::kCW_Direction == dir) {
321 v = p1 - p0;
322 edgeBegin = &p0;
323 } else {
324 v = p0 - p1;
325 edgeBegin = &p1;
326 }
327 if (v.fX || v.fY) {
328 // check the cross product of v with the vec from edgeBegin to each rect corner
329 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
330 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
331 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
332 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
333 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
334 return false;
335 }
336 }
337 return true;
338}
339
340bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
341 // This only handles non-degenerate convex paths currently.
342 if (kConvex_Convexity != this->getConvexity()) {
343 return false;
344 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000345
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000346 Direction direction;
347 if (!this->cheapComputeDirection(&direction)) {
348 return false;
349 }
350
351 SkPoint firstPt;
352 SkPoint prevPt;
353 RawIter iter(*this);
354 SkPath::Verb verb;
355 SkPoint pts[4];
356 SkDEBUGCODE(int moveCnt = 0;)
357
358 while ((verb = iter.next(pts)) != kDone_Verb) {
359 int nextPt = -1;
360 switch (verb) {
361 case kMove_Verb:
362 SkASSERT(!moveCnt);
363 SkDEBUGCODE(++moveCnt);
364 firstPt = prevPt = pts[0];
365 break;
366 case kLine_Verb:
367 nextPt = 1;
368 SkASSERT(moveCnt);
369 break;
370 case kQuad_Verb:
371 SkASSERT(moveCnt);
372 nextPt = 2;
373 break;
374 case kCubic_Verb:
375 SkASSERT(moveCnt);
376 nextPt = 3;
377 break;
378 case kClose_Verb:
379 break;
380 default:
381 SkDEBUGFAIL("unknown verb");
382 }
383 if (-1 != nextPt) {
384 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
385 return false;
386 }
387 prevPt = pts[nextPt];
388 }
389 }
390
391 return check_edge_against_rect(prevPt, firstPt, rect, direction);
392}
393
djsollen@google.com56c69772011-11-08 19:00:26 +0000394#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000395uint32_t SkPath::getGenerationID() const {
396 return fGenerationID;
397}
djsollen@google.come63793a2012-03-21 15:39:03 +0000398
399const SkPath* SkPath::getSourcePath() const {
400 return fSourcePath;
401}
402
403void SkPath::setSourcePath(const SkPath* path) {
404 fSourcePath = path;
405}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000406#endif
407
reed@android.com8a1c16f2008-12-17 15:59:43 +0000408void SkPath::reset() {
409 SkDEBUGCODE(this->validate();)
410
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000411 fPathRef.reset(SkPathRef::CreateEmpty());
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000412 GEN_ID_INC;
reed@android.comd252db02009-04-01 18:31:44 +0000413 fBoundsIsDirty = true;
reed@google.com04863fa2011-05-15 04:08:24 +0000414 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000415 fDirection = kUnknown_Direction;
reed@google.com10296cc2011-09-21 12:29:05 +0000416 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000417 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000418 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000419}
420
421void SkPath::rewind() {
422 SkDEBUGCODE(this->validate();)
423
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000424 SkPathRef::Rewind(&fPathRef);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000425 GEN_ID_INC;
reed@google.com04863fa2011-05-15 04:08:24 +0000426 fConvexity = kUnknown_Convexity;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000427 fBoundsIsDirty = true;
reed@google.com10296cc2011-09-21 12:29:05 +0000428 fSegmentMask = 0;
reed@google.comd335d1d2012-01-12 18:17:11 +0000429 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000430 fIsOval = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000431}
432
433bool SkPath::isEmpty() const {
434 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000435 return 0 == fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000436}
437
reed@google.com7e6c4d12012-05-10 14:05:43 +0000438bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000439 int verbCount = fPathRef->countVerbs();
440 int ptCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000441
reed@google.com7e6c4d12012-05-10 14:05:43 +0000442 if (2 == verbCount && 2 == ptCount) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000443 if (kMove_Verb == fPathRef->atVerb(0) &&
444 kLine_Verb == fPathRef->atVerb(1)) {
reed@google.com7e6c4d12012-05-10 14:05:43 +0000445 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000446 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000447 line[0] = pts[0];
448 line[1] = pts[1];
449 }
450 return true;
451 }
452 }
453 return false;
454}
455
caryclark@google.comf1316942011-07-26 19:54:45 +0000456/*
457 Determines if path is a rect by keeping track of changes in direction
458 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000459
caryclark@google.comf1316942011-07-26 19:54:45 +0000460 The direction is computed such that:
461 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000462 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000463 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000464 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000465
caryclark@google.comf1316942011-07-26 19:54:45 +0000466A rectangle cycles up/right/down/left or up/left/down/right.
467
468The test fails if:
469 The path is closed, and followed by a line.
470 A second move creates a new endpoint.
471 A diagonal line is parsed.
472 There's more than four changes of direction.
473 There's a discontinuity on the line (e.g., a move in the middle)
474 The line reverses direction.
475 The rectangle doesn't complete a cycle.
476 The path contains a quadratic or cubic.
477 The path contains fewer than four points.
478 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000479
caryclark@google.comf1316942011-07-26 19:54:45 +0000480It's OK if the path has:
481 Several colinear line segments composing a rectangle side.
482 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000483
caryclark@google.comf1316942011-07-26 19:54:45 +0000484The direction takes advantage of the corners found since opposite sides
485must travel in opposite directions.
486
487FIXME: Allow colinear quads and cubics to be treated like lines.
488FIXME: If the API passes fill-only, return true if the filled stroke
489 is a rectangle, though the caller failed to close the path.
490 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000491bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
492 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000493 int corners = 0;
494 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000495 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000496 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000497 first.set(0, 0);
498 last.set(0, 0);
499 int firstDirection = 0;
500 int lastDirection = 0;
501 int nextDirection = 0;
502 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000503 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000504 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000505 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
506 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000507 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000508 savePts = pts;
509 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000510 autoClose = true;
511 case kLine_Verb: {
512 SkScalar left = last.fX;
513 SkScalar top = last.fY;
514 SkScalar right = pts->fX;
515 SkScalar bottom = pts->fY;
516 ++pts;
517 if (left != right && top != bottom) {
518 return false; // diagonal
519 }
520 if (left == right && top == bottom) {
521 break; // single point on side OK
522 }
523 nextDirection = (left != right) << 0 |
524 (left < right || top < bottom) << 1;
525 if (0 == corners) {
526 firstDirection = nextDirection;
527 first = last;
528 last = pts[-1];
529 corners = 1;
530 closedOrMoved = false;
531 break;
532 }
533 if (closedOrMoved) {
534 return false; // closed followed by a line
535 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000536 if (autoClose && nextDirection == firstDirection) {
537 break; // colinear with first
538 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000539 closedOrMoved = autoClose;
540 if (lastDirection != nextDirection) {
541 if (++corners > 4) {
542 return false; // too many direction changes
543 }
544 }
545 last = pts[-1];
546 if (lastDirection == nextDirection) {
547 break; // colinear segment
548 }
549 // Possible values for corners are 2, 3, and 4.
550 // When corners == 3, nextDirection opposes firstDirection.
551 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000552 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000553 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
554 if ((directionCycle ^ turn) != nextDirection) {
555 return false; // direction didn't follow cycle
556 }
557 break;
558 }
559 case kQuad_Verb:
560 case kCubic_Verb:
561 return false; // quadratic, cubic not allowed
562 case kMove_Verb:
563 last = *pts++;
564 closedOrMoved = true;
565 break;
566 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000567 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000568 lastDirection = nextDirection;
569 }
570 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000571 bool result = 4 == corners && (first == last || autoClose);
572 if (savePts) {
573 *ptsPtr = savePts;
574 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000575 if (result && isClosed) {
576 *isClosed = autoClose;
577 }
578 if (result && direction) {
579 *direction = firstDirection == (lastDirection + 1 & 3) ? kCCW_Direction : kCW_Direction;
580 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000581 return result;
582}
583
584bool SkPath::isRect(SkRect* rect) const {
585 SkDEBUGCODE(this->validate();)
586 int currVerb = 0;
587 const SkPoint* pts = fPathRef->points();
caryclark@google.comf68154a2012-11-21 15:18:06 +0000588 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
caryclark@google.comf1316942011-07-26 19:54:45 +0000589 if (result && rect) {
590 *rect = getBounds();
591 }
592 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000593}
594
caryclark@google.comf68154a2012-11-21 15:18:06 +0000595bool SkPath::isRect(bool* isClosed, Direction* direction) const {
596 SkDEBUGCODE(this->validate();)
597 int currVerb = 0;
598 const SkPoint* pts = fPathRef->points();
599 return isRectContour(false, &currVerb, &pts, isClosed, direction);
600}
601
caryclark@google.com56f233a2012-11-19 13:06:06 +0000602bool SkPath::isNestedRects(SkRect rects[2]) const {
603 SkDEBUGCODE(this->validate();)
604 int currVerb = 0;
605 const SkPoint* pts = fPathRef->points();
606 const SkPoint* first = pts;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000607 if (!isRectContour(true, &currVerb, &pts, NULL, NULL)) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000608 return false;
609 }
610 const SkPoint* last = pts;
611 SkRect testRects[2];
caryclark@google.comf68154a2012-11-21 15:18:06 +0000612 if (isRectContour(false, &currVerb, &pts, NULL, NULL)) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000613 testRects[0].set(first, last - first);
614 testRects[1].set(last, pts - last);
615 if (testRects[0].contains(testRects[1])) {
616 if (rects) {
617 rects[0] = testRects[0];
618 rects[1] = testRects[1];
619 }
620 return true;
621 }
622 if (testRects[1].contains(testRects[0])) {
623 if (rects) {
624 rects[0] = testRects[1];
625 rects[1] = testRects[0];
626 }
627 return true;
628 }
629 }
630 return false;
631}
632
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000633int SkPath::countPoints() const {
634 return fPathRef->countPoints();
635}
636
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000637int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000638 SkDEBUGCODE(this->validate();)
639
640 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000641 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000642 int count = SkMin32(max, fPathRef->countPoints());
643 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
644 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000645}
646
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000647SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000648 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
649 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000650 }
651 return SkPoint::Make(0, 0);
652}
653
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000654int SkPath::countVerbs() const {
655 return fPathRef->countVerbs();
656}
657
658static inline void copy_verbs_reverse(uint8_t* inorderDst,
659 const uint8_t* reversedSrc,
660 int count) {
661 for (int i = 0; i < count; ++i) {
662 inorderDst[i] = reversedSrc[~i];
663 }
664}
665
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000666int SkPath::getVerbs(uint8_t dst[], int max) const {
667 SkDEBUGCODE(this->validate();)
668
669 SkASSERT(max >= 0);
670 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000671 int count = SkMin32(max, fPathRef->countVerbs());
672 copy_verbs_reverse(dst, fPathRef->verbs(), count);
673 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000674}
675
reed@google.com294dd7b2011-10-11 11:58:32 +0000676bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000677 SkDEBUGCODE(this->validate();)
678
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000679 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000680 if (count > 0) {
681 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000682 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000683 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000684 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000685 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000686 if (lastPt) {
687 lastPt->set(0, 0);
688 }
689 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690}
691
692void SkPath::setLastPt(SkScalar x, SkScalar y) {
693 SkDEBUGCODE(this->validate();)
694
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000695 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696 if (count == 0) {
697 this->moveTo(x, y);
698 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000699 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000700 SkPathRef::Editor ed(&fPathRef);
701 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000702 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000703 }
704}
705
reed@android.comd252db02009-04-01 18:31:44 +0000706void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000707 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000708 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000709
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000710 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000711 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000712}
713
reed@google.com04863fa2011-05-15 04:08:24 +0000714void SkPath::setConvexity(Convexity c) {
715 if (fConvexity != c) {
716 fConvexity = c;
717 GEN_ID_INC;
718 }
719}
720
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721//////////////////////////////////////////////////////////////////////////////
722// Construction methods
723
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000724#define DIRTY_AFTER_EDIT \
725 do { \
726 fBoundsIsDirty = true; \
727 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000728 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000729 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000730 } while (0)
731
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000732#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
733 do { \
734 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000735 } while (0)
736
reed@android.com8a1c16f2008-12-17 15:59:43 +0000737void SkPath::incReserve(U16CPU inc) {
738 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000739 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000740 SkDEBUGCODE(this->validate();)
741}
742
743void SkPath::moveTo(SkScalar x, SkScalar y) {
744 SkDEBUGCODE(this->validate();)
745
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000746 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747
reed@google.comd335d1d2012-01-12 18:17:11 +0000748 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000749 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000750
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000751 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000752
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000753 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000754 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000755}
756
757void SkPath::rMoveTo(SkScalar x, SkScalar y) {
758 SkPoint pt;
759 this->getLastPt(&pt);
760 this->moveTo(pt.fX + x, pt.fY + y);
761}
762
reed@google.comd335d1d2012-01-12 18:17:11 +0000763void SkPath::injectMoveToIfNeeded() {
764 if (fLastMoveToIndex < 0) {
765 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000766 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000767 x = y = 0;
768 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000769 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000770 x = pt.fX;
771 y = pt.fY;
772 }
773 this->moveTo(x, y);
774 }
775}
776
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777void SkPath::lineTo(SkScalar x, SkScalar y) {
778 SkDEBUGCODE(this->validate();)
779
reed@google.comd335d1d2012-01-12 18:17:11 +0000780 this->injectMoveToIfNeeded();
781
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000782 SkPathRef::Editor ed(&fPathRef);
783 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000784 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000785
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000786 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000787 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000788}
789
790void SkPath::rLineTo(SkScalar x, SkScalar y) {
791 SkPoint pt;
792 this->getLastPt(&pt);
793 this->lineTo(pt.fX + x, pt.fY + y);
794}
795
796void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
797 SkDEBUGCODE(this->validate();)
798
reed@google.comd335d1d2012-01-12 18:17:11 +0000799 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000801 SkPathRef::Editor ed(&fPathRef);
802 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000803 pts[0].set(x1, y1);
804 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000805 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000806
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000807 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000808 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809}
810
811void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
812 SkPoint pt;
813 this->getLastPt(&pt);
814 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
815}
816
817void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
818 SkScalar x3, SkScalar y3) {
819 SkDEBUGCODE(this->validate();)
820
reed@google.comd335d1d2012-01-12 18:17:11 +0000821 this->injectMoveToIfNeeded();
822
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000823 SkPathRef::Editor ed(&fPathRef);
824 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825 pts[0].set(x1, y1);
826 pts[1].set(x2, y2);
827 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000828 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000829
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000830 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000831 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000832}
833
834void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
835 SkScalar x3, SkScalar y3) {
836 SkPoint pt;
837 this->getLastPt(&pt);
838 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
839 pt.fX + x3, pt.fY + y3);
840}
841
842void SkPath::close() {
843 SkDEBUGCODE(this->validate();)
844
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000845 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000846 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000847 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000848 case kLine_Verb:
849 case kQuad_Verb:
850 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000851 case kMove_Verb: {
852 SkPathRef::Editor ed(&fPathRef);
853 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000854 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000855 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000856 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000857 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000858 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000859 break;
860 }
861 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000862
863 // signal that we need a moveTo to follow us (unless we're done)
864#if 0
865 if (fLastMoveToIndex >= 0) {
866 fLastMoveToIndex = ~fLastMoveToIndex;
867 }
868#else
869 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
870#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000871}
872
873///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000874
reed@android.com8a1c16f2008-12-17 15:59:43 +0000875void SkPath::addRect(const SkRect& rect, Direction dir) {
876 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
877}
878
879void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
880 SkScalar bottom, Direction dir) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000881 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
882 SkAutoDisableDirectionCheck addc(this);
883
reed@android.com8a1c16f2008-12-17 15:59:43 +0000884 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
885
886 this->incReserve(5);
887
888 this->moveTo(left, top);
889 if (dir == kCCW_Direction) {
890 this->lineTo(left, bottom);
891 this->lineTo(right, bottom);
892 this->lineTo(right, top);
893 } else {
894 this->lineTo(right, top);
895 this->lineTo(right, bottom);
896 this->lineTo(left, bottom);
897 }
898 this->close();
899}
900
reed@google.com744faba2012-05-29 19:54:52 +0000901void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
902 SkDEBUGCODE(this->validate();)
903 if (count <= 0) {
904 return;
905 }
906
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000907 SkPathRef::Editor ed(&fPathRef);
908 fLastMoveToIndex = ed.pathRef()->countPoints();
909 uint8_t* vb;
910 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000911 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000912 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000913
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000914 memcpy(p, pts, count * sizeof(SkPoint));
915 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000916 if (count > 1) {
917 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
918 // be 0, the compiler will remove the test/branch entirely.
919 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000920 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000921 } else {
922 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000923 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000924 }
925 }
926 fSegmentMask |= kLine_SegmentMask;
927 }
928 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000929 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000930 }
931
932 GEN_ID_INC;
933 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000934 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000935}
936
reed@android.com8a1c16f2008-12-17 15:59:43 +0000937#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
938
939void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
940 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000941 SkScalar w = rect.width();
942 SkScalar halfW = SkScalarHalf(w);
943 SkScalar h = rect.height();
944 SkScalar halfH = SkScalarHalf(h);
945
946 if (halfW <= 0 || halfH <= 0) {
947 return;
948 }
949
reed@google.comabf15c12011-01-18 20:35:51 +0000950 bool skip_hori = rx >= halfW;
951 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000952
953 if (skip_hori && skip_vert) {
954 this->addOval(rect, dir);
955 return;
956 }
reed@google.comabf15c12011-01-18 20:35:51 +0000957
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000958 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
959
reed@google.comabf15c12011-01-18 20:35:51 +0000960 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000961 SkAutoDisableDirectionCheck(this);
reed@google.comabf15c12011-01-18 20:35:51 +0000962
reed@android.com8a1c16f2008-12-17 15:59:43 +0000963 if (skip_hori) {
964 rx = halfW;
965 } else if (skip_vert) {
966 ry = halfH;
967 }
968
969 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
970 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
971
972 this->incReserve(17);
973 this->moveTo(rect.fRight - rx, rect.fTop);
974 if (dir == kCCW_Direction) {
975 if (!skip_hori) {
976 this->lineTo(rect.fLeft + rx, rect.fTop); // top
977 }
978 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
979 rect.fLeft, rect.fTop + ry - sy,
980 rect.fLeft, rect.fTop + ry); // top-left
981 if (!skip_vert) {
982 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
983 }
984 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
985 rect.fLeft + rx - sx, rect.fBottom,
986 rect.fLeft + rx, rect.fBottom); // bot-left
987 if (!skip_hori) {
988 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
989 }
990 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
991 rect.fRight, rect.fBottom - ry + sy,
992 rect.fRight, rect.fBottom - ry); // bot-right
993 if (!skip_vert) {
994 this->lineTo(rect.fRight, rect.fTop + ry);
995 }
996 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
997 rect.fRight - rx + sx, rect.fTop,
998 rect.fRight - rx, rect.fTop); // top-right
999 } else {
1000 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1001 rect.fRight, rect.fTop + ry - sy,
1002 rect.fRight, rect.fTop + ry); // top-right
1003 if (!skip_vert) {
1004 this->lineTo(rect.fRight, rect.fBottom - ry);
1005 }
1006 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1007 rect.fRight - rx + sx, rect.fBottom,
1008 rect.fRight - rx, rect.fBottom); // bot-right
1009 if (!skip_hori) {
1010 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
1011 }
1012 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1013 rect.fLeft, rect.fBottom - ry + sy,
1014 rect.fLeft, rect.fBottom - ry); // bot-left
1015 if (!skip_vert) {
1016 this->lineTo(rect.fLeft, rect.fTop + ry); // left
1017 }
1018 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1019 rect.fLeft + rx - sx, rect.fTop,
1020 rect.fLeft + rx, rect.fTop); // top-left
1021 if (!skip_hori) {
1022 this->lineTo(rect.fRight - rx, rect.fTop); // top
1023 }
1024 }
1025 this->close();
1026}
1027
1028static void add_corner_arc(SkPath* path, const SkRect& rect,
1029 SkScalar rx, SkScalar ry, int startAngle,
1030 SkPath::Direction dir, bool forceMoveTo) {
1031 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
1032 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +00001033
reed@android.com8a1c16f2008-12-17 15:59:43 +00001034 SkRect r;
1035 r.set(-rx, -ry, rx, ry);
1036
1037 switch (startAngle) {
1038 case 0:
1039 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
1040 break;
1041 case 90:
1042 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
1043 break;
1044 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
1045 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001046 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001047 }
reed@google.comabf15c12011-01-18 20:35:51 +00001048
reed@android.com8a1c16f2008-12-17 15:59:43 +00001049 SkScalar start = SkIntToScalar(startAngle);
1050 SkScalar sweep = SkIntToScalar(90);
1051 if (SkPath::kCCW_Direction == dir) {
1052 start += sweep;
1053 sweep = -sweep;
1054 }
reed@google.comabf15c12011-01-18 20:35:51 +00001055
reed@android.com8a1c16f2008-12-17 15:59:43 +00001056 path->arcTo(r, start, sweep, forceMoveTo);
1057}
1058
1059void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
1060 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +00001061 // abort before we invoke SkAutoPathBoundsUpdate()
1062 if (rect.isEmpty()) {
1063 return;
1064 }
1065
reed@android.com8a1c16f2008-12-17 15:59:43 +00001066 SkAutoPathBoundsUpdate apbu(this, rect);
1067
1068 if (kCW_Direction == dir) {
1069 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
1070 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
1071 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
1072 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
1073 } else {
1074 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
1075 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
1076 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
1077 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
1078 }
1079 this->close();
1080}
1081
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001082bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001083 int count = fPathRef->countVerbs();
1084 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1085 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001086 if (*verbs == kLine_Verb ||
1087 *verbs == kQuad_Verb ||
1088 *verbs == kCubic_Verb) {
1089 return false;
1090 }
1091 ++verbs;
1092 }
1093 return true;
1094}
1095
reed@android.com8a1c16f2008-12-17 15:59:43 +00001096void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001097 /* If addOval() is called after previous moveTo(),
1098 this path is still marked as an oval. This is used to
1099 fit into WebKit's calling sequences.
1100 We can't simply check isEmpty() in this case, as additional
1101 moveTo() would mark the path non empty.
1102 */
1103 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001104 if (fIsOval) {
1105 fDirection = dir;
1106 } else {
1107 fDirection = kUnknown_Direction;
1108 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001109
1110 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001111 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001112
reed@android.com8a1c16f2008-12-17 15:59:43 +00001113 SkAutoPathBoundsUpdate apbu(this, oval);
1114
1115 SkScalar cx = oval.centerX();
1116 SkScalar cy = oval.centerY();
1117 SkScalar rx = SkScalarHalf(oval.width());
1118 SkScalar ry = SkScalarHalf(oval.height());
1119#if 0 // these seem faster than using quads (1/2 the number of edges)
1120 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1121 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
1122
1123 this->incReserve(13);
1124 this->moveTo(cx + rx, cy);
1125 if (dir == kCCW_Direction) {
1126 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
1127 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
1128 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
1129 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
1130 } else {
1131 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
1132 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
1133 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
1134 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
1135 }
1136#else
1137 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1138 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1139 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1140 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1141
1142 /*
1143 To handle imprecision in computing the center and radii, we revert to
1144 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1145 to ensure that we don't exceed the oval's bounds *ever*, since we want
1146 to use oval for our fast-bounds, rather than have to recompute it.
1147 */
1148 const SkScalar L = oval.fLeft; // cx - rx
1149 const SkScalar T = oval.fTop; // cy - ry
1150 const SkScalar R = oval.fRight; // cx + rx
1151 const SkScalar B = oval.fBottom; // cy + ry
1152
1153 this->incReserve(17); // 8 quads + close
1154 this->moveTo(R, cy);
1155 if (dir == kCCW_Direction) {
1156 this->quadTo( R, cy - sy, cx + mx, cy - my);
1157 this->quadTo(cx + sx, T, cx , T);
1158 this->quadTo(cx - sx, T, cx - mx, cy - my);
1159 this->quadTo( L, cy - sy, L, cy );
1160 this->quadTo( L, cy + sy, cx - mx, cy + my);
1161 this->quadTo(cx - sx, B, cx , B);
1162 this->quadTo(cx + sx, B, cx + mx, cy + my);
1163 this->quadTo( R, cy + sy, R, cy );
1164 } else {
1165 this->quadTo( R, cy + sy, cx + mx, cy + my);
1166 this->quadTo(cx + sx, B, cx , B);
1167 this->quadTo(cx - sx, B, cx - mx, cy + my);
1168 this->quadTo( L, cy + sy, L, cy );
1169 this->quadTo( L, cy - sy, cx - mx, cy - my);
1170 this->quadTo(cx - sx, T, cx , T);
1171 this->quadTo(cx + sx, T, cx + mx, cy - my);
1172 this->quadTo( R, cy - sy, R, cy );
1173 }
1174#endif
1175 this->close();
1176}
1177
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001178bool SkPath::isOval(SkRect* rect) const {
1179 if (fIsOval && rect) {
1180 *rect = getBounds();
1181 }
1182
1183 return fIsOval;
1184}
1185
reed@android.com8a1c16f2008-12-17 15:59:43 +00001186void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1187 if (r > 0) {
1188 SkRect rect;
1189 rect.set(x - r, y - r, x + r, y + r);
1190 this->addOval(rect, dir);
1191 }
1192}
1193
1194#include "SkGeometry.h"
1195
1196static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1197 SkScalar sweepAngle,
1198 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001199
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001200 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001201 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1202 // Chrome uses this path to move into and out of ovals. If not
1203 // treated as a special case the moves can distort the oval's
1204 // bounding box (and break the circle special case).
1205 pts[0].set(oval.fRight, oval.centerY());
1206 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001207 } else if (0 == oval.width() && 0 == oval.height()) {
1208 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001209 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001210 // a rect.
1211 // TODO: optimizing the case where only one of width or height is zero
1212 // should also be considered. This case, however, doesn't seem to be
1213 // as common as the single point case.
1214 pts[0].set(oval.fRight, oval.fTop);
1215 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001216 }
1217
reed@android.com8a1c16f2008-12-17 15:59:43 +00001218 SkVector start, stop;
1219
1220 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1221 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1222 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001223
1224 /* If the sweep angle is nearly (but less than) 360, then due to precision
1225 loss in radians-conversion and/or sin/cos, we may end up with coincident
1226 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1227 of drawing a nearly complete circle (good).
1228 e.g. canvas.drawArc(0, 359.99, ...)
1229 -vs- canvas.drawArc(0, 359.9, ...)
1230 We try to detect this edge case, and tweak the stop vector
1231 */
1232 if (start == stop) {
1233 SkScalar sw = SkScalarAbs(sweepAngle);
1234 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1235 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1236 // make a guess at a tiny angle (in radians) to tweak by
1237 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1238 // not sure how much will be enough, so we use a loop
1239 do {
1240 stopRad -= deltaRad;
1241 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1242 } while (start == stop);
1243 }
1244 }
1245
reed@android.com8a1c16f2008-12-17 15:59:43 +00001246 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001247
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1249 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001250
reed@android.com8a1c16f2008-12-17 15:59:43 +00001251 return SkBuildQuadArc(start, stop,
1252 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1253 &matrix, pts);
1254}
1255
1256void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1257 bool forceMoveTo) {
1258 if (oval.width() < 0 || oval.height() < 0) {
1259 return;
1260 }
1261
1262 SkPoint pts[kSkBuildQuadArcStorage];
1263 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1264 SkASSERT((count & 1) == 1);
1265
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001266 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001267 forceMoveTo = true;
1268 }
1269 this->incReserve(count);
1270 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1271 for (int i = 1; i < count; i += 2) {
1272 this->quadTo(pts[i], pts[i+1]);
1273 }
1274}
1275
1276void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1277 SkScalar sweepAngle) {
1278 if (oval.isEmpty() || 0 == sweepAngle) {
1279 return;
1280 }
1281
1282 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1283
1284 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1285 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1286 return;
1287 }
1288
1289 SkPoint pts[kSkBuildQuadArcStorage];
1290 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1291
1292 this->incReserve(count);
1293 this->moveTo(pts[0]);
1294 for (int i = 1; i < count; i += 2) {
1295 this->quadTo(pts[i], pts[i+1]);
1296 }
1297}
1298
1299/*
1300 Need to handle the case when the angle is sharp, and our computed end-points
1301 for the arc go behind pt1 and/or p2...
1302*/
1303void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1304 SkScalar radius) {
1305 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001306
reed@android.com8a1c16f2008-12-17 15:59:43 +00001307 // need to know our prev pt so we can construct tangent vectors
1308 {
1309 SkPoint start;
1310 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001311 // Handle degenerate cases by adding a line to the first point and
1312 // bailing out.
1313 if ((x1 == start.fX && y1 == start.fY) ||
1314 (x1 == x2 && y1 == y2) ||
1315 radius == 0) {
1316 this->lineTo(x1, y1);
1317 return;
1318 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001319 before.setNormalize(x1 - start.fX, y1 - start.fY);
1320 after.setNormalize(x2 - x1, y2 - y1);
1321 }
reed@google.comabf15c12011-01-18 20:35:51 +00001322
reed@android.com8a1c16f2008-12-17 15:59:43 +00001323 SkScalar cosh = SkPoint::DotProduct(before, after);
1324 SkScalar sinh = SkPoint::CrossProduct(before, after);
1325
1326 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001327 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001328 return;
1329 }
reed@google.comabf15c12011-01-18 20:35:51 +00001330
reed@android.com8a1c16f2008-12-17 15:59:43 +00001331 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1332 if (dist < 0) {
1333 dist = -dist;
1334 }
1335
1336 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1337 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1338 SkRotationDirection arcDir;
1339
1340 // now turn before/after into normals
1341 if (sinh > 0) {
1342 before.rotateCCW();
1343 after.rotateCCW();
1344 arcDir = kCW_SkRotationDirection;
1345 } else {
1346 before.rotateCW();
1347 after.rotateCW();
1348 arcDir = kCCW_SkRotationDirection;
1349 }
1350
1351 SkMatrix matrix;
1352 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001353
reed@android.com8a1c16f2008-12-17 15:59:43 +00001354 matrix.setScale(radius, radius);
1355 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1356 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001357
reed@android.com8a1c16f2008-12-17 15:59:43 +00001358 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001359
reed@android.com8a1c16f2008-12-17 15:59:43 +00001360 this->incReserve(count);
1361 // [xx,yy] == pts[0]
1362 this->lineTo(xx, yy);
1363 for (int i = 1; i < count; i += 2) {
1364 this->quadTo(pts[i], pts[i+1]);
1365 }
1366}
1367
1368///////////////////////////////////////////////////////////////////////////////
1369
1370void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1371 SkMatrix matrix;
1372
1373 matrix.setTranslate(dx, dy);
1374 this->addPath(path, matrix);
1375}
1376
1377void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001378 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001379
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001380 fIsOval = false;
1381
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001382 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001383 SkPoint pts[4];
1384 Verb verb;
1385
1386 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1387
1388 while ((verb = iter.next(pts)) != kDone_Verb) {
1389 switch (verb) {
1390 case kMove_Verb:
1391 proc(matrix, &pts[0], &pts[0], 1);
1392 this->moveTo(pts[0]);
1393 break;
1394 case kLine_Verb:
1395 proc(matrix, &pts[1], &pts[1], 1);
1396 this->lineTo(pts[1]);
1397 break;
1398 case kQuad_Verb:
1399 proc(matrix, &pts[1], &pts[1], 2);
1400 this->quadTo(pts[1], pts[2]);
1401 break;
1402 case kCubic_Verb:
1403 proc(matrix, &pts[1], &pts[1], 3);
1404 this->cubicTo(pts[1], pts[2], pts[3]);
1405 break;
1406 case kClose_Verb:
1407 this->close();
1408 break;
1409 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001410 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411 }
1412 }
1413}
1414
1415///////////////////////////////////////////////////////////////////////////////
1416
1417static const uint8_t gPtsInVerb[] = {
1418 1, // kMove
1419 1, // kLine
1420 2, // kQuad
1421 3, // kCubic
1422 0, // kClose
1423 0 // kDone
1424};
1425
1426// ignore the initial moveto, and stop when the 1st contour ends
1427void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001428 int i, vcount = path.fPathRef->countVerbs();
1429 // exit early if the path is empty, or just has a moveTo.
1430 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001431 return;
1432 }
1433
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001434 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001435
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001436 fIsOval = false;
1437
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001438 const uint8_t* verbs = path.fPathRef->verbs();
1439 // skip the initial moveTo
1440 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001441
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001442 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001444 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001445 case kLine_Verb:
1446 this->lineTo(pts[0].fX, pts[0].fY);
1447 break;
1448 case kQuad_Verb:
1449 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1450 break;
1451 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001452 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 +00001453 break;
1454 case kClose_Verb:
1455 return;
1456 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001457 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001458 }
1459}
1460
1461// ignore the last point of the 1st contour
1462void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001463 int i, vcount = path.fPathRef->countVerbs();
1464 // exit early if the path is empty, or just has a moveTo.
1465 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001466 return;
1467 }
1468
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001469 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001470
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001471 fIsOval = false;
1472
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001473 const uint8_t* verbs = path.fPathRef->verbs();
1474 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001475
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001476 SkASSERT(verbs[~0] == kMove_Verb);
1477 for (i = 1; i < vcount; ++i) {
1478 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001479 if (n == 0) {
1480 break;
1481 }
1482 pts += n;
1483 }
1484
1485 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001486 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487 case kLine_Verb:
1488 this->lineTo(pts[-1].fX, pts[-1].fY);
1489 break;
1490 case kQuad_Verb:
1491 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1492 break;
1493 case kCubic_Verb:
1494 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1495 pts[-3].fX, pts[-3].fY);
1496 break;
1497 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001498 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001499 break;
1500 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001501 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001502 }
1503}
1504
reed@google.com63d73742012-01-10 15:33:12 +00001505void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001506 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001507
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001508 const SkPoint* pts = src.fPathRef->pointsEnd();
1509 // we will iterator through src's verbs backwards
1510 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1511 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001512
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001513 fIsOval = false;
1514
reed@google.com63d73742012-01-10 15:33:12 +00001515 bool needMove = true;
1516 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001517 while (verbs < verbsEnd) {
1518 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001519 int n = gPtsInVerb[v];
1520
1521 if (needMove) {
1522 --pts;
1523 this->moveTo(pts->fX, pts->fY);
1524 needMove = false;
1525 }
1526 pts -= n;
1527 switch (v) {
1528 case kMove_Verb:
1529 if (needClose) {
1530 this->close();
1531 needClose = false;
1532 }
1533 needMove = true;
1534 pts += 1; // so we see the point in "if (needMove)" above
1535 break;
1536 case kLine_Verb:
1537 this->lineTo(pts[0]);
1538 break;
1539 case kQuad_Verb:
1540 this->quadTo(pts[1], pts[0]);
1541 break;
1542 case kCubic_Verb:
1543 this->cubicTo(pts[2], pts[1], pts[0]);
1544 break;
1545 case kClose_Verb:
1546 needClose = true;
1547 break;
1548 default:
1549 SkASSERT(!"unexpected verb");
1550 }
1551 }
1552}
1553
reed@android.com8a1c16f2008-12-17 15:59:43 +00001554///////////////////////////////////////////////////////////////////////////////
1555
1556void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1557 SkMatrix matrix;
1558
1559 matrix.setTranslate(dx, dy);
1560 this->transform(matrix, dst);
1561}
1562
1563#include "SkGeometry.h"
1564
1565static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1566 int level = 2) {
1567 if (--level >= 0) {
1568 SkPoint tmp[5];
1569
1570 SkChopQuadAtHalf(pts, tmp);
1571 subdivide_quad_to(path, &tmp[0], level);
1572 subdivide_quad_to(path, &tmp[2], level);
1573 } else {
1574 path->quadTo(pts[1], pts[2]);
1575 }
1576}
1577
1578static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1579 int level = 2) {
1580 if (--level >= 0) {
1581 SkPoint tmp[7];
1582
1583 SkChopCubicAtHalf(pts, tmp);
1584 subdivide_cubic_to(path, &tmp[0], level);
1585 subdivide_cubic_to(path, &tmp[3], level);
1586 } else {
1587 path->cubicTo(pts[1], pts[2], pts[3]);
1588 }
1589}
1590
1591void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1592 SkDEBUGCODE(this->validate();)
1593 if (dst == NULL) {
1594 dst = (SkPath*)this;
1595 }
1596
tomhudson@google.com8d430182011-06-06 19:11:19 +00001597 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598 SkPath tmp;
1599 tmp.fFillType = fFillType;
1600
1601 SkPath::Iter iter(*this, false);
1602 SkPoint pts[4];
1603 SkPath::Verb verb;
1604
reed@google.com4a3b7142012-05-16 17:16:46 +00001605 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606 switch (verb) {
1607 case kMove_Verb:
1608 tmp.moveTo(pts[0]);
1609 break;
1610 case kLine_Verb:
1611 tmp.lineTo(pts[1]);
1612 break;
1613 case kQuad_Verb:
1614 subdivide_quad_to(&tmp, pts);
1615 break;
1616 case kCubic_Verb:
1617 subdivide_cubic_to(&tmp, pts);
1618 break;
1619 case kClose_Verb:
1620 tmp.close();
1621 break;
1622 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001623 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001624 break;
1625 }
1626 }
1627
1628 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001629 SkPathRef::Editor ed(&dst->fPathRef);
1630 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001631 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001632 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001633 /*
1634 * If we're not in perspective, we can transform all of the points at
1635 * once.
1636 *
1637 * Here we also want to optimize bounds, by noting if the bounds are
1638 * already known, and if so, we just transform those as well and mark
1639 * them as "known", rather than force the transformed path to have to
1640 * recompute them.
1641 *
1642 * Special gotchas if the path is effectively empty (<= 1 point) or
1643 * if it is non-finite. In those cases bounds need to stay empty,
1644 * regardless of the matrix.
1645 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001646 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001647 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001648 if (fIsFinite) {
1649 matrix.mapRect(&dst->fBounds, fBounds);
1650 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1651 dst->fBounds.setEmpty();
1652 }
1653 } else {
1654 dst->fIsFinite = false;
1655 dst->fBounds.setEmpty();
1656 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001657 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001658 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001659 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001660 }
1661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001662 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1663
reed@android.com8a1c16f2008-12-17 15:59:43 +00001664 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001665 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001666 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001667 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001669
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001670 if (kUnknown_Direction == fDirection) {
1671 dst->fDirection = kUnknown_Direction;
1672 } else {
1673 SkScalar det2x2 =
1674 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1675 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1676 if (det2x2 < 0) {
1677 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1678 } else if (det2x2 > 0) {
1679 dst->fDirection = fDirection;
1680 } else {
1681 dst->fDirection = kUnknown_Direction;
1682 }
1683 }
1684
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001685 // It's an oval only if it stays a rect.
1686 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001687
reed@android.com8a1c16f2008-12-17 15:59:43 +00001688 SkDEBUGCODE(dst->validate();)
1689 }
1690}
1691
reed@android.com8a1c16f2008-12-17 15:59:43 +00001692///////////////////////////////////////////////////////////////////////////////
1693///////////////////////////////////////////////////////////////////////////////
1694
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001695enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001696 kEmptyContour_SegmentState, // The current contour is empty. We may be
1697 // starting processing or we may have just
1698 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001699 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1700 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1701 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702};
1703
1704SkPath::Iter::Iter() {
1705#ifdef SK_DEBUG
1706 fPts = NULL;
1707 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001708 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001709 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001710#endif
1711 // need to init enough to make next() harmlessly return kDone_Verb
1712 fVerbs = NULL;
1713 fVerbStop = NULL;
1714 fNeedClose = false;
1715}
1716
1717SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1718 this->setPath(path, forceClose);
1719}
1720
1721void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001722 fPts = path.fPathRef->points();
1723 fVerbs = path.fPathRef->verbs();
1724 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001725 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001726 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001727 fForceClose = SkToU8(forceClose);
1728 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001729 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001730}
1731
1732bool SkPath::Iter::isClosedContour() const {
1733 if (fVerbs == NULL || fVerbs == fVerbStop) {
1734 return false;
1735 }
1736 if (fForceClose) {
1737 return true;
1738 }
1739
1740 const uint8_t* verbs = fVerbs;
1741 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001742
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001743 if (kMove_Verb == *(verbs - 1)) {
1744 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001745 }
1746
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001747 while (verbs > stop) {
1748 // verbs points one beyond the current verb, decrement first.
1749 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001750 if (kMove_Verb == v) {
1751 break;
1752 }
1753 if (kClose_Verb == v) {
1754 return true;
1755 }
1756 }
1757 return false;
1758}
1759
1760SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001761 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001762 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001763 // A special case: if both points are NaN, SkPoint::operation== returns
1764 // false, but the iterator expects that they are treated as the same.
1765 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001766 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1767 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001768 return kClose_Verb;
1769 }
1770
reed@google.com9e25dbf2012-05-15 17:05:38 +00001771 pts[0] = fLastPt;
1772 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773 fLastPt = fMoveTo;
1774 fCloseLine = true;
1775 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001776 } else {
1777 pts[0] = fMoveTo;
1778 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001779 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001780}
1781
reed@google.com9e25dbf2012-05-15 17:05:38 +00001782const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001783 if (fSegmentState == kAfterMove_SegmentState) {
1784 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001785 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001786 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001787 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001788 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1789 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001790 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001791 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001792}
1793
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001794void SkPath::Iter::consumeDegenerateSegments() {
1795 // We need to step over anything that will not move the current draw point
1796 // forward before the next move is seen
1797 const uint8_t* lastMoveVerb = 0;
1798 const SkPoint* lastMovePt = 0;
1799 SkPoint lastPt = fLastPt;
1800 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001801 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001802 switch (verb) {
1803 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001804 // Keep a record of this most recent move
1805 lastMoveVerb = fVerbs;
1806 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001807 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001808 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001809 fPts++;
1810 break;
1811
1812 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001813 // A close when we are in a segment is always valid except when it
1814 // follows a move which follows a segment.
1815 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001816 return;
1817 }
1818 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001819 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001820 break;
1821
1822 case kLine_Verb:
1823 if (!IsLineDegenerate(lastPt, fPts[0])) {
1824 if (lastMoveVerb) {
1825 fVerbs = lastMoveVerb;
1826 fPts = lastMovePt;
1827 return;
1828 }
1829 return;
1830 }
1831 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001832 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001833 fPts++;
1834 break;
1835
1836 case kQuad_Verb:
1837 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1838 if (lastMoveVerb) {
1839 fVerbs = lastMoveVerb;
1840 fPts = lastMovePt;
1841 return;
1842 }
1843 return;
1844 }
1845 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001846 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001847 fPts += 2;
1848 break;
1849
1850 case kCubic_Verb:
1851 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1852 if (lastMoveVerb) {
1853 fVerbs = lastMoveVerb;
1854 fPts = lastMovePt;
1855 return;
1856 }
1857 return;
1858 }
1859 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001860 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001861 fPts += 3;
1862 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001863
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001864 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001865 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001866 }
1867 }
1868}
1869
reed@google.com4a3b7142012-05-16 17:16:46 +00001870SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001871 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001872
reed@android.com8a1c16f2008-12-17 15:59:43 +00001873 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001874 // Close the curve if requested and if there is some curve to close
1875 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001876 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001877 return kLine_Verb;
1878 }
1879 fNeedClose = false;
1880 return kClose_Verb;
1881 }
1882 return kDone_Verb;
1883 }
1884
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001885 // fVerbs is one beyond the current verb, decrement first
1886 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001887 const SkPoint* SK_RESTRICT srcPts = fPts;
1888 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001889
1890 switch (verb) {
1891 case kMove_Verb:
1892 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001893 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001894 verb = this->autoClose(pts);
1895 if (verb == kClose_Verb) {
1896 fNeedClose = false;
1897 }
1898 return (Verb)verb;
1899 }
1900 if (fVerbs == fVerbStop) { // might be a trailing moveto
1901 return kDone_Verb;
1902 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001903 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001904 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001905 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001906 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001907 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001908 fNeedClose = fForceClose;
1909 break;
1910 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001911 pts[0] = this->cons_moveTo();
1912 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001913 fLastPt = srcPts[0];
1914 fCloseLine = false;
1915 srcPts += 1;
1916 break;
1917 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001918 pts[0] = this->cons_moveTo();
1919 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001920 fLastPt = srcPts[1];
1921 srcPts += 2;
1922 break;
1923 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001924 pts[0] = this->cons_moveTo();
1925 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001926 fLastPt = srcPts[2];
1927 srcPts += 3;
1928 break;
1929 case kClose_Verb:
1930 verb = this->autoClose(pts);
1931 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001932 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001933 } else {
1934 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001935 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001936 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001937 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001938 break;
1939 }
1940 fPts = srcPts;
1941 return (Verb)verb;
1942}
1943
1944///////////////////////////////////////////////////////////////////////////////
1945
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001946SkPath::RawIter::RawIter() {
1947#ifdef SK_DEBUG
1948 fPts = NULL;
1949 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1950#endif
1951 // need to init enough to make next() harmlessly return kDone_Verb
1952 fVerbs = NULL;
1953 fVerbStop = NULL;
1954}
1955
1956SkPath::RawIter::RawIter(const SkPath& path) {
1957 this->setPath(path);
1958}
1959
1960void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001961 fPts = path.fPathRef->points();
1962 fVerbs = path.fPathRef->verbs();
1963 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001964 fMoveTo.fX = fMoveTo.fY = 0;
1965 fLastPt.fX = fLastPt.fY = 0;
1966}
1967
1968SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001969 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001970 if (fVerbs == fVerbStop) {
1971 return kDone_Verb;
1972 }
1973
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001974 // fVerbs points one beyond next verb so decrement first.
1975 unsigned verb = *(--fVerbs);
1976 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001977
1978 switch (verb) {
1979 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001980 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001981 fMoveTo = srcPts[0];
1982 fLastPt = fMoveTo;
1983 srcPts += 1;
1984 break;
1985 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001986 pts[0] = fLastPt;
1987 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001988 fLastPt = srcPts[0];
1989 srcPts += 1;
1990 break;
1991 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001992 pts[0] = fLastPt;
1993 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001994 fLastPt = srcPts[1];
1995 srcPts += 2;
1996 break;
1997 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001998 pts[0] = fLastPt;
1999 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002000 fLastPt = srcPts[2];
2001 srcPts += 3;
2002 break;
2003 case kClose_Verb:
2004 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002005 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002006 break;
2007 }
2008 fPts = srcPts;
2009 return (Verb)verb;
2010}
2011
2012///////////////////////////////////////////////////////////////////////////////
2013
reed@android.com8a1c16f2008-12-17 15:59:43 +00002014/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002015 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002016*/
2017
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002018uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002019 SkDEBUGCODE(this->validate();)
2020
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002021 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002022 const int byteCount = sizeof(int32_t)
2023#if NEW_PICTURE_FORMAT
2024 + fPathRef->writeSize()
2025#else
2026 + 2 * sizeof(int32_t)
2027 + sizeof(SkPoint) * fPathRef->countPoints()
2028 + sizeof(uint8_t) * fPathRef->countVerbs()
2029#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002030 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002031 return SkAlign4(byteCount);
2032 }
2033
2034 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002035#if !NEW_PICTURE_FORMAT
2036 buffer.write32(fPathRef->countPoints());
2037 buffer.write32(fPathRef->countVerbs());
2038#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002039
2040 // Call getBounds() to ensure (as a side-effect) that fBounds
2041 // and fIsFinite are computed.
2042 const SkRect& bounds = this->getBounds();
2043 SkASSERT(!fBoundsIsDirty);
2044
2045 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
2046 ((fIsOval & 1) << kIsOval_SerializationShift) |
2047 (fConvexity << kConvexity_SerializationShift) |
2048 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002049 (fSegmentMask << kSegmentMask_SerializationShift) |
2050 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002051
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002052 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002053
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002054 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002055
2056 buffer.write(&bounds, sizeof(bounds));
2057
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002058 buffer.padToAlign4();
2059 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002060}
2061
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002062uint32_t SkPath::readFromMemory(const void* storage) {
2063 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002064#if !NEW_PICTURE_FORMAT
2065 int32_t pcount = buffer.readS32();
2066 int32_t vcount = buffer.readS32();
2067#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002068
reed@google.com98b11f12011-09-21 18:40:27 +00002069 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002070 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2071 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2072 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2073 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002074 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
2075 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002076
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002077#if NEW_PICTURE_FORMAT
2078 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
2079#else
2080 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
2081#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002082
2083 buffer.read(&fBounds, sizeof(fBounds));
2084 fBoundsIsDirty = false;
2085
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002086 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002087
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002088 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002089
2090 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002091 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002092}
2093
2094///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002095
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096void SkPath::dump(bool forceClose, const char title[]) const {
2097 Iter iter(*this, forceClose);
2098 SkPoint pts[4];
2099 Verb verb;
2100
2101 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2102 title ? title : "");
2103
reed@google.com4a3b7142012-05-16 17:16:46 +00002104 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002105 switch (verb) {
2106 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002107 SkDebugf(" path: moveTo [%g %g]\n",
2108 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002109 break;
2110 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002111 SkDebugf(" path: lineTo [%g %g]\n",
2112 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002113 break;
2114 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002115 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
2116 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2117 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002118 break;
2119 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002120 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
2121 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2122 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
2123 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002124 break;
2125 case kClose_Verb:
2126 SkDebugf(" path: close\n");
2127 break;
2128 default:
2129 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2130 verb = kDone_Verb; // stop the loop
2131 break;
2132 }
2133 }
2134 SkDebugf("path: done %s\n", title ? title : "");
2135}
2136
reed@android.come522ca52009-11-23 20:10:41 +00002137void SkPath::dump() const {
2138 this->dump(false);
2139}
2140
2141#ifdef SK_DEBUG
2142void SkPath::validate() const {
2143 SkASSERT(this != NULL);
2144 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002145
djsollen@google.com077348c2012-10-22 20:23:32 +00002146#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002147 if (!fBoundsIsDirty) {
2148 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002149
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002150 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002151 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002152
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002153 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002154 // if we're empty, fBounds may be empty but translated, so we can't
2155 // necessarily compare to bounds directly
2156 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2157 // be [2, 2, 2, 2]
2158 SkASSERT(bounds.isEmpty());
2159 SkASSERT(fBounds.isEmpty());
2160 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002161 if (bounds.isEmpty()) {
2162 SkASSERT(fBounds.isEmpty());
2163 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002164 if (!fBounds.isEmpty()) {
2165 SkASSERT(fBounds.contains(bounds));
2166 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002167 }
reed@android.come522ca52009-11-23 20:10:41 +00002168 }
2169 }
reed@google.com10296cc2011-09-21 12:29:05 +00002170
2171 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002172 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2173 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2174 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002175 case kLine_Verb:
2176 mask |= kLine_SegmentMask;
2177 break;
2178 case kQuad_Verb:
2179 mask |= kQuad_SegmentMask;
2180 break;
2181 case kCubic_Verb:
2182 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002183 case kMove_Verb: // these verbs aren't included in the segment mask.
2184 case kClose_Verb:
2185 break;
2186 case kDone_Verb:
2187 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2188 break;
2189 default:
2190 SkDEBUGFAIL("Unknown Verb");
2191 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002192 }
2193 }
2194 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002195#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002196}
djsollen@google.com077348c2012-10-22 20:23:32 +00002197#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002198
reed@google.com04863fa2011-05-15 04:08:24 +00002199///////////////////////////////////////////////////////////////////////////////
2200
reed@google.com0b7b9822011-05-16 12:29:27 +00002201static int sign(SkScalar x) { return x < 0; }
2202#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002203
reed@google.com04863fa2011-05-15 04:08:24 +00002204static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002205 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002206}
2207
2208// only valid for a single contour
2209struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002210 Convexicator()
2211 : fPtCount(0)
2212 , fConvexity(SkPath::kConvex_Convexity)
2213 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002214 fSign = 0;
2215 // warnings
2216 fCurrPt.set(0, 0);
2217 fVec0.set(0, 0);
2218 fVec1.set(0, 0);
2219 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002220
2221 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002222 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002223 }
2224
2225 SkPath::Convexity getConvexity() const { return fConvexity; }
2226
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002227 /** The direction returned is only valid if the path is determined convex */
2228 SkPath::Direction getDirection() const { return fDirection; }
2229
reed@google.com04863fa2011-05-15 04:08:24 +00002230 void addPt(const SkPoint& pt) {
2231 if (SkPath::kConcave_Convexity == fConvexity) {
2232 return;
2233 }
2234
2235 if (0 == fPtCount) {
2236 fCurrPt = pt;
2237 ++fPtCount;
2238 } else {
2239 SkVector vec = pt - fCurrPt;
2240 if (vec.fX || vec.fY) {
2241 fCurrPt = pt;
2242 if (++fPtCount == 2) {
2243 fFirstVec = fVec1 = vec;
2244 } else {
2245 SkASSERT(fPtCount > 2);
2246 this->addVec(vec);
2247 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002248
reed@google.com85b6e392011-05-15 20:25:17 +00002249 int sx = sign(vec.fX);
2250 int sy = sign(vec.fY);
2251 fDx += (sx != fSx);
2252 fDy += (sy != fSy);
2253 fSx = sx;
2254 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002255
reed@google.com85b6e392011-05-15 20:25:17 +00002256 if (fDx > 3 || fDy > 3) {
2257 fConvexity = SkPath::kConcave_Convexity;
2258 }
reed@google.com04863fa2011-05-15 04:08:24 +00002259 }
2260 }
2261 }
2262
2263 void close() {
2264 if (fPtCount > 2) {
2265 this->addVec(fFirstVec);
2266 }
2267 }
2268
2269private:
2270 void addVec(const SkVector& vec) {
2271 SkASSERT(vec.fX || vec.fY);
2272 fVec0 = fVec1;
2273 fVec1 = vec;
2274 int sign = CrossProductSign(fVec0, fVec1);
2275 if (0 == fSign) {
2276 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002277 if (1 == sign) {
2278 fDirection = SkPath::kCW_Direction;
2279 } else if (-1 == sign) {
2280 fDirection = SkPath::kCCW_Direction;
2281 }
reed@google.com04863fa2011-05-15 04:08:24 +00002282 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002283 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002284 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002285 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002286 }
2287 }
2288 }
2289
2290 SkPoint fCurrPt;
2291 SkVector fVec0, fVec1, fFirstVec;
2292 int fPtCount; // non-degenerate points
2293 int fSign;
2294 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002295 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002296 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002297};
2298
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002299SkPath::Convexity SkPath::internalGetConvexity() const {
2300 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002301 SkPoint pts[4];
2302 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002303 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002304
2305 int contourCount = 0;
2306 int count;
2307 Convexicator state;
2308
2309 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2310 switch (verb) {
2311 case kMove_Verb:
2312 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002313 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002314 return kConcave_Convexity;
2315 }
2316 pts[1] = pts[0];
2317 count = 1;
2318 break;
2319 case kLine_Verb: count = 1; break;
2320 case kQuad_Verb: count = 2; break;
2321 case kCubic_Verb: count = 3; break;
2322 case kClose_Verb:
2323 state.close();
2324 count = 0;
2325 break;
2326 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002327 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002328 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002329 return kConcave_Convexity;
2330 }
2331
2332 for (int i = 1; i <= count; i++) {
2333 state.addPt(pts[i]);
2334 }
2335 // early exit
2336 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002337 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002338 return kConcave_Convexity;
2339 }
2340 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002341 fConvexity = state.getConvexity();
2342 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2343 fDirection = state.getDirection();
2344 }
2345 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002346}
reed@google.com69a99432012-01-10 18:00:10 +00002347
2348///////////////////////////////////////////////////////////////////////////////
2349
2350class ContourIter {
2351public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002352 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002353
2354 bool done() const { return fDone; }
2355 // if !done() then these may be called
2356 int count() const { return fCurrPtCount; }
2357 const SkPoint* pts() const { return fCurrPt; }
2358 void next();
2359
2360private:
2361 int fCurrPtCount;
2362 const SkPoint* fCurrPt;
2363 const uint8_t* fCurrVerb;
2364 const uint8_t* fStopVerbs;
2365 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002366 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002367};
2368
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002369ContourIter::ContourIter(const SkPathRef& pathRef) {
2370 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002371 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002372 fCurrPt = pathRef.points();
2373 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002374 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002375 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002376 this->next();
2377}
2378
2379void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002380 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002381 fDone = true;
2382 }
2383 if (fDone) {
2384 return;
2385 }
2386
2387 // skip pts of prev contour
2388 fCurrPt += fCurrPtCount;
2389
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002390 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002391 int ptCount = 1; // moveTo
2392 const uint8_t* verbs = fCurrVerb;
2393
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002394 for (--verbs; verbs > fStopVerbs; --verbs) {
2395 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002396 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002397 goto CONTOUR_END;
2398 case SkPath::kLine_Verb:
2399 ptCount += 1;
2400 break;
2401 case SkPath::kQuad_Verb:
2402 ptCount += 2;
2403 break;
2404 case SkPath::kCubic_Verb:
2405 ptCount += 3;
2406 break;
2407 default: // kClose_Verb, just keep going
2408 break;
2409 }
2410 }
2411CONTOUR_END:
2412 fCurrPtCount = ptCount;
2413 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002414 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002415}
2416
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002417// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002418static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002419 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2420 // We may get 0 when the above subtracts underflow. We expect this to be
2421 // very rare and lazily promote to double.
2422 if (0 == cross) {
2423 double p0x = SkScalarToDouble(p0.fX);
2424 double p0y = SkScalarToDouble(p0.fY);
2425
2426 double p1x = SkScalarToDouble(p1.fX);
2427 double p1y = SkScalarToDouble(p1.fY);
2428
2429 double p2x = SkScalarToDouble(p2.fX);
2430 double p2y = SkScalarToDouble(p2.fY);
2431
2432 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2433 (p1y - p0y) * (p2x - p0x));
2434
2435 }
2436 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002437}
2438
reed@google.comc1ea60a2012-01-31 15:15:36 +00002439// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002440static int find_max_y(const SkPoint pts[], int count) {
2441 SkASSERT(count > 0);
2442 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002443 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002444 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002445 SkScalar y = pts[i].fY;
2446 if (y > max) {
2447 max = y;
2448 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002449 }
2450 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002451 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002452}
2453
reed@google.comcabaf1d2012-01-11 21:03:05 +00002454static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2455 int i = index;
2456 for (;;) {
2457 i = (i + inc) % n;
2458 if (i == index) { // we wrapped around, so abort
2459 break;
2460 }
2461 if (pts[index] != pts[i]) { // found a different point, success!
2462 break;
2463 }
2464 }
2465 return i;
2466}
2467
reed@google.comc1ea60a2012-01-31 15:15:36 +00002468/**
2469 * Starting at index, and moving forward (incrementing), find the xmin and
2470 * xmax of the contiguous points that have the same Y.
2471 */
2472static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2473 int* maxIndexPtr) {
2474 const SkScalar y = pts[index].fY;
2475 SkScalar min = pts[index].fX;
2476 SkScalar max = min;
2477 int minIndex = index;
2478 int maxIndex = index;
2479 for (int i = index + 1; i < n; ++i) {
2480 if (pts[i].fY != y) {
2481 break;
2482 }
2483 SkScalar x = pts[i].fX;
2484 if (x < min) {
2485 min = x;
2486 minIndex = i;
2487 } else if (x > max) {
2488 max = x;
2489 maxIndex = i;
2490 }
2491 }
2492 *maxIndexPtr = maxIndex;
2493 return minIndex;
2494}
2495
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002496static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002497 if (dir) {
2498 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2499 }
reed@google.comac8543f2012-01-30 20:51:25 +00002500}
2501
reed@google.comc1ea60a2012-01-31 15:15:36 +00002502#if 0
2503#include "SkString.h"
2504#include "../utils/SkParsePath.h"
2505static void dumpPath(const SkPath& path) {
2506 SkString str;
2507 SkParsePath::ToSVGString(path, &str);
2508 SkDebugf("%s\n", str.c_str());
2509}
2510#endif
2511
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002512namespace {
2513// for use with convex_dir_test
2514double mul(double a, double b) { return a * b; }
2515SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2516double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2517SkScalar toScalar(SkScalar a) { return a; }
2518
2519// determines the winding direction of a convex polygon with the precision
2520// of T. CAST_SCALAR casts an SkScalar to T.
2521template <typename T, T (CAST_SCALAR)(SkScalar)>
2522bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2523 // we find the first three points that form a non-degenerate
2524 // triangle. If there are no such points then the path is
2525 // degenerate. The first is always point 0. Now we find the second
2526 // point.
2527 int i = 0;
2528 enum { kX = 0, kY = 1 };
2529 T v0[2];
2530 while (1) {
2531 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2532 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2533 if (v0[kX] || v0[kY]) {
2534 break;
2535 }
2536 if (++i == n - 1) {
2537 return false;
2538 }
2539 }
2540 // now find a third point that is not colinear with the first two
2541 // points and check the orientation of the triangle (which will be
2542 // the same as the orientation of the path).
2543 for (++i; i < n; ++i) {
2544 T v1[2];
2545 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2546 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2547 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2548 if (0 != cross) {
2549 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2550 return true;
2551 }
2552 }
2553 return false;
2554}
2555}
2556
reed@google.comac8543f2012-01-30 20:51:25 +00002557/*
2558 * We loop through all contours, and keep the computed cross-product of the
2559 * contour that contained the global y-max. If we just look at the first
2560 * contour, we may find one that is wound the opposite way (correctly) since
2561 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2562 * that is outer most (or at least has the global y-max) before we can consider
2563 * its cross product.
2564 */
reed@google.com69a99432012-01-10 18:00:10 +00002565bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002566// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002567 // don't want to pay the cost for computing this if it
2568 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002569
2570 if (kUnknown_Direction != fDirection) {
2571 *dir = static_cast<Direction>(fDirection);
2572 return true;
2573 }
reed@google.com69a99432012-01-10 18:00:10 +00002574 const Convexity conv = this->getConvexityOrUnknown();
2575
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002576 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002577
reed@google.comac8543f2012-01-30 20:51:25 +00002578 // initialize with our logical y-min
2579 SkScalar ymax = this->getBounds().fTop;
2580 SkScalar ymaxCross = 0;
2581
reed@google.com69a99432012-01-10 18:00:10 +00002582 for (; !iter.done(); iter.next()) {
2583 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002584 if (n < 3) {
2585 continue;
2586 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002587
reed@google.comcabaf1d2012-01-11 21:03:05 +00002588 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002589 SkScalar cross = 0;
2590 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002591 // We try first at scalar precision, and then again at double
2592 // precision. This is because the vectors computed between distant
2593 // points may lose too much precision.
2594 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002595 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002596 return true;
2597 }
2598 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002599 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002600 return true;
2601 } else {
2602 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002603 }
2604 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002605 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002606 if (pts[index].fY < ymax) {
2607 continue;
2608 }
2609
reed@google.comc1ea60a2012-01-31 15:15:36 +00002610 // If there is more than 1 distinct point at the y-max, we take the
2611 // x-min and x-max of them and just subtract to compute the dir.
2612 if (pts[(index + 1) % n].fY == pts[index].fY) {
2613 int maxIndex;
2614 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002615 if (minIndex == maxIndex) {
2616 goto TRY_CROSSPROD;
2617 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002618 SkASSERT(pts[minIndex].fY == pts[index].fY);
2619 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2620 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2621 // we just subtract the indices, and let that auto-convert to
2622 // SkScalar, since we just want - or + to signal the direction.
2623 cross = minIndex - maxIndex;
2624 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002625 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002626 // Find a next and prev index to use for the cross-product test,
2627 // but we try to find pts that form non-zero vectors from pts[index]
2628 //
2629 // Its possible that we can't find two non-degenerate vectors, so
2630 // we have to guard our search (e.g. all the pts could be in the
2631 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002632
reed@google.comc1ea60a2012-01-31 15:15:36 +00002633 // we pass n - 1 instead of -1 so we don't foul up % operator by
2634 // passing it a negative LH argument.
2635 int prev = find_diff_pt(pts, index, n, n - 1);
2636 if (prev == index) {
2637 // completely degenerate, skip to next contour
2638 continue;
2639 }
2640 int next = find_diff_pt(pts, index, n, 1);
2641 SkASSERT(next != index);
2642 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002643 // 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 +00002644 // x-direction. We really should continue to walk away from the degeneracy until
2645 // there is a divergence.
2646 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002647 // construct the subtract so we get the correct Direction below
2648 cross = pts[index].fX - pts[next].fX;
2649 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002650 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002651
reed@google.comac8543f2012-01-30 20:51:25 +00002652 if (cross) {
2653 // record our best guess so far
2654 ymax = pts[index].fY;
2655 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002656 }
reed@google.com69a99432012-01-10 18:00:10 +00002657 }
2658 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002659 if (ymaxCross) {
2660 crossToDir(ymaxCross, dir);
2661 fDirection = *dir;
2662 return true;
2663 } else {
2664 return false;
2665 }
reed@google.comac8543f2012-01-30 20:51:25 +00002666}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002667
2668///////////////////////////////////////////////////////////////////////////////
2669
2670static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2671 SkScalar D, SkScalar t) {
2672 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2673}
2674
2675static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2676 SkScalar t) {
2677 SkScalar A = c3 + 3*(c1 - c2) - c0;
2678 SkScalar B = 3*(c2 - c1 - c1 + c0);
2679 SkScalar C = 3*(c1 - c0);
2680 SkScalar D = c0;
2681 return eval_cubic_coeff(A, B, C, D, t);
2682}
2683
2684/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2685 t value such that cubic(t) = target
2686 */
2687static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2688 SkScalar target, SkScalar* t) {
2689 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2690 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002691
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002692 SkScalar D = c0 - target;
2693 SkScalar A = c3 + 3*(c1 - c2) - c0;
2694 SkScalar B = 3*(c2 - c1 - c1 + c0);
2695 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002696
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002697 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2698 SkScalar minT = 0;
2699 SkScalar maxT = SK_Scalar1;
2700 SkScalar mid;
2701 int i;
2702 for (i = 0; i < 16; i++) {
2703 mid = SkScalarAve(minT, maxT);
2704 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2705 if (delta < 0) {
2706 minT = mid;
2707 delta = -delta;
2708 } else {
2709 maxT = mid;
2710 }
2711 if (delta < TOLERANCE) {
2712 break;
2713 }
2714 }
2715 *t = mid;
2716 return true;
2717}
2718
2719template <size_t N> static void find_minmax(const SkPoint pts[],
2720 SkScalar* minPtr, SkScalar* maxPtr) {
2721 SkScalar min, max;
2722 min = max = pts[0].fX;
2723 for (size_t i = 1; i < N; ++i) {
2724 min = SkMinScalar(min, pts[i].fX);
2725 max = SkMaxScalar(max, pts[i].fX);
2726 }
2727 *minPtr = min;
2728 *maxPtr = max;
2729}
2730
2731static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2732 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002733
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002734 int dir = 1;
2735 if (pts[0].fY > pts[3].fY) {
2736 storage[0] = pts[3];
2737 storage[1] = pts[2];
2738 storage[2] = pts[1];
2739 storage[3] = pts[0];
2740 pts = storage;
2741 dir = -1;
2742 }
2743 if (y < pts[0].fY || y >= pts[3].fY) {
2744 return 0;
2745 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002746
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002747 // quickreject or quickaccept
2748 SkScalar min, max;
2749 find_minmax<4>(pts, &min, &max);
2750 if (x < min) {
2751 return 0;
2752 }
2753 if (x > max) {
2754 return dir;
2755 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002756
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002757 // compute the actual x(t) value
2758 SkScalar t, xt;
2759 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2760 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2761 } else {
2762 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2763 xt = y < mid ? pts[0].fX : pts[3].fX;
2764 }
2765 return xt < x ? dir : 0;
2766}
2767
2768static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2769 SkPoint dst[10];
2770 int n = SkChopCubicAtYExtrema(pts, dst);
2771 int w = 0;
2772 for (int i = 0; i <= n; ++i) {
2773 w += winding_mono_cubic(&dst[i * 3], x, y);
2774 }
2775 return w;
2776}
2777
2778static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2779 SkScalar y0 = pts[0].fY;
2780 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002781
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002782 int dir = 1;
2783 if (y0 > y2) {
2784 SkTSwap(y0, y2);
2785 dir = -1;
2786 }
2787 if (y < y0 || y >= y2) {
2788 return 0;
2789 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002790
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002791 // bounds check on X (not required. is it faster?)
2792#if 0
2793 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2794 return 0;
2795 }
2796#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002797
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002798 SkScalar roots[2];
2799 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2800 2 * (pts[1].fY - pts[0].fY),
2801 pts[0].fY - y,
2802 roots);
2803 SkASSERT(n <= 1);
2804 SkScalar xt;
2805 if (0 == n) {
2806 SkScalar mid = SkScalarAve(y0, y2);
2807 // Need [0] and [2] if dir == 1
2808 // and [2] and [0] if dir == -1
2809 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2810 } else {
2811 SkScalar t = roots[0];
2812 SkScalar C = pts[0].fX;
2813 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2814 SkScalar B = 2 * (pts[1].fX - C);
2815 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2816 }
2817 return xt < x ? dir : 0;
2818}
2819
2820static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2821 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2822 if (y0 == y1) {
2823 return true;
2824 }
2825 if (y0 < y1) {
2826 return y1 <= y2;
2827 } else {
2828 return y1 >= y2;
2829 }
2830}
2831
2832static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2833 SkPoint dst[5];
2834 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002835
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002836 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2837 n = SkChopQuadAtYExtrema(pts, dst);
2838 pts = dst;
2839 }
2840 int w = winding_mono_quad(pts, x, y);
2841 if (n > 0) {
2842 w += winding_mono_quad(&pts[2], x, y);
2843 }
2844 return w;
2845}
2846
2847static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2848 SkScalar x0 = pts[0].fX;
2849 SkScalar y0 = pts[0].fY;
2850 SkScalar x1 = pts[1].fX;
2851 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002852
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002853 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002854
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002855 int dir = 1;
2856 if (y0 > y1) {
2857 SkTSwap(y0, y1);
2858 dir = -1;
2859 }
2860 if (y < y0 || y >= y1) {
2861 return 0;
2862 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002863
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002864 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2865 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002866
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002867 if (SkScalarSignAsInt(cross) == dir) {
2868 dir = 0;
2869 }
2870 return dir;
2871}
2872
2873bool SkPath::contains(SkScalar x, SkScalar y) const {
2874 bool isInverse = this->isInverseFillType();
2875 if (this->isEmpty()) {
2876 return isInverse;
2877 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002878
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002879 const SkRect& bounds = this->getBounds();
2880 if (!bounds.contains(x, y)) {
2881 return isInverse;
2882 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002883
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002884 SkPath::Iter iter(*this, true);
2885 bool done = false;
2886 int w = 0;
2887 do {
2888 SkPoint pts[4];
2889 switch (iter.next(pts, false)) {
2890 case SkPath::kMove_Verb:
2891 case SkPath::kClose_Verb:
2892 break;
2893 case SkPath::kLine_Verb:
2894 w += winding_line(pts, x, y);
2895 break;
2896 case SkPath::kQuad_Verb:
2897 w += winding_quad(pts, x, y);
2898 break;
2899 case SkPath::kCubic_Verb:
2900 w += winding_cubic(pts, x, y);
2901 break;
2902 case SkPath::kDone_Verb:
2903 done = true;
2904 break;
2905 }
2906 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002907
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002908 switch (this->getFillType()) {
2909 case SkPath::kEvenOdd_FillType:
2910 case SkPath::kInverseEvenOdd_FillType:
2911 w &= 1;
2912 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002913 default:
2914 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002915 }
2916 return SkToBool(w);
2917}
2918