blob: 3b1b5186369d47ade7de375bdc87c73a5a769fce [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
462 1: horizontal right
463 2: vertical down
464 3: horizontal left
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 */
491bool SkPath::isRect(SkRect* rect) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000492 SkDEBUGCODE(this->validate();)
reed@google.comabf15c12011-01-18 20:35:51 +0000493
caryclark@google.comf1316942011-07-26 19:54:45 +0000494 int corners = 0;
495 SkPoint first, last;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000496 first.set(0, 0);
497 last.set(0, 0);
498 int firstDirection = 0;
499 int lastDirection = 0;
500 int nextDirection = 0;
501 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000502 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000503 const SkPoint* pts = fPathRef->points();
504 int verbCnt = fPathRef->countVerbs();
505 int currVerb = 0;
506 while (currVerb < verbCnt) {
507 switch (fPathRef->atVerb(currVerb++)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000508 case kClose_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000509 pts = fPathRef->points();
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 }
536 closedOrMoved = autoClose;
537 if (lastDirection != nextDirection) {
538 if (++corners > 4) {
539 return false; // too many direction changes
540 }
541 }
542 last = pts[-1];
543 if (lastDirection == nextDirection) {
544 break; // colinear segment
545 }
546 // Possible values for corners are 2, 3, and 4.
547 // When corners == 3, nextDirection opposes firstDirection.
548 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000549 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000550 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
551 if ((directionCycle ^ turn) != nextDirection) {
552 return false; // direction didn't follow cycle
553 }
554 break;
555 }
556 case kQuad_Verb:
557 case kCubic_Verb:
558 return false; // quadratic, cubic not allowed
559 case kMove_Verb:
560 last = *pts++;
561 closedOrMoved = true;
562 break;
563 }
564 lastDirection = nextDirection;
565 }
566 // Success if 4 corners and first point equals last
567 bool result = 4 == corners && first == last;
568 if (result && rect) {
569 *rect = getBounds();
570 }
571 return result;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000572}
573
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000574int SkPath::countPoints() const {
575 return fPathRef->countPoints();
576}
577
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000578int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000579 SkDEBUGCODE(this->validate();)
580
581 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000582 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000583 int count = SkMin32(max, fPathRef->countPoints());
584 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
585 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000586}
587
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000588SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000589 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
590 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000591 }
592 return SkPoint::Make(0, 0);
593}
594
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000595int SkPath::countVerbs() const {
596 return fPathRef->countVerbs();
597}
598
599static inline void copy_verbs_reverse(uint8_t* inorderDst,
600 const uint8_t* reversedSrc,
601 int count) {
602 for (int i = 0; i < count; ++i) {
603 inorderDst[i] = reversedSrc[~i];
604 }
605}
606
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000607int SkPath::getVerbs(uint8_t dst[], int max) const {
608 SkDEBUGCODE(this->validate();)
609
610 SkASSERT(max >= 0);
611 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000612 int count = SkMin32(max, fPathRef->countVerbs());
613 copy_verbs_reverse(dst, fPathRef->verbs(), count);
614 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000615}
616
reed@google.com294dd7b2011-10-11 11:58:32 +0000617bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000618 SkDEBUGCODE(this->validate();)
619
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000620 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000621 if (count > 0) {
622 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000623 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000624 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000625 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000626 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000627 if (lastPt) {
628 lastPt->set(0, 0);
629 }
630 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000631}
632
633void SkPath::setLastPt(SkScalar x, SkScalar y) {
634 SkDEBUGCODE(this->validate();)
635
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000636 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000637 if (count == 0) {
638 this->moveTo(x, y);
639 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000640 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000641 SkPathRef::Editor ed(&fPathRef);
642 ed.atPoint(count-1)->set(x, y);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000643 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000644 }
645}
646
reed@android.comd252db02009-04-01 18:31:44 +0000647void SkPath::computeBounds() const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000648 SkDEBUGCODE(this->validate();)
reed@android.comd252db02009-04-01 18:31:44 +0000649 SkASSERT(fBoundsIsDirty);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000650
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000651 fIsFinite = compute_pt_bounds(&fBounds, *fPathRef.get());
djsollen@google.comc9ab9872012-08-29 18:52:07 +0000652 fBoundsIsDirty = false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653}
654
reed@google.com04863fa2011-05-15 04:08:24 +0000655void SkPath::setConvexity(Convexity c) {
656 if (fConvexity != c) {
657 fConvexity = c;
658 GEN_ID_INC;
659 }
660}
661
reed@android.com8a1c16f2008-12-17 15:59:43 +0000662//////////////////////////////////////////////////////////////////////////////
663// Construction methods
664
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000665#define DIRTY_AFTER_EDIT \
666 do { \
667 fBoundsIsDirty = true; \
668 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000669 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000670 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000671 } while (0)
672
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000673#define DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE \
674 do { \
675 fBoundsIsDirty = true; \
bsalomon@google.comf3bf18b2012-02-02 15:00:19 +0000676 } while (0)
677
reed@android.com8a1c16f2008-12-17 15:59:43 +0000678void SkPath::incReserve(U16CPU inc) {
679 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000680 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000681 SkDEBUGCODE(this->validate();)
682}
683
684void SkPath::moveTo(SkScalar x, SkScalar y) {
685 SkDEBUGCODE(this->validate();)
686
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000687 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000688
reed@google.comd335d1d2012-01-12 18:17:11 +0000689 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000690 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000691
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000692 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000693
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000694 GEN_ID_INC;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000695 DIRTY_AFTER_EDIT_NO_CONVEXITY_OR_DIRECTION_CHANGE;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000696}
697
698void SkPath::rMoveTo(SkScalar x, SkScalar y) {
699 SkPoint pt;
700 this->getLastPt(&pt);
701 this->moveTo(pt.fX + x, pt.fY + y);
702}
703
reed@google.comd335d1d2012-01-12 18:17:11 +0000704void SkPath::injectMoveToIfNeeded() {
705 if (fLastMoveToIndex < 0) {
706 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000707 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000708 x = y = 0;
709 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000710 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000711 x = pt.fX;
712 y = pt.fY;
713 }
714 this->moveTo(x, y);
715 }
716}
717
reed@android.com8a1c16f2008-12-17 15:59:43 +0000718void SkPath::lineTo(SkScalar x, SkScalar y) {
719 SkDEBUGCODE(this->validate();)
720
reed@google.comd335d1d2012-01-12 18:17:11 +0000721 this->injectMoveToIfNeeded();
722
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000723 SkPathRef::Editor ed(&fPathRef);
724 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000725 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000726
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000727 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000728 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000729}
730
731void SkPath::rLineTo(SkScalar x, SkScalar y) {
732 SkPoint pt;
733 this->getLastPt(&pt);
734 this->lineTo(pt.fX + x, pt.fY + y);
735}
736
737void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
738 SkDEBUGCODE(this->validate();)
739
reed@google.comd335d1d2012-01-12 18:17:11 +0000740 this->injectMoveToIfNeeded();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000741
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000742 SkPathRef::Editor ed(&fPathRef);
743 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000744 pts[0].set(x1, y1);
745 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000746 fSegmentMask |= kQuad_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000747
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000748 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000749 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000750}
751
752void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
753 SkPoint pt;
754 this->getLastPt(&pt);
755 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
756}
757
758void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
759 SkScalar x3, SkScalar y3) {
760 SkDEBUGCODE(this->validate();)
761
reed@google.comd335d1d2012-01-12 18:17:11 +0000762 this->injectMoveToIfNeeded();
763
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000764 SkPathRef::Editor ed(&fPathRef);
765 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000766 pts[0].set(x1, y1);
767 pts[1].set(x2, y2);
768 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000769 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000770
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000771 GEN_ID_INC;
reed@google.comb54455e2011-05-16 14:16:04 +0000772 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000773}
774
775void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
776 SkScalar x3, SkScalar y3) {
777 SkPoint pt;
778 this->getLastPt(&pt);
779 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
780 pt.fX + x3, pt.fY + y3);
781}
782
783void SkPath::close() {
784 SkDEBUGCODE(this->validate();)
785
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000786 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000787 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000788 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000789 case kLine_Verb:
790 case kQuad_Verb:
791 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000792 case kMove_Verb: {
793 SkPathRef::Editor ed(&fPathRef);
794 ed.growForVerb(kClose_Verb);
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000795 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000796 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000797 }
reed@android.com8a1c16f2008-12-17 15:59:43 +0000798 default:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000799 // don't add a close if it's the first verb or a repeat
reed@android.com8a1c16f2008-12-17 15:59:43 +0000800 break;
801 }
802 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000803
804 // signal that we need a moveTo to follow us (unless we're done)
805#if 0
806 if (fLastMoveToIndex >= 0) {
807 fLastMoveToIndex = ~fLastMoveToIndex;
808 }
809#else
810 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
811#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000812}
813
814///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000815
reed@android.com8a1c16f2008-12-17 15:59:43 +0000816void SkPath::addRect(const SkRect& rect, Direction dir) {
817 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
818}
819
820void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
821 SkScalar bottom, Direction dir) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000822 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
823 SkAutoDisableDirectionCheck addc(this);
824
reed@android.com8a1c16f2008-12-17 15:59:43 +0000825 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
826
827 this->incReserve(5);
828
829 this->moveTo(left, top);
830 if (dir == kCCW_Direction) {
831 this->lineTo(left, bottom);
832 this->lineTo(right, bottom);
833 this->lineTo(right, top);
834 } else {
835 this->lineTo(right, top);
836 this->lineTo(right, bottom);
837 this->lineTo(left, bottom);
838 }
839 this->close();
840}
841
reed@google.com744faba2012-05-29 19:54:52 +0000842void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
843 SkDEBUGCODE(this->validate();)
844 if (count <= 0) {
845 return;
846 }
847
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000848 SkPathRef::Editor ed(&fPathRef);
849 fLastMoveToIndex = ed.pathRef()->countPoints();
850 uint8_t* vb;
851 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000852 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000853 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000854
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000855 memcpy(p, pts, count * sizeof(SkPoint));
856 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000857 if (count > 1) {
858 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
859 // be 0, the compiler will remove the test/branch entirely.
860 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000861 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000862 } else {
863 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000864 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000865 }
866 }
867 fSegmentMask |= kLine_SegmentMask;
868 }
869 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000870 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000871 }
872
873 GEN_ID_INC;
874 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000875 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000876}
877
reed@android.com8a1c16f2008-12-17 15:59:43 +0000878#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
879
880void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
881 Direction dir) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000882 SkScalar w = rect.width();
883 SkScalar halfW = SkScalarHalf(w);
884 SkScalar h = rect.height();
885 SkScalar halfH = SkScalarHalf(h);
886
887 if (halfW <= 0 || halfH <= 0) {
888 return;
889 }
890
reed@google.comabf15c12011-01-18 20:35:51 +0000891 bool skip_hori = rx >= halfW;
892 bool skip_vert = ry >= halfH;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000893
894 if (skip_hori && skip_vert) {
895 this->addOval(rect, dir);
896 return;
897 }
reed@google.comabf15c12011-01-18 20:35:51 +0000898
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000899 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
900
reed@google.comabf15c12011-01-18 20:35:51 +0000901 SkAutoPathBoundsUpdate apbu(this, rect);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000902 SkAutoDisableDirectionCheck(this);
reed@google.comabf15c12011-01-18 20:35:51 +0000903
reed@android.com8a1c16f2008-12-17 15:59:43 +0000904 if (skip_hori) {
905 rx = halfW;
906 } else if (skip_vert) {
907 ry = halfH;
908 }
909
910 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
911 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
912
913 this->incReserve(17);
914 this->moveTo(rect.fRight - rx, rect.fTop);
915 if (dir == kCCW_Direction) {
916 if (!skip_hori) {
917 this->lineTo(rect.fLeft + rx, rect.fTop); // top
918 }
919 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
920 rect.fLeft, rect.fTop + ry - sy,
921 rect.fLeft, rect.fTop + ry); // top-left
922 if (!skip_vert) {
923 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
924 }
925 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
926 rect.fLeft + rx - sx, rect.fBottom,
927 rect.fLeft + rx, rect.fBottom); // bot-left
928 if (!skip_hori) {
929 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
930 }
931 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
932 rect.fRight, rect.fBottom - ry + sy,
933 rect.fRight, rect.fBottom - ry); // bot-right
934 if (!skip_vert) {
935 this->lineTo(rect.fRight, rect.fTop + ry);
936 }
937 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
938 rect.fRight - rx + sx, rect.fTop,
939 rect.fRight - rx, rect.fTop); // top-right
940 } else {
941 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
942 rect.fRight, rect.fTop + ry - sy,
943 rect.fRight, rect.fTop + ry); // top-right
944 if (!skip_vert) {
945 this->lineTo(rect.fRight, rect.fBottom - ry);
946 }
947 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
948 rect.fRight - rx + sx, rect.fBottom,
949 rect.fRight - rx, rect.fBottom); // bot-right
950 if (!skip_hori) {
951 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
952 }
953 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
954 rect.fLeft, rect.fBottom - ry + sy,
955 rect.fLeft, rect.fBottom - ry); // bot-left
956 if (!skip_vert) {
957 this->lineTo(rect.fLeft, rect.fTop + ry); // left
958 }
959 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
960 rect.fLeft + rx - sx, rect.fTop,
961 rect.fLeft + rx, rect.fTop); // top-left
962 if (!skip_hori) {
963 this->lineTo(rect.fRight - rx, rect.fTop); // top
964 }
965 }
966 this->close();
967}
968
969static void add_corner_arc(SkPath* path, const SkRect& rect,
970 SkScalar rx, SkScalar ry, int startAngle,
971 SkPath::Direction dir, bool forceMoveTo) {
972 rx = SkMinScalar(SkScalarHalf(rect.width()), rx);
973 ry = SkMinScalar(SkScalarHalf(rect.height()), ry);
reed@google.comabf15c12011-01-18 20:35:51 +0000974
reed@android.com8a1c16f2008-12-17 15:59:43 +0000975 SkRect r;
976 r.set(-rx, -ry, rx, ry);
977
978 switch (startAngle) {
979 case 0:
980 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
981 break;
982 case 90:
983 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
984 break;
985 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
986 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000987 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000988 }
reed@google.comabf15c12011-01-18 20:35:51 +0000989
reed@android.com8a1c16f2008-12-17 15:59:43 +0000990 SkScalar start = SkIntToScalar(startAngle);
991 SkScalar sweep = SkIntToScalar(90);
992 if (SkPath::kCCW_Direction == dir) {
993 start += sweep;
994 sweep = -sweep;
995 }
reed@google.comabf15c12011-01-18 20:35:51 +0000996
reed@android.com8a1c16f2008-12-17 15:59:43 +0000997 path->arcTo(r, start, sweep, forceMoveTo);
998}
999
1000void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
1001 Direction dir) {
reed@google.com44b2c732011-01-18 20:55:57 +00001002 // abort before we invoke SkAutoPathBoundsUpdate()
1003 if (rect.isEmpty()) {
1004 return;
1005 }
1006
reed@android.com8a1c16f2008-12-17 15:59:43 +00001007 SkAutoPathBoundsUpdate apbu(this, rect);
1008
1009 if (kCW_Direction == dir) {
1010 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
1011 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
1012 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
1013 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
1014 } else {
1015 add_corner_arc(this, rect, rad[0], rad[1], 180, dir, true);
1016 add_corner_arc(this, rect, rad[6], rad[7], 90, dir, false);
1017 add_corner_arc(this, rect, rad[4], rad[5], 0, dir, false);
1018 add_corner_arc(this, rect, rad[2], rad[3], 270, dir, false);
1019 }
1020 this->close();
1021}
1022
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001023bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001024 int count = fPathRef->countVerbs();
1025 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1026 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001027 if (*verbs == kLine_Verb ||
1028 *verbs == kQuad_Verb ||
1029 *verbs == kCubic_Verb) {
1030 return false;
1031 }
1032 ++verbs;
1033 }
1034 return true;
1035}
1036
reed@android.com8a1c16f2008-12-17 15:59:43 +00001037void SkPath::addOval(const SkRect& oval, Direction dir) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001038 /* If addOval() is called after previous moveTo(),
1039 this path is still marked as an oval. This is used to
1040 fit into WebKit's calling sequences.
1041 We can't simply check isEmpty() in this case, as additional
1042 moveTo() would mark the path non empty.
1043 */
1044 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001045 if (fIsOval) {
1046 fDirection = dir;
1047 } else {
1048 fDirection = kUnknown_Direction;
1049 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001050
1051 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001052 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001053
reed@android.com8a1c16f2008-12-17 15:59:43 +00001054 SkAutoPathBoundsUpdate apbu(this, oval);
1055
1056 SkScalar cx = oval.centerX();
1057 SkScalar cy = oval.centerY();
1058 SkScalar rx = SkScalarHalf(oval.width());
1059 SkScalar ry = SkScalarHalf(oval.height());
1060#if 0 // these seem faster than using quads (1/2 the number of edges)
1061 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1062 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
1063
1064 this->incReserve(13);
1065 this->moveTo(cx + rx, cy);
1066 if (dir == kCCW_Direction) {
1067 this->cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry);
1068 this->cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy);
1069 this->cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry);
1070 this->cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy);
1071 } else {
1072 this->cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry);
1073 this->cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy);
1074 this->cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry);
1075 this->cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy);
1076 }
1077#else
1078 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1079 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1080 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1081 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1082
1083 /*
1084 To handle imprecision in computing the center and radii, we revert to
1085 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1086 to ensure that we don't exceed the oval's bounds *ever*, since we want
1087 to use oval for our fast-bounds, rather than have to recompute it.
1088 */
1089 const SkScalar L = oval.fLeft; // cx - rx
1090 const SkScalar T = oval.fTop; // cy - ry
1091 const SkScalar R = oval.fRight; // cx + rx
1092 const SkScalar B = oval.fBottom; // cy + ry
1093
1094 this->incReserve(17); // 8 quads + close
1095 this->moveTo(R, cy);
1096 if (dir == kCCW_Direction) {
1097 this->quadTo( R, cy - sy, cx + mx, cy - my);
1098 this->quadTo(cx + sx, T, cx , T);
1099 this->quadTo(cx - sx, T, cx - mx, cy - my);
1100 this->quadTo( L, cy - sy, L, cy );
1101 this->quadTo( L, cy + sy, cx - mx, cy + my);
1102 this->quadTo(cx - sx, B, cx , B);
1103 this->quadTo(cx + sx, B, cx + mx, cy + my);
1104 this->quadTo( R, cy + sy, R, cy );
1105 } else {
1106 this->quadTo( R, cy + sy, cx + mx, cy + my);
1107 this->quadTo(cx + sx, B, cx , B);
1108 this->quadTo(cx - sx, B, cx - mx, cy + my);
1109 this->quadTo( L, cy + sy, L, cy );
1110 this->quadTo( L, cy - sy, cx - mx, cy - my);
1111 this->quadTo(cx - sx, T, cx , T);
1112 this->quadTo(cx + sx, T, cx + mx, cy - my);
1113 this->quadTo( R, cy - sy, R, cy );
1114 }
1115#endif
1116 this->close();
1117}
1118
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001119bool SkPath::isOval(SkRect* rect) const {
1120 if (fIsOval && rect) {
1121 *rect = getBounds();
1122 }
1123
1124 return fIsOval;
1125}
1126
reed@android.com8a1c16f2008-12-17 15:59:43 +00001127void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1128 if (r > 0) {
1129 SkRect rect;
1130 rect.set(x - r, y - r, x + r, y + r);
1131 this->addOval(rect, dir);
1132 }
1133}
1134
1135#include "SkGeometry.h"
1136
1137static int build_arc_points(const SkRect& oval, SkScalar startAngle,
1138 SkScalar sweepAngle,
1139 SkPoint pts[kSkBuildQuadArcStorage]) {
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001140
rmistry@google.comfbfcd562012-08-23 18:09:54 +00001141 if (0 == sweepAngle &&
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001142 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
1143 // Chrome uses this path to move into and out of ovals. If not
1144 // treated as a special case the moves can distort the oval's
1145 // bounding box (and break the circle special case).
1146 pts[0].set(oval.fRight, oval.centerY());
1147 return 1;
robertphillips@google.com158618e2012-10-23 16:56:56 +00001148 } else if (0 == oval.width() && 0 == oval.height()) {
1149 // Chrome will sometimes create 0 radius round rects. Having degenerate
skia.committer@gmail.com1e34ff72012-10-24 02:01:24 +00001150 // quad segments in the path prevents the path from being recognized as
robertphillips@google.com158618e2012-10-23 16:56:56 +00001151 // a rect.
1152 // TODO: optimizing the case where only one of width or height is zero
1153 // should also be considered. This case, however, doesn't seem to be
1154 // as common as the single point case.
1155 pts[0].set(oval.fRight, oval.fTop);
1156 return 1;
robertphillips@google.com17bb4582012-08-20 17:24:16 +00001157 }
1158
reed@android.com8a1c16f2008-12-17 15:59:43 +00001159 SkVector start, stop;
1160
1161 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
1162 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
1163 &stop.fX);
reed@android.comeebf5cb2010-02-09 18:30:59 +00001164
1165 /* If the sweep angle is nearly (but less than) 360, then due to precision
1166 loss in radians-conversion and/or sin/cos, we may end up with coincident
1167 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
1168 of drawing a nearly complete circle (good).
1169 e.g. canvas.drawArc(0, 359.99, ...)
1170 -vs- canvas.drawArc(0, 359.9, ...)
1171 We try to detect this edge case, and tweak the stop vector
1172 */
1173 if (start == stop) {
1174 SkScalar sw = SkScalarAbs(sweepAngle);
1175 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
1176 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
1177 // make a guess at a tiny angle (in radians) to tweak by
1178 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
1179 // not sure how much will be enough, so we use a loop
1180 do {
1181 stopRad -= deltaRad;
1182 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
1183 } while (start == stop);
1184 }
1185 }
1186
reed@android.com8a1c16f2008-12-17 15:59:43 +00001187 SkMatrix matrix;
reed@google.comabf15c12011-01-18 20:35:51 +00001188
reed@android.com8a1c16f2008-12-17 15:59:43 +00001189 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
1190 matrix.postTranslate(oval.centerX(), oval.centerY());
reed@google.comabf15c12011-01-18 20:35:51 +00001191
reed@android.com8a1c16f2008-12-17 15:59:43 +00001192 return SkBuildQuadArc(start, stop,
1193 sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection,
1194 &matrix, pts);
1195}
1196
1197void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1198 bool forceMoveTo) {
1199 if (oval.width() < 0 || oval.height() < 0) {
1200 return;
1201 }
1202
1203 SkPoint pts[kSkBuildQuadArcStorage];
1204 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1205 SkASSERT((count & 1) == 1);
1206
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001207 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001208 forceMoveTo = true;
1209 }
1210 this->incReserve(count);
1211 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1212 for (int i = 1; i < count; i += 2) {
1213 this->quadTo(pts[i], pts[i+1]);
1214 }
1215}
1216
1217void SkPath::addArc(const SkRect& oval, SkScalar startAngle,
1218 SkScalar sweepAngle) {
1219 if (oval.isEmpty() || 0 == sweepAngle) {
1220 return;
1221 }
1222
1223 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1224
1225 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1226 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1227 return;
1228 }
1229
1230 SkPoint pts[kSkBuildQuadArcStorage];
1231 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1232
1233 this->incReserve(count);
1234 this->moveTo(pts[0]);
1235 for (int i = 1; i < count; i += 2) {
1236 this->quadTo(pts[i], pts[i+1]);
1237 }
1238}
1239
1240/*
1241 Need to handle the case when the angle is sharp, and our computed end-points
1242 for the arc go behind pt1 and/or p2...
1243*/
1244void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1245 SkScalar radius) {
1246 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001247
reed@android.com8a1c16f2008-12-17 15:59:43 +00001248 // need to know our prev pt so we can construct tangent vectors
1249 {
1250 SkPoint start;
1251 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001252 // Handle degenerate cases by adding a line to the first point and
1253 // bailing out.
1254 if ((x1 == start.fX && y1 == start.fY) ||
1255 (x1 == x2 && y1 == y2) ||
1256 radius == 0) {
1257 this->lineTo(x1, y1);
1258 return;
1259 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001260 before.setNormalize(x1 - start.fX, y1 - start.fY);
1261 after.setNormalize(x2 - x1, y2 - y1);
1262 }
reed@google.comabf15c12011-01-18 20:35:51 +00001263
reed@android.com8a1c16f2008-12-17 15:59:43 +00001264 SkScalar cosh = SkPoint::DotProduct(before, after);
1265 SkScalar sinh = SkPoint::CrossProduct(before, after);
1266
1267 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001268 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001269 return;
1270 }
reed@google.comabf15c12011-01-18 20:35:51 +00001271
reed@android.com8a1c16f2008-12-17 15:59:43 +00001272 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1273 if (dist < 0) {
1274 dist = -dist;
1275 }
1276
1277 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1278 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1279 SkRotationDirection arcDir;
1280
1281 // now turn before/after into normals
1282 if (sinh > 0) {
1283 before.rotateCCW();
1284 after.rotateCCW();
1285 arcDir = kCW_SkRotationDirection;
1286 } else {
1287 before.rotateCW();
1288 after.rotateCW();
1289 arcDir = kCCW_SkRotationDirection;
1290 }
1291
1292 SkMatrix matrix;
1293 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001294
reed@android.com8a1c16f2008-12-17 15:59:43 +00001295 matrix.setScale(radius, radius);
1296 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1297 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001298
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001300
reed@android.com8a1c16f2008-12-17 15:59:43 +00001301 this->incReserve(count);
1302 // [xx,yy] == pts[0]
1303 this->lineTo(xx, yy);
1304 for (int i = 1; i < count; i += 2) {
1305 this->quadTo(pts[i], pts[i+1]);
1306 }
1307}
1308
1309///////////////////////////////////////////////////////////////////////////////
1310
1311void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1312 SkMatrix matrix;
1313
1314 matrix.setTranslate(dx, dy);
1315 this->addPath(path, matrix);
1316}
1317
1318void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001319 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001320
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001321 fIsOval = false;
1322
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001323 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001324 SkPoint pts[4];
1325 Verb verb;
1326
1327 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1328
1329 while ((verb = iter.next(pts)) != kDone_Verb) {
1330 switch (verb) {
1331 case kMove_Verb:
1332 proc(matrix, &pts[0], &pts[0], 1);
1333 this->moveTo(pts[0]);
1334 break;
1335 case kLine_Verb:
1336 proc(matrix, &pts[1], &pts[1], 1);
1337 this->lineTo(pts[1]);
1338 break;
1339 case kQuad_Verb:
1340 proc(matrix, &pts[1], &pts[1], 2);
1341 this->quadTo(pts[1], pts[2]);
1342 break;
1343 case kCubic_Verb:
1344 proc(matrix, &pts[1], &pts[1], 3);
1345 this->cubicTo(pts[1], pts[2], pts[3]);
1346 break;
1347 case kClose_Verb:
1348 this->close();
1349 break;
1350 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001351 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001352 }
1353 }
1354}
1355
1356///////////////////////////////////////////////////////////////////////////////
1357
1358static const uint8_t gPtsInVerb[] = {
1359 1, // kMove
1360 1, // kLine
1361 2, // kQuad
1362 3, // kCubic
1363 0, // kClose
1364 0 // kDone
1365};
1366
1367// ignore the initial moveto, and stop when the 1st contour ends
1368void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001369 int i, vcount = path.fPathRef->countVerbs();
1370 // exit early if the path is empty, or just has a moveTo.
1371 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001372 return;
1373 }
1374
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001375 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001376
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001377 fIsOval = false;
1378
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001379 const uint8_t* verbs = path.fPathRef->verbs();
1380 // skip the initial moveTo
1381 const SkPoint* pts = path.fPathRef->points() + 1;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001382
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001383 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001384 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001385 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001386 case kLine_Verb:
1387 this->lineTo(pts[0].fX, pts[0].fY);
1388 break;
1389 case kQuad_Verb:
1390 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1391 break;
1392 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001393 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 +00001394 break;
1395 case kClose_Verb:
1396 return;
1397 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001398 pts += gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399 }
1400}
1401
1402// ignore the last point of the 1st contour
1403void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001404 int i, vcount = path.fPathRef->countVerbs();
1405 // exit early if the path is empty, or just has a moveTo.
1406 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 return;
1408 }
1409
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001410 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001411
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001412 fIsOval = false;
1413
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001414 const uint8_t* verbs = path.fPathRef->verbs();
1415 const SkPoint* pts = path.fPathRef->points();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001416
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001417 SkASSERT(verbs[~0] == kMove_Verb);
1418 for (i = 1; i < vcount; ++i) {
1419 int n = gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001420 if (n == 0) {
1421 break;
1422 }
1423 pts += n;
1424 }
1425
1426 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001427 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001428 case kLine_Verb:
1429 this->lineTo(pts[-1].fX, pts[-1].fY);
1430 break;
1431 case kQuad_Verb:
1432 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1433 break;
1434 case kCubic_Verb:
1435 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1436 pts[-3].fX, pts[-3].fY);
1437 break;
1438 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001439 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001440 break;
1441 }
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001442 pts -= gPtsInVerb[verbs[~i]];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001443 }
1444}
1445
reed@google.com63d73742012-01-10 15:33:12 +00001446void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001447 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001448
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001449 const SkPoint* pts = src.fPathRef->pointsEnd();
1450 // we will iterator through src's verbs backwards
1451 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1452 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com63d73742012-01-10 15:33:12 +00001453
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001454 fIsOval = false;
1455
reed@google.com63d73742012-01-10 15:33:12 +00001456 bool needMove = true;
1457 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001458 while (verbs < verbsEnd) {
1459 uint8_t v = *(verbs++);
reed@google.com63d73742012-01-10 15:33:12 +00001460 int n = gPtsInVerb[v];
1461
1462 if (needMove) {
1463 --pts;
1464 this->moveTo(pts->fX, pts->fY);
1465 needMove = false;
1466 }
1467 pts -= n;
1468 switch (v) {
1469 case kMove_Verb:
1470 if (needClose) {
1471 this->close();
1472 needClose = false;
1473 }
1474 needMove = true;
1475 pts += 1; // so we see the point in "if (needMove)" above
1476 break;
1477 case kLine_Verb:
1478 this->lineTo(pts[0]);
1479 break;
1480 case kQuad_Verb:
1481 this->quadTo(pts[1], pts[0]);
1482 break;
1483 case kCubic_Verb:
1484 this->cubicTo(pts[2], pts[1], pts[0]);
1485 break;
1486 case kClose_Verb:
1487 needClose = true;
1488 break;
1489 default:
1490 SkASSERT(!"unexpected verb");
1491 }
1492 }
1493}
1494
reed@android.com8a1c16f2008-12-17 15:59:43 +00001495///////////////////////////////////////////////////////////////////////////////
1496
1497void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1498 SkMatrix matrix;
1499
1500 matrix.setTranslate(dx, dy);
1501 this->transform(matrix, dst);
1502}
1503
1504#include "SkGeometry.h"
1505
1506static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1507 int level = 2) {
1508 if (--level >= 0) {
1509 SkPoint tmp[5];
1510
1511 SkChopQuadAtHalf(pts, tmp);
1512 subdivide_quad_to(path, &tmp[0], level);
1513 subdivide_quad_to(path, &tmp[2], level);
1514 } else {
1515 path->quadTo(pts[1], pts[2]);
1516 }
1517}
1518
1519static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1520 int level = 2) {
1521 if (--level >= 0) {
1522 SkPoint tmp[7];
1523
1524 SkChopCubicAtHalf(pts, tmp);
1525 subdivide_cubic_to(path, &tmp[0], level);
1526 subdivide_cubic_to(path, &tmp[3], level);
1527 } else {
1528 path->cubicTo(pts[1], pts[2], pts[3]);
1529 }
1530}
1531
1532void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1533 SkDEBUGCODE(this->validate();)
1534 if (dst == NULL) {
1535 dst = (SkPath*)this;
1536 }
1537
tomhudson@google.com8d430182011-06-06 19:11:19 +00001538 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001539 SkPath tmp;
1540 tmp.fFillType = fFillType;
1541
1542 SkPath::Iter iter(*this, false);
1543 SkPoint pts[4];
1544 SkPath::Verb verb;
1545
reed@google.com4a3b7142012-05-16 17:16:46 +00001546 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001547 switch (verb) {
1548 case kMove_Verb:
1549 tmp.moveTo(pts[0]);
1550 break;
1551 case kLine_Verb:
1552 tmp.lineTo(pts[1]);
1553 break;
1554 case kQuad_Verb:
1555 subdivide_quad_to(&tmp, pts);
1556 break;
1557 case kCubic_Verb:
1558 subdivide_cubic_to(&tmp, pts);
1559 break;
1560 case kClose_Verb:
1561 tmp.close();
1562 break;
1563 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001564 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001565 break;
1566 }
1567 }
1568
1569 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001570 SkPathRef::Editor ed(&dst->fPathRef);
1571 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001572 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001573 } else {
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001574 /*
1575 * If we're not in perspective, we can transform all of the points at
1576 * once.
1577 *
1578 * Here we also want to optimize bounds, by noting if the bounds are
1579 * already known, and if so, we just transform those as well and mark
1580 * them as "known", rather than force the transformed path to have to
1581 * recompute them.
1582 *
1583 * Special gotchas if the path is effectively empty (<= 1 point) or
1584 * if it is non-finite. In those cases bounds need to stay empty,
1585 * regardless of the matrix.
1586 */
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001587 if (!fBoundsIsDirty && matrix.rectStaysRect() && fPathRef->countPoints() > 1) {
reed@android.comd252db02009-04-01 18:31:44 +00001588 dst->fBoundsIsDirty = false;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00001589 if (fIsFinite) {
1590 matrix.mapRect(&dst->fBounds, fBounds);
1591 if (!(dst->fIsFinite = dst->fBounds.isFinite())) {
1592 dst->fBounds.setEmpty();
1593 }
1594 } else {
1595 dst->fIsFinite = false;
1596 dst->fBounds.setEmpty();
1597 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598 } else {
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00001599 GEN_ID_PTR_INC(dst);
reed@android.comd252db02009-04-01 18:31:44 +00001600 dst->fBoundsIsDirty = true;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001601 }
1602
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001603 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1604
reed@android.com8a1c16f2008-12-17 15:59:43 +00001605 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001606 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001607 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001608 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001609 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001610
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001611 if (kUnknown_Direction == fDirection) {
1612 dst->fDirection = kUnknown_Direction;
1613 } else {
1614 SkScalar det2x2 =
1615 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1616 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1617 if (det2x2 < 0) {
1618 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1619 } else if (det2x2 > 0) {
1620 dst->fDirection = fDirection;
1621 } else {
1622 dst->fDirection = kUnknown_Direction;
1623 }
1624 }
1625
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001626 // It's an oval only if it stays a rect.
1627 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001628
reed@android.com8a1c16f2008-12-17 15:59:43 +00001629 SkDEBUGCODE(dst->validate();)
1630 }
1631}
1632
reed@android.com8a1c16f2008-12-17 15:59:43 +00001633///////////////////////////////////////////////////////////////////////////////
1634///////////////////////////////////////////////////////////////////////////////
1635
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001636enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001637 kEmptyContour_SegmentState, // The current contour is empty. We may be
1638 // starting processing or we may have just
1639 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001640 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1641 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1642 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001643};
1644
1645SkPath::Iter::Iter() {
1646#ifdef SK_DEBUG
1647 fPts = NULL;
1648 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001649 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001650 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001651#endif
1652 // need to init enough to make next() harmlessly return kDone_Verb
1653 fVerbs = NULL;
1654 fVerbStop = NULL;
1655 fNeedClose = false;
1656}
1657
1658SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1659 this->setPath(path, forceClose);
1660}
1661
1662void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001663 fPts = path.fPathRef->points();
1664 fVerbs = path.fPathRef->verbs();
1665 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001666 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001667 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001668 fForceClose = SkToU8(forceClose);
1669 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001670 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001671}
1672
1673bool SkPath::Iter::isClosedContour() const {
1674 if (fVerbs == NULL || fVerbs == fVerbStop) {
1675 return false;
1676 }
1677 if (fForceClose) {
1678 return true;
1679 }
1680
1681 const uint8_t* verbs = fVerbs;
1682 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001683
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001684 if (kMove_Verb == *(verbs - 1)) {
1685 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 }
1687
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001688 while (verbs > stop) {
1689 // verbs points one beyond the current verb, decrement first.
1690 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001691 if (kMove_Verb == v) {
1692 break;
1693 }
1694 if (kClose_Verb == v) {
1695 return true;
1696 }
1697 }
1698 return false;
1699}
1700
1701SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001702 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001703 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001704 // A special case: if both points are NaN, SkPoint::operation== returns
1705 // false, but the iterator expects that they are treated as the same.
1706 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001707 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1708 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001709 return kClose_Verb;
1710 }
1711
reed@google.com9e25dbf2012-05-15 17:05:38 +00001712 pts[0] = fLastPt;
1713 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001714 fLastPt = fMoveTo;
1715 fCloseLine = true;
1716 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001717 } else {
1718 pts[0] = fMoveTo;
1719 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001720 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001721}
1722
reed@google.com9e25dbf2012-05-15 17:05:38 +00001723const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001724 if (fSegmentState == kAfterMove_SegmentState) {
1725 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001726 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001727 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001728 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001729 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1730 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001731 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001732 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733}
1734
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001735void SkPath::Iter::consumeDegenerateSegments() {
1736 // We need to step over anything that will not move the current draw point
1737 // forward before the next move is seen
1738 const uint8_t* lastMoveVerb = 0;
1739 const SkPoint* lastMovePt = 0;
1740 SkPoint lastPt = fLastPt;
1741 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001742 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001743 switch (verb) {
1744 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001745 // Keep a record of this most recent move
1746 lastMoveVerb = fVerbs;
1747 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001748 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001749 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001750 fPts++;
1751 break;
1752
1753 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001754 // A close when we are in a segment is always valid except when it
1755 // follows a move which follows a segment.
1756 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001757 return;
1758 }
1759 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001760 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001761 break;
1762
1763 case kLine_Verb:
1764 if (!IsLineDegenerate(lastPt, fPts[0])) {
1765 if (lastMoveVerb) {
1766 fVerbs = lastMoveVerb;
1767 fPts = lastMovePt;
1768 return;
1769 }
1770 return;
1771 }
1772 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001773 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001774 fPts++;
1775 break;
1776
1777 case kQuad_Verb:
1778 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1779 if (lastMoveVerb) {
1780 fVerbs = lastMoveVerb;
1781 fPts = lastMovePt;
1782 return;
1783 }
1784 return;
1785 }
1786 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001787 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001788 fPts += 2;
1789 break;
1790
1791 case kCubic_Verb:
1792 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1793 if (lastMoveVerb) {
1794 fVerbs = lastMoveVerb;
1795 fPts = lastMovePt;
1796 return;
1797 }
1798 return;
1799 }
1800 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001801 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001802 fPts += 3;
1803 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001804
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001805 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001806 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001807 }
1808 }
1809}
1810
reed@google.com4a3b7142012-05-16 17:16:46 +00001811SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001812 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001813
reed@android.com8a1c16f2008-12-17 15:59:43 +00001814 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001815 // Close the curve if requested and if there is some curve to close
1816 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001817 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001818 return kLine_Verb;
1819 }
1820 fNeedClose = false;
1821 return kClose_Verb;
1822 }
1823 return kDone_Verb;
1824 }
1825
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001826 // fVerbs is one beyond the current verb, decrement first
1827 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001828 const SkPoint* SK_RESTRICT srcPts = fPts;
1829 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001830
1831 switch (verb) {
1832 case kMove_Verb:
1833 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001834 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001835 verb = this->autoClose(pts);
1836 if (verb == kClose_Verb) {
1837 fNeedClose = false;
1838 }
1839 return (Verb)verb;
1840 }
1841 if (fVerbs == fVerbStop) { // might be a trailing moveto
1842 return kDone_Verb;
1843 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001844 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001845 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001846 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001847 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001848 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001849 fNeedClose = fForceClose;
1850 break;
1851 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001852 pts[0] = this->cons_moveTo();
1853 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001854 fLastPt = srcPts[0];
1855 fCloseLine = false;
1856 srcPts += 1;
1857 break;
1858 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001859 pts[0] = this->cons_moveTo();
1860 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001861 fLastPt = srcPts[1];
1862 srcPts += 2;
1863 break;
1864 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001865 pts[0] = this->cons_moveTo();
1866 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001867 fLastPt = srcPts[2];
1868 srcPts += 3;
1869 break;
1870 case kClose_Verb:
1871 verb = this->autoClose(pts);
1872 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001873 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001874 } else {
1875 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001876 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001877 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001878 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001879 break;
1880 }
1881 fPts = srcPts;
1882 return (Verb)verb;
1883}
1884
1885///////////////////////////////////////////////////////////////////////////////
1886
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001887SkPath::RawIter::RawIter() {
1888#ifdef SK_DEBUG
1889 fPts = NULL;
1890 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1891#endif
1892 // need to init enough to make next() harmlessly return kDone_Verb
1893 fVerbs = NULL;
1894 fVerbStop = NULL;
1895}
1896
1897SkPath::RawIter::RawIter(const SkPath& path) {
1898 this->setPath(path);
1899}
1900
1901void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001902 fPts = path.fPathRef->points();
1903 fVerbs = path.fPathRef->verbs();
1904 fVerbStop = path.fPathRef->verbsMemBegin();
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001905 fMoveTo.fX = fMoveTo.fY = 0;
1906 fLastPt.fX = fLastPt.fY = 0;
1907}
1908
1909SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001910 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001911 if (fVerbs == fVerbStop) {
1912 return kDone_Verb;
1913 }
1914
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001915 // fVerbs points one beyond next verb so decrement first.
1916 unsigned verb = *(--fVerbs);
1917 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001918
1919 switch (verb) {
1920 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001921 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001922 fMoveTo = srcPts[0];
1923 fLastPt = fMoveTo;
1924 srcPts += 1;
1925 break;
1926 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001927 pts[0] = fLastPt;
1928 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001929 fLastPt = srcPts[0];
1930 srcPts += 1;
1931 break;
1932 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001933 pts[0] = fLastPt;
1934 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001935 fLastPt = srcPts[1];
1936 srcPts += 2;
1937 break;
1938 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001939 pts[0] = fLastPt;
1940 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001941 fLastPt = srcPts[2];
1942 srcPts += 3;
1943 break;
1944 case kClose_Verb:
1945 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00001946 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001947 break;
1948 }
1949 fPts = srcPts;
1950 return (Verb)verb;
1951}
1952
1953///////////////////////////////////////////////////////////////////////////////
1954
reed@android.com8a1c16f2008-12-17 15:59:43 +00001955/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001956 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00001957*/
1958
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001959uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001960 SkDEBUGCODE(this->validate();)
1961
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001962 if (NULL == storage) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001963 const int byteCount = sizeof(int32_t)
1964#if NEW_PICTURE_FORMAT
1965 + fPathRef->writeSize()
1966#else
1967 + 2 * sizeof(int32_t)
1968 + sizeof(SkPoint) * fPathRef->countPoints()
1969 + sizeof(uint8_t) * fPathRef->countVerbs()
1970#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001971 + sizeof(SkRect);
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001972 return SkAlign4(byteCount);
1973 }
1974
1975 SkWBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001976#if !NEW_PICTURE_FORMAT
1977 buffer.write32(fPathRef->countPoints());
1978 buffer.write32(fPathRef->countVerbs());
1979#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001980
1981 // Call getBounds() to ensure (as a side-effect) that fBounds
1982 // and fIsFinite are computed.
1983 const SkRect& bounds = this->getBounds();
1984 SkASSERT(!fBoundsIsDirty);
1985
1986 int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) |
1987 ((fIsOval & 1) << kIsOval_SerializationShift) |
1988 (fConvexity << kConvexity_SerializationShift) |
1989 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001990 (fSegmentMask << kSegmentMask_SerializationShift) |
1991 (fDirection << kDirection_SerializationShift);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001992
robertphillips@google.com2972bb52012-08-07 17:32:51 +00001993 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001994
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001995 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00001996
1997 buffer.write(&bounds, sizeof(bounds));
1998
djsollen@google.com94e75ee2012-06-08 18:30:46 +00001999 buffer.padToAlign4();
2000 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002001}
2002
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002003uint32_t SkPath::readFromMemory(const void* storage) {
2004 SkRBuffer buffer(storage);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002005#if !NEW_PICTURE_FORMAT
2006 int32_t pcount = buffer.readS32();
2007 int32_t vcount = buffer.readS32();
2008#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002009
reed@google.com98b11f12011-09-21 18:40:27 +00002010 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002011 fIsFinite = (packed >> kIsFinite_SerializationShift) & 1;
2012 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2013 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2014 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002015 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0x7;
2016 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002017
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002018#if NEW_PICTURE_FORMAT
2019 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer));
2020#else
2021 fPathRef.reset(SkPathRef::CreateFromBuffer(vcount, pcount, &buffer));
2022#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002023
2024 buffer.read(&fBounds, sizeof(fBounds));
2025 fBoundsIsDirty = false;
2026
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002027 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002028
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +00002029 GEN_ID_INC;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002030
2031 SkDEBUGCODE(this->validate();)
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002032 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002033}
2034
2035///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002036
reed@android.com8a1c16f2008-12-17 15:59:43 +00002037void SkPath::dump(bool forceClose, const char title[]) const {
2038 Iter iter(*this, forceClose);
2039 SkPoint pts[4];
2040 Verb verb;
2041
2042 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2043 title ? title : "");
2044
reed@google.com4a3b7142012-05-16 17:16:46 +00002045 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002046 switch (verb) {
2047 case kMove_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002048 SkDebugf(" path: moveTo [%g %g]\n",
2049 SkScalarToFloat(pts[0].fX), SkScalarToFloat(pts[0].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002050 break;
2051 case kLine_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002052 SkDebugf(" path: lineTo [%g %g]\n",
2053 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002054 break;
2055 case kQuad_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002056 SkDebugf(" path: quadTo [%g %g] [%g %g]\n",
2057 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2058 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002059 break;
2060 case kCubic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +00002061 SkDebugf(" path: cubeTo [%g %g] [%g %g] [%g %g]\n",
2062 SkScalarToFloat(pts[1].fX), SkScalarToFloat(pts[1].fY),
2063 SkScalarToFloat(pts[2].fX), SkScalarToFloat(pts[2].fY),
2064 SkScalarToFloat(pts[3].fX), SkScalarToFloat(pts[3].fY));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002065 break;
2066 case kClose_Verb:
2067 SkDebugf(" path: close\n");
2068 break;
2069 default:
2070 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2071 verb = kDone_Verb; // stop the loop
2072 break;
2073 }
2074 }
2075 SkDebugf("path: done %s\n", title ? title : "");
2076}
2077
reed@android.come522ca52009-11-23 20:10:41 +00002078void SkPath::dump() const {
2079 this->dump(false);
2080}
2081
2082#ifdef SK_DEBUG
2083void SkPath::validate() const {
2084 SkASSERT(this != NULL);
2085 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002086
djsollen@google.com077348c2012-10-22 20:23:32 +00002087#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002088 if (!fBoundsIsDirty) {
2089 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002090
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002091 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002092 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002093
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002094 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002095 // if we're empty, fBounds may be empty but translated, so we can't
2096 // necessarily compare to bounds directly
2097 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2098 // be [2, 2, 2, 2]
2099 SkASSERT(bounds.isEmpty());
2100 SkASSERT(fBounds.isEmpty());
2101 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002102 if (bounds.isEmpty()) {
2103 SkASSERT(fBounds.isEmpty());
2104 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002105 if (!fBounds.isEmpty()) {
2106 SkASSERT(fBounds.contains(bounds));
2107 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002108 }
reed@android.come522ca52009-11-23 20:10:41 +00002109 }
2110 }
reed@google.com10296cc2011-09-21 12:29:05 +00002111
2112 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002113 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2114 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2115 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002116 case kLine_Verb:
2117 mask |= kLine_SegmentMask;
2118 break;
2119 case kQuad_Verb:
2120 mask |= kQuad_SegmentMask;
2121 break;
2122 case kCubic_Verb:
2123 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002124 case kMove_Verb: // these verbs aren't included in the segment mask.
2125 case kClose_Verb:
2126 break;
2127 case kDone_Verb:
2128 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2129 break;
2130 default:
2131 SkDEBUGFAIL("Unknown Verb");
2132 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002133 }
2134 }
2135 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002136#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002137}
djsollen@google.com077348c2012-10-22 20:23:32 +00002138#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002139
reed@google.com04863fa2011-05-15 04:08:24 +00002140///////////////////////////////////////////////////////////////////////////////
2141
reed@google.com0b7b9822011-05-16 12:29:27 +00002142static int sign(SkScalar x) { return x < 0; }
2143#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002144
reed@google.com04863fa2011-05-15 04:08:24 +00002145static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002146 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002147}
2148
2149// only valid for a single contour
2150struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002151 Convexicator()
2152 : fPtCount(0)
2153 , fConvexity(SkPath::kConvex_Convexity)
2154 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002155 fSign = 0;
2156 // warnings
2157 fCurrPt.set(0, 0);
2158 fVec0.set(0, 0);
2159 fVec1.set(0, 0);
2160 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002161
2162 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002163 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002164 }
2165
2166 SkPath::Convexity getConvexity() const { return fConvexity; }
2167
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002168 /** The direction returned is only valid if the path is determined convex */
2169 SkPath::Direction getDirection() const { return fDirection; }
2170
reed@google.com04863fa2011-05-15 04:08:24 +00002171 void addPt(const SkPoint& pt) {
2172 if (SkPath::kConcave_Convexity == fConvexity) {
2173 return;
2174 }
2175
2176 if (0 == fPtCount) {
2177 fCurrPt = pt;
2178 ++fPtCount;
2179 } else {
2180 SkVector vec = pt - fCurrPt;
2181 if (vec.fX || vec.fY) {
2182 fCurrPt = pt;
2183 if (++fPtCount == 2) {
2184 fFirstVec = fVec1 = vec;
2185 } else {
2186 SkASSERT(fPtCount > 2);
2187 this->addVec(vec);
2188 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002189
reed@google.com85b6e392011-05-15 20:25:17 +00002190 int sx = sign(vec.fX);
2191 int sy = sign(vec.fY);
2192 fDx += (sx != fSx);
2193 fDy += (sy != fSy);
2194 fSx = sx;
2195 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002196
reed@google.com85b6e392011-05-15 20:25:17 +00002197 if (fDx > 3 || fDy > 3) {
2198 fConvexity = SkPath::kConcave_Convexity;
2199 }
reed@google.com04863fa2011-05-15 04:08:24 +00002200 }
2201 }
2202 }
2203
2204 void close() {
2205 if (fPtCount > 2) {
2206 this->addVec(fFirstVec);
2207 }
2208 }
2209
2210private:
2211 void addVec(const SkVector& vec) {
2212 SkASSERT(vec.fX || vec.fY);
2213 fVec0 = fVec1;
2214 fVec1 = vec;
2215 int sign = CrossProductSign(fVec0, fVec1);
2216 if (0 == fSign) {
2217 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002218 if (1 == sign) {
2219 fDirection = SkPath::kCW_Direction;
2220 } else if (-1 == sign) {
2221 fDirection = SkPath::kCCW_Direction;
2222 }
reed@google.com04863fa2011-05-15 04:08:24 +00002223 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002224 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002225 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002226 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002227 }
2228 }
2229 }
2230
2231 SkPoint fCurrPt;
2232 SkVector fVec0, fVec1, fFirstVec;
2233 int fPtCount; // non-degenerate points
2234 int fSign;
2235 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002236 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002237 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002238};
2239
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002240SkPath::Convexity SkPath::internalGetConvexity() const {
2241 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002242 SkPoint pts[4];
2243 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002244 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002245
2246 int contourCount = 0;
2247 int count;
2248 Convexicator state;
2249
2250 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2251 switch (verb) {
2252 case kMove_Verb:
2253 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002254 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002255 return kConcave_Convexity;
2256 }
2257 pts[1] = pts[0];
2258 count = 1;
2259 break;
2260 case kLine_Verb: count = 1; break;
2261 case kQuad_Verb: count = 2; break;
2262 case kCubic_Verb: count = 3; break;
2263 case kClose_Verb:
2264 state.close();
2265 count = 0;
2266 break;
2267 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002268 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002269 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002270 return kConcave_Convexity;
2271 }
2272
2273 for (int i = 1; i <= count; i++) {
2274 state.addPt(pts[i]);
2275 }
2276 // early exit
2277 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002278 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002279 return kConcave_Convexity;
2280 }
2281 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002282 fConvexity = state.getConvexity();
2283 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2284 fDirection = state.getDirection();
2285 }
2286 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002287}
reed@google.com69a99432012-01-10 18:00:10 +00002288
2289///////////////////////////////////////////////////////////////////////////////
2290
2291class ContourIter {
2292public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002293 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002294
2295 bool done() const { return fDone; }
2296 // if !done() then these may be called
2297 int count() const { return fCurrPtCount; }
2298 const SkPoint* pts() const { return fCurrPt; }
2299 void next();
2300
2301private:
2302 int fCurrPtCount;
2303 const SkPoint* fCurrPt;
2304 const uint8_t* fCurrVerb;
2305 const uint8_t* fStopVerbs;
2306 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002307 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002308};
2309
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002310ContourIter::ContourIter(const SkPathRef& pathRef) {
2311 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002312 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002313 fCurrPt = pathRef.points();
2314 fCurrVerb = pathRef.verbs();
reed@google.com69a99432012-01-10 18:00:10 +00002315 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002316 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002317 this->next();
2318}
2319
2320void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002321 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002322 fDone = true;
2323 }
2324 if (fDone) {
2325 return;
2326 }
2327
2328 // skip pts of prev contour
2329 fCurrPt += fCurrPtCount;
2330
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002331 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002332 int ptCount = 1; // moveTo
2333 const uint8_t* verbs = fCurrVerb;
2334
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002335 for (--verbs; verbs > fStopVerbs; --verbs) {
2336 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002337 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002338 goto CONTOUR_END;
2339 case SkPath::kLine_Verb:
2340 ptCount += 1;
2341 break;
2342 case SkPath::kQuad_Verb:
2343 ptCount += 2;
2344 break;
2345 case SkPath::kCubic_Verb:
2346 ptCount += 3;
2347 break;
2348 default: // kClose_Verb, just keep going
2349 break;
2350 }
2351 }
2352CONTOUR_END:
2353 fCurrPtCount = ptCount;
2354 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002355 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002356}
2357
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002358// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002359static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002360 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2361 // We may get 0 when the above subtracts underflow. We expect this to be
2362 // very rare and lazily promote to double.
2363 if (0 == cross) {
2364 double p0x = SkScalarToDouble(p0.fX);
2365 double p0y = SkScalarToDouble(p0.fY);
2366
2367 double p1x = SkScalarToDouble(p1.fX);
2368 double p1y = SkScalarToDouble(p1.fY);
2369
2370 double p2x = SkScalarToDouble(p2.fX);
2371 double p2y = SkScalarToDouble(p2.fY);
2372
2373 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2374 (p1y - p0y) * (p2x - p0x));
2375
2376 }
2377 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002378}
2379
reed@google.comc1ea60a2012-01-31 15:15:36 +00002380// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002381static int find_max_y(const SkPoint pts[], int count) {
2382 SkASSERT(count > 0);
2383 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002384 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002385 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002386 SkScalar y = pts[i].fY;
2387 if (y > max) {
2388 max = y;
2389 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002390 }
2391 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002392 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002393}
2394
reed@google.comcabaf1d2012-01-11 21:03:05 +00002395static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2396 int i = index;
2397 for (;;) {
2398 i = (i + inc) % n;
2399 if (i == index) { // we wrapped around, so abort
2400 break;
2401 }
2402 if (pts[index] != pts[i]) { // found a different point, success!
2403 break;
2404 }
2405 }
2406 return i;
2407}
2408
reed@google.comc1ea60a2012-01-31 15:15:36 +00002409/**
2410 * Starting at index, and moving forward (incrementing), find the xmin and
2411 * xmax of the contiguous points that have the same Y.
2412 */
2413static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2414 int* maxIndexPtr) {
2415 const SkScalar y = pts[index].fY;
2416 SkScalar min = pts[index].fX;
2417 SkScalar max = min;
2418 int minIndex = index;
2419 int maxIndex = index;
2420 for (int i = index + 1; i < n; ++i) {
2421 if (pts[i].fY != y) {
2422 break;
2423 }
2424 SkScalar x = pts[i].fX;
2425 if (x < min) {
2426 min = x;
2427 minIndex = i;
2428 } else if (x > max) {
2429 max = x;
2430 maxIndex = i;
2431 }
2432 }
2433 *maxIndexPtr = maxIndex;
2434 return minIndex;
2435}
2436
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002437static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002438 if (dir) {
2439 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2440 }
reed@google.comac8543f2012-01-30 20:51:25 +00002441}
2442
reed@google.comc1ea60a2012-01-31 15:15:36 +00002443#if 0
2444#include "SkString.h"
2445#include "../utils/SkParsePath.h"
2446static void dumpPath(const SkPath& path) {
2447 SkString str;
2448 SkParsePath::ToSVGString(path, &str);
2449 SkDebugf("%s\n", str.c_str());
2450}
2451#endif
2452
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002453namespace {
2454// for use with convex_dir_test
2455double mul(double a, double b) { return a * b; }
2456SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2457double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2458SkScalar toScalar(SkScalar a) { return a; }
2459
2460// determines the winding direction of a convex polygon with the precision
2461// of T. CAST_SCALAR casts an SkScalar to T.
2462template <typename T, T (CAST_SCALAR)(SkScalar)>
2463bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2464 // we find the first three points that form a non-degenerate
2465 // triangle. If there are no such points then the path is
2466 // degenerate. The first is always point 0. Now we find the second
2467 // point.
2468 int i = 0;
2469 enum { kX = 0, kY = 1 };
2470 T v0[2];
2471 while (1) {
2472 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2473 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2474 if (v0[kX] || v0[kY]) {
2475 break;
2476 }
2477 if (++i == n - 1) {
2478 return false;
2479 }
2480 }
2481 // now find a third point that is not colinear with the first two
2482 // points and check the orientation of the triangle (which will be
2483 // the same as the orientation of the path).
2484 for (++i; i < n; ++i) {
2485 T v1[2];
2486 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2487 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2488 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2489 if (0 != cross) {
2490 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2491 return true;
2492 }
2493 }
2494 return false;
2495}
2496}
2497
reed@google.comac8543f2012-01-30 20:51:25 +00002498/*
2499 * We loop through all contours, and keep the computed cross-product of the
2500 * contour that contained the global y-max. If we just look at the first
2501 * contour, we may find one that is wound the opposite way (correctly) since
2502 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2503 * that is outer most (or at least has the global y-max) before we can consider
2504 * its cross product.
2505 */
reed@google.com69a99432012-01-10 18:00:10 +00002506bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002507// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002508 // don't want to pay the cost for computing this if it
2509 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002510
2511 if (kUnknown_Direction != fDirection) {
2512 *dir = static_cast<Direction>(fDirection);
2513 return true;
2514 }
reed@google.com69a99432012-01-10 18:00:10 +00002515 const Convexity conv = this->getConvexityOrUnknown();
2516
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002517 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002518
reed@google.comac8543f2012-01-30 20:51:25 +00002519 // initialize with our logical y-min
2520 SkScalar ymax = this->getBounds().fTop;
2521 SkScalar ymaxCross = 0;
2522
reed@google.com69a99432012-01-10 18:00:10 +00002523 for (; !iter.done(); iter.next()) {
2524 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002525 if (n < 3) {
2526 continue;
2527 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002528
reed@google.comcabaf1d2012-01-11 21:03:05 +00002529 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002530 SkScalar cross = 0;
2531 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002532 // We try first at scalar precision, and then again at double
2533 // precision. This is because the vectors computed between distant
2534 // points may lose too much precision.
2535 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002536 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002537 return true;
2538 }
2539 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002540 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002541 return true;
2542 } else {
2543 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002544 }
2545 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002546 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002547 if (pts[index].fY < ymax) {
2548 continue;
2549 }
2550
reed@google.comc1ea60a2012-01-31 15:15:36 +00002551 // If there is more than 1 distinct point at the y-max, we take the
2552 // x-min and x-max of them and just subtract to compute the dir.
2553 if (pts[(index + 1) % n].fY == pts[index].fY) {
2554 int maxIndex;
2555 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002556 if (minIndex == maxIndex) {
2557 goto TRY_CROSSPROD;
2558 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002559 SkASSERT(pts[minIndex].fY == pts[index].fY);
2560 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2561 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2562 // we just subtract the indices, and let that auto-convert to
2563 // SkScalar, since we just want - or + to signal the direction.
2564 cross = minIndex - maxIndex;
2565 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002566 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002567 // Find a next and prev index to use for the cross-product test,
2568 // but we try to find pts that form non-zero vectors from pts[index]
2569 //
2570 // Its possible that we can't find two non-degenerate vectors, so
2571 // we have to guard our search (e.g. all the pts could be in the
2572 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002573
reed@google.comc1ea60a2012-01-31 15:15:36 +00002574 // we pass n - 1 instead of -1 so we don't foul up % operator by
2575 // passing it a negative LH argument.
2576 int prev = find_diff_pt(pts, index, n, n - 1);
2577 if (prev == index) {
2578 // completely degenerate, skip to next contour
2579 continue;
2580 }
2581 int next = find_diff_pt(pts, index, n, 1);
2582 SkASSERT(next != index);
2583 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002584 // 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 +00002585 // x-direction. We really should continue to walk away from the degeneracy until
2586 // there is a divergence.
2587 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002588 // construct the subtract so we get the correct Direction below
2589 cross = pts[index].fX - pts[next].fX;
2590 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002591 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002592
reed@google.comac8543f2012-01-30 20:51:25 +00002593 if (cross) {
2594 // record our best guess so far
2595 ymax = pts[index].fY;
2596 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002597 }
reed@google.com69a99432012-01-10 18:00:10 +00002598 }
2599 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002600 if (ymaxCross) {
2601 crossToDir(ymaxCross, dir);
2602 fDirection = *dir;
2603 return true;
2604 } else {
2605 return false;
2606 }
reed@google.comac8543f2012-01-30 20:51:25 +00002607}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002608
2609///////////////////////////////////////////////////////////////////////////////
2610
2611static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2612 SkScalar D, SkScalar t) {
2613 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2614}
2615
2616static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2617 SkScalar t) {
2618 SkScalar A = c3 + 3*(c1 - c2) - c0;
2619 SkScalar B = 3*(c2 - c1 - c1 + c0);
2620 SkScalar C = 3*(c1 - c0);
2621 SkScalar D = c0;
2622 return eval_cubic_coeff(A, B, C, D, t);
2623}
2624
2625/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2626 t value such that cubic(t) = target
2627 */
2628static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2629 SkScalar target, SkScalar* t) {
2630 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2631 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002632
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002633 SkScalar D = c0 - target;
2634 SkScalar A = c3 + 3*(c1 - c2) - c0;
2635 SkScalar B = 3*(c2 - c1 - c1 + c0);
2636 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002637
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002638 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2639 SkScalar minT = 0;
2640 SkScalar maxT = SK_Scalar1;
2641 SkScalar mid;
2642 int i;
2643 for (i = 0; i < 16; i++) {
2644 mid = SkScalarAve(minT, maxT);
2645 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2646 if (delta < 0) {
2647 minT = mid;
2648 delta = -delta;
2649 } else {
2650 maxT = mid;
2651 }
2652 if (delta < TOLERANCE) {
2653 break;
2654 }
2655 }
2656 *t = mid;
2657 return true;
2658}
2659
2660template <size_t N> static void find_minmax(const SkPoint pts[],
2661 SkScalar* minPtr, SkScalar* maxPtr) {
2662 SkScalar min, max;
2663 min = max = pts[0].fX;
2664 for (size_t i = 1; i < N; ++i) {
2665 min = SkMinScalar(min, pts[i].fX);
2666 max = SkMaxScalar(max, pts[i].fX);
2667 }
2668 *minPtr = min;
2669 *maxPtr = max;
2670}
2671
2672static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2673 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002674
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002675 int dir = 1;
2676 if (pts[0].fY > pts[3].fY) {
2677 storage[0] = pts[3];
2678 storage[1] = pts[2];
2679 storage[2] = pts[1];
2680 storage[3] = pts[0];
2681 pts = storage;
2682 dir = -1;
2683 }
2684 if (y < pts[0].fY || y >= pts[3].fY) {
2685 return 0;
2686 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002687
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002688 // quickreject or quickaccept
2689 SkScalar min, max;
2690 find_minmax<4>(pts, &min, &max);
2691 if (x < min) {
2692 return 0;
2693 }
2694 if (x > max) {
2695 return dir;
2696 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002697
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002698 // compute the actual x(t) value
2699 SkScalar t, xt;
2700 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2701 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2702 } else {
2703 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2704 xt = y < mid ? pts[0].fX : pts[3].fX;
2705 }
2706 return xt < x ? dir : 0;
2707}
2708
2709static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2710 SkPoint dst[10];
2711 int n = SkChopCubicAtYExtrema(pts, dst);
2712 int w = 0;
2713 for (int i = 0; i <= n; ++i) {
2714 w += winding_mono_cubic(&dst[i * 3], x, y);
2715 }
2716 return w;
2717}
2718
2719static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2720 SkScalar y0 = pts[0].fY;
2721 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002722
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002723 int dir = 1;
2724 if (y0 > y2) {
2725 SkTSwap(y0, y2);
2726 dir = -1;
2727 }
2728 if (y < y0 || y >= y2) {
2729 return 0;
2730 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002731
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002732 // bounds check on X (not required. is it faster?)
2733#if 0
2734 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2735 return 0;
2736 }
2737#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002738
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002739 SkScalar roots[2];
2740 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2741 2 * (pts[1].fY - pts[0].fY),
2742 pts[0].fY - y,
2743 roots);
2744 SkASSERT(n <= 1);
2745 SkScalar xt;
2746 if (0 == n) {
2747 SkScalar mid = SkScalarAve(y0, y2);
2748 // Need [0] and [2] if dir == 1
2749 // and [2] and [0] if dir == -1
2750 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2751 } else {
2752 SkScalar t = roots[0];
2753 SkScalar C = pts[0].fX;
2754 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2755 SkScalar B = 2 * (pts[1].fX - C);
2756 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2757 }
2758 return xt < x ? dir : 0;
2759}
2760
2761static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2762 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2763 if (y0 == y1) {
2764 return true;
2765 }
2766 if (y0 < y1) {
2767 return y1 <= y2;
2768 } else {
2769 return y1 >= y2;
2770 }
2771}
2772
2773static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2774 SkPoint dst[5];
2775 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002776
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002777 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2778 n = SkChopQuadAtYExtrema(pts, dst);
2779 pts = dst;
2780 }
2781 int w = winding_mono_quad(pts, x, y);
2782 if (n > 0) {
2783 w += winding_mono_quad(&pts[2], x, y);
2784 }
2785 return w;
2786}
2787
2788static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2789 SkScalar x0 = pts[0].fX;
2790 SkScalar y0 = pts[0].fY;
2791 SkScalar x1 = pts[1].fX;
2792 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002793
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002794 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002795
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002796 int dir = 1;
2797 if (y0 > y1) {
2798 SkTSwap(y0, y1);
2799 dir = -1;
2800 }
2801 if (y < y0 || y >= y1) {
2802 return 0;
2803 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002804
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002805 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2806 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002807
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002808 if (SkScalarSignAsInt(cross) == dir) {
2809 dir = 0;
2810 }
2811 return dir;
2812}
2813
2814bool SkPath::contains(SkScalar x, SkScalar y) const {
2815 bool isInverse = this->isInverseFillType();
2816 if (this->isEmpty()) {
2817 return isInverse;
2818 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002819
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002820 const SkRect& bounds = this->getBounds();
2821 if (!bounds.contains(x, y)) {
2822 return isInverse;
2823 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002824
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002825 SkPath::Iter iter(*this, true);
2826 bool done = false;
2827 int w = 0;
2828 do {
2829 SkPoint pts[4];
2830 switch (iter.next(pts, false)) {
2831 case SkPath::kMove_Verb:
2832 case SkPath::kClose_Verb:
2833 break;
2834 case SkPath::kLine_Verb:
2835 w += winding_line(pts, x, y);
2836 break;
2837 case SkPath::kQuad_Verb:
2838 w += winding_quad(pts, x, y);
2839 break;
2840 case SkPath::kCubic_Verb:
2841 w += winding_cubic(pts, x, y);
2842 break;
2843 case SkPath::kDone_Verb:
2844 done = true;
2845 break;
2846 }
2847 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002848
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002849 switch (this->getFillType()) {
2850 case SkPath::kEvenOdd_FillType:
2851 case SkPath::kInverseEvenOdd_FillType:
2852 w &= 1;
2853 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002854 default:
2855 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002856 }
2857 return SkToBool(w);
2858}
2859