blob: 94dd6518afbec24535f24ed5f219abc404429684 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2006 The Android Open Source Project
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
reed@android.com8a1c16f2008-12-17 15:59:43 +00009
djsollen@google.com94e75ee2012-06-08 18:30:46 +000010#include "SkBuffer.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000011#include "SkErrorInternals.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000012#include "SkMath.h"
humper@google.com75e3ca12013-04-08 21:44:11 +000013#include "SkPath.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000014#include "SkPathRef.h"
reed@google.com4ed0fb72012-12-12 20:48:18 +000015#include "SkRRect.h"
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +000016#include "SkThread.h"
reed@android.com8a1c16f2008-12-17 15:59:43 +000017
bsalomon@google.com65a87cc2012-08-14 13:15:44 +000018SK_DEFINE_INST_COUNT(SkPath);
19
reed@google.com744faba2012-05-29 19:54:52 +000020// This value is just made-up for now. When count is 4, calling memset was much
21// slower than just writing the loop. This seems odd, and hopefully in the
22// future this we appear to have been a fluke...
23#define MIN_COUNT_FOR_MEMSET_TO_BE_FAST 16
24
reed@android.com8a1c16f2008-12-17 15:59:43 +000025////////////////////////////////////////////////////////////////////////////
26
reed@google.com3563c9e2011-11-14 19:34:57 +000027/**
28 * Path.bounds is defined to be the bounds of all the control points.
29 * If we called bounds.join(r) we would skip r if r was empty, which breaks
30 * our promise. Hence we have a custom joiner that doesn't look at emptiness
31 */
32static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) {
33 dst->fLeft = SkMinScalar(dst->fLeft, src.fLeft);
34 dst->fTop = SkMinScalar(dst->fTop, src.fTop);
35 dst->fRight = SkMaxScalar(dst->fRight, src.fRight);
36 dst->fBottom = SkMaxScalar(dst->fBottom, src.fBottom);
37}
38
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000039static bool is_degenerate(const SkPath& path) {
40 SkPath::Iter iter(path, false);
41 SkPoint pts[4];
42 return SkPath::kDone_Verb == iter.next(pts);
43}
44
bsalomon@google.com6aa29652012-04-18 13:29:52 +000045class SkAutoDisableOvalCheck {
46public:
47 SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
48 fSaved = fPath->fIsOval;
49 }
50
51 ~SkAutoDisableOvalCheck() {
52 fPath->fIsOval = fSaved;
53 }
54
55private:
56 SkPath* fPath;
57 bool fSaved;
58};
59
bsalomon@google.com30c174b2012-11-13 14:36:42 +000060class SkAutoDisableDirectionCheck {
61public:
62 SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) {
63 fSaved = static_cast<SkPath::Direction>(fPath->fDirection);
64 }
65
66 ~SkAutoDisableDirectionCheck() {
67 fPath->fDirection = fSaved;
68 }
69
70private:
71 SkPath* fPath;
72 SkPath::Direction fSaved;
73};
74
reed@android.com8a1c16f2008-12-17 15:59:43 +000075/* This guy's constructor/destructor bracket a path editing operation. It is
76 used when we know the bounds of the amount we are going to add to the path
77 (usually a new contour, but not required).
reed@google.comabf15c12011-01-18 20:35:51 +000078
reed@android.com8a1c16f2008-12-17 15:59:43 +000079 It captures some state about the path up front (i.e. if it already has a
robertphillips@google.comca0c8382013-09-26 12:18:23 +000080 cached bounds), and then if it can, it updates the cache bounds explicitly,
reed@android.comd252db02009-04-01 18:31:44 +000081 avoiding the need to revisit all of the points in getBounds().
reed@google.comabf15c12011-01-18 20:35:51 +000082
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +000083 It also notes if the path was originally degenerate, and if so, sets
84 isConvex to true. Thus it can only be used if the contour being added is
robertphillips@google.comca0c8382013-09-26 12:18:23 +000085 convex (which is always true since we only allow the addition of rects).
reed@android.com8a1c16f2008-12-17 15:59:43 +000086 */
87class SkAutoPathBoundsUpdate {
88public:
89 SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fRect(r) {
90 this->init(path);
91 }
92
93 SkAutoPathBoundsUpdate(SkPath* path, SkScalar left, SkScalar top,
94 SkScalar right, SkScalar bottom) {
95 fRect.set(left, top, right, bottom);
96 this->init(path);
97 }
reed@google.comabf15c12011-01-18 20:35:51 +000098
reed@android.com8a1c16f2008-12-17 15:59:43 +000099 ~SkAutoPathBoundsUpdate() {
reed@google.com44699382013-10-31 17:28:30 +0000100 fPath->setConvexity(fDegenerate ? SkPath::kConvex_Convexity
101 : SkPath::kUnknown_Convexity);
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000102 if (fEmpty || fHasValidBounds) {
103 fPath->setBounds(fRect);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000104 }
105 }
reed@google.comabf15c12011-01-18 20:35:51 +0000106
reed@android.com8a1c16f2008-12-17 15:59:43 +0000107private:
reed@android.com6b82d1a2009-06-03 02:35:01 +0000108 SkPath* fPath;
109 SkRect fRect;
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000110 bool fHasValidBounds;
bsalomon@google.comfb6f0f62012-01-31 18:44:34 +0000111 bool fDegenerate;
reed@android.com6b82d1a2009-06-03 02:35:01 +0000112 bool fEmpty;
reed@google.comabf15c12011-01-18 20:35:51 +0000113
reed@android.com6b82d1a2009-06-03 02:35:01 +0000114 void init(SkPath* path) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000115 // Cannot use fRect for our bounds unless we know it is sorted
116 fRect.sort();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000117 fPath = path;
reed@google.coma8790de2012-10-24 21:04:04 +0000118 // Mark the path's bounds as dirty if (1) they are, or (2) the path
119 // is non-finite, and therefore its bounds are not meaningful
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000120 fHasValidBounds = path->hasComputedBounds() && path->isFinite();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000121 fEmpty = path->isEmpty();
robertphillips@google.comca0c8382013-09-26 12:18:23 +0000122 if (fHasValidBounds && !fEmpty) {
123 joinNoEmptyChecks(&fRect, fPath->getBounds());
124 }
125 fDegenerate = is_degenerate(*path);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000126 }
127};
128
reed@android.com8a1c16f2008-12-17 15:59:43 +0000129////////////////////////////////////////////////////////////////////////////
130
131/*
132 Stores the verbs and points as they are given to us, with exceptions:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000133 - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
reed@android.com8a1c16f2008-12-17 15:59:43 +0000134 - we insert a Move(0,0) if Line | Quad | Cubic is our first command
135
136 The iterator does more cleanup, especially if forceClose == true
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000137 1. If we encounter degenerate segments, remove them
138 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
139 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
140 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
reed@android.com8a1c16f2008-12-17 15:59:43 +0000141*/
142
143////////////////////////////////////////////////////////////////////////////
144
reed@google.comd335d1d2012-01-12 18:17:11 +0000145// flag to require a moveTo if we begin with something else, like lineTo etc.
146#define INITIAL_LASTMOVETOINDEX_VALUE ~0
147
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000148SkPath::SkPath()
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000149 : fPathRef(SkPathRef::CreateEmpty())
bungeman@google.coma5809a32013-06-21 15:13:34 +0000150#ifdef SK_BUILD_FOR_ANDROID
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000151 , fSourcePath(NULL)
bungeman@google.coma5809a32013-06-21 15:13:34 +0000152#endif
153{
154 this->resetFields();
155}
156
157void SkPath::resetFields() {
158 //fPathRef is assumed to have been emptied by the caller.
159 fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
160 fFillType = kWinding_FillType;
161 fSegmentMask = 0;
reed@google.com04863fa2011-05-15 04:08:24 +0000162 fConvexity = kUnknown_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000163 fDirection = kUnknown_Direction;
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000164 fIsOval = false;
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000165
166 // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we
167 // don't want to muck with it if it's been set to something non-NULL.
reed@android.com6b82d1a2009-06-03 02:35:01 +0000168}
reed@android.com8a1c16f2008-12-17 15:59:43 +0000169
bungeman@google.coma5809a32013-06-21 15:13:34 +0000170SkPath::SkPath(const SkPath& that)
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000171 : fPathRef(SkRef(that.fPathRef.get())) {
bungeman@google.coma5809a32013-06-21 15:13:34 +0000172 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000173#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000174 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000175#endif
bungeman@google.coma5809a32013-06-21 15:13:34 +0000176 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000177}
178
179SkPath::~SkPath() {
180 SkDEBUGCODE(this->validate();)
181}
182
bungeman@google.coma5809a32013-06-21 15:13:34 +0000183SkPath& SkPath::operator=(const SkPath& that) {
184 SkDEBUGCODE(that.validate();)
reed@android.com8a1c16f2008-12-17 15:59:43 +0000185
bungeman@google.coma5809a32013-06-21 15:13:34 +0000186 if (this != &that) {
187 fPathRef.reset(SkRef(that.fPathRef.get()));
188 this->copyFields(that);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000189#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.comcb8b0ee2013-08-15 21:14:51 +0000190 fSourcePath = that.fSourcePath;
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000191#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000192 }
193 SkDEBUGCODE(this->validate();)
194 return *this;
195}
196
bungeman@google.coma5809a32013-06-21 15:13:34 +0000197void SkPath::copyFields(const SkPath& that) {
198 //fPathRef is assumed to have been set by the caller.
bungeman@google.coma5809a32013-06-21 15:13:34 +0000199 fLastMoveToIndex = that.fLastMoveToIndex;
200 fFillType = that.fFillType;
201 fSegmentMask = that.fSegmentMask;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000202 fConvexity = that.fConvexity;
203 fDirection = that.fDirection;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000204 fIsOval = that.fIsOval;
bungeman@google.coma5809a32013-06-21 15:13:34 +0000205}
206
djsollen@google.com9c1a9672013-08-09 13:49:13 +0000207bool operator==(const SkPath& a, const SkPath& b) {
reed@android.com6b82d1a2009-06-03 02:35:01 +0000208 // note: don't need to look at isConvex or bounds, since just comparing the
209 // raw data is sufficient.
reed@google.com10296cc2011-09-21 12:29:05 +0000210
211 // We explicitly check fSegmentMask as a quick-reject. We could skip it,
212 // since it is only a cache of info in the fVerbs, but its a fast way to
213 // notice a difference
214
reed@android.com3abec1d2009-03-02 05:36:20 +0000215 return &a == &b ||
reed@google.com10296cc2011-09-21 12:29:05 +0000216 (a.fFillType == b.fFillType && a.fSegmentMask == b.fSegmentMask &&
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000217 *a.fPathRef.get() == *b.fPathRef.get());
reed@android.com3abec1d2009-03-02 05:36:20 +0000218}
219
bungeman@google.coma5809a32013-06-21 15:13:34 +0000220void SkPath::swap(SkPath& that) {
221 SkASSERT(&that != NULL);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000222
bungeman@google.coma5809a32013-06-21 15:13:34 +0000223 if (this != &that) {
224 fPathRef.swap(&that.fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000225 SkTSwap<int>(fLastMoveToIndex, that.fLastMoveToIndex);
226 SkTSwap<uint8_t>(fFillType, that.fFillType);
227 SkTSwap<uint8_t>(fSegmentMask, that.fSegmentMask);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000228 SkTSwap<uint8_t>(fConvexity, that.fConvexity);
229 SkTSwap<uint8_t>(fDirection, that.fDirection);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000230 SkTSwap<SkBool8>(fIsOval, that.fIsOval);
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000231#ifdef SK_BUILD_FOR_ANDROID
mtklein@google.com9c9d4a72013-08-07 19:17:53 +0000232 SkTSwap<const SkPath*>(fSourcePath, that.fSourcePath);
233#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000234 }
235}
236
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000237static inline bool check_edge_against_rect(const SkPoint& p0,
238 const SkPoint& p1,
239 const SkRect& rect,
240 SkPath::Direction dir) {
241 const SkPoint* edgeBegin;
242 SkVector v;
243 if (SkPath::kCW_Direction == dir) {
244 v = p1 - p0;
245 edgeBegin = &p0;
246 } else {
247 v = p0 - p1;
248 edgeBegin = &p1;
249 }
250 if (v.fX || v.fY) {
251 // check the cross product of v with the vec from edgeBegin to each rect corner
252 SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
253 SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
254 SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
255 SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
256 if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
257 return false;
258 }
259 }
260 return true;
261}
262
263bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
264 // This only handles non-degenerate convex paths currently.
265 if (kConvex_Convexity != this->getConvexity()) {
266 return false;
267 }
skia.committer@gmail.comcec8de62012-11-14 02:01:22 +0000268
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000269 Direction direction;
270 if (!this->cheapComputeDirection(&direction)) {
271 return false;
272 }
273
274 SkPoint firstPt;
275 SkPoint prevPt;
276 RawIter iter(*this);
277 SkPath::Verb verb;
278 SkPoint pts[4];
279 SkDEBUGCODE(int moveCnt = 0;)
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000280 SkDEBUGCODE(int segmentCount = 0;)
281 SkDEBUGCODE(int closeCount = 0;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000282
283 while ((verb = iter.next(pts)) != kDone_Verb) {
284 int nextPt = -1;
285 switch (verb) {
286 case kMove_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000287 SkASSERT(!segmentCount && !closeCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000288 SkDEBUGCODE(++moveCnt);
289 firstPt = prevPt = pts[0];
290 break;
291 case kLine_Verb:
292 nextPt = 1;
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000293 SkASSERT(moveCnt && !closeCount);
294 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000295 break;
296 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000297 case kConic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000298 SkASSERT(moveCnt && !closeCount);
299 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000300 nextPt = 2;
301 break;
302 case kCubic_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000303 SkASSERT(moveCnt && !closeCount);
304 SkDEBUGCODE(++segmentCount);
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000305 nextPt = 3;
306 break;
307 case kClose_Verb:
commit-bot@chromium.org62df5262013-08-01 15:35:06 +0000308 SkDEBUGCODE(++closeCount;)
bsalomon@google.com9bee33a2012-11-13 21:51:38 +0000309 break;
310 default:
311 SkDEBUGFAIL("unknown verb");
312 }
313 if (-1 != nextPt) {
314 if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
315 return false;
316 }
317 prevPt = pts[nextPt];
318 }
319 }
320
321 return check_edge_against_rect(prevPt, firstPt, rect, direction);
322}
323
robertphillips@google.com7101abe2013-10-29 22:45:37 +0000324uint32_t SkPath::getGenerationID() const {
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000325 uint32_t genID = fPathRef->genID();
326#ifdef SK_BUILD_FOR_ANDROID
327 SkASSERT((unsigned)fFillType < (1 << (32 - kPathRefGenIDBitCnt)));
328 genID |= static_cast<uint32_t>(fFillType) << kPathRefGenIDBitCnt;
329#endif
330 return genID;
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000331}
djsollen@google.come63793a2012-03-21 15:39:03 +0000332
commit-bot@chromium.org1ab9f732013-10-30 18:57:55 +0000333#ifdef SK_BUILD_FOR_ANDROID
djsollen@google.come63793a2012-03-21 15:39:03 +0000334const SkPath* SkPath::getSourcePath() const {
335 return fSourcePath;
336}
337
338void SkPath::setSourcePath(const SkPath* path) {
339 fSourcePath = path;
340}
djsollen@google.comf5dbe2f2011-04-15 13:41:26 +0000341#endif
342
reed@android.com8a1c16f2008-12-17 15:59:43 +0000343void SkPath::reset() {
344 SkDEBUGCODE(this->validate();)
345
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000346 fPathRef.reset(SkPathRef::CreateEmpty());
bungeman@google.coma5809a32013-06-21 15:13:34 +0000347 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000348}
349
350void SkPath::rewind() {
351 SkDEBUGCODE(this->validate();)
352
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000353 SkPathRef::Rewind(&fPathRef);
bungeman@google.coma5809a32013-06-21 15:13:34 +0000354 this->resetFields();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000355}
356
reed@google.com7e6c4d12012-05-10 14:05:43 +0000357bool SkPath::isLine(SkPoint line[2]) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000358 int verbCount = fPathRef->countVerbs();
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000359
commit-bot@chromium.orga62efcc2013-08-05 13:23:13 +0000360 if (2 == verbCount) {
361 SkASSERT(kMove_Verb == fPathRef->atVerb(0));
362 if (kLine_Verb == fPathRef->atVerb(1)) {
363 SkASSERT(2 == fPathRef->countPoints());
reed@google.com7e6c4d12012-05-10 14:05:43 +0000364 if (line) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000365 const SkPoint* pts = fPathRef->points();
reed@google.com7e6c4d12012-05-10 14:05:43 +0000366 line[0] = pts[0];
367 line[1] = pts[1];
368 }
369 return true;
370 }
371 }
372 return false;
373}
374
caryclark@google.comf1316942011-07-26 19:54:45 +0000375/*
376 Determines if path is a rect by keeping track of changes in direction
377 and looking for a loop either clockwise or counterclockwise.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000378
caryclark@google.comf1316942011-07-26 19:54:45 +0000379 The direction is computed such that:
380 0: vertical up
caryclark@google.comf68154a2012-11-21 15:18:06 +0000381 1: horizontal left
caryclark@google.comf1316942011-07-26 19:54:45 +0000382 2: vertical down
caryclark@google.comf68154a2012-11-21 15:18:06 +0000383 3: horizontal right
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000384
caryclark@google.comf1316942011-07-26 19:54:45 +0000385A rectangle cycles up/right/down/left or up/left/down/right.
386
387The test fails if:
388 The path is closed, and followed by a line.
389 A second move creates a new endpoint.
390 A diagonal line is parsed.
391 There's more than four changes of direction.
392 There's a discontinuity on the line (e.g., a move in the middle)
393 The line reverses direction.
394 The rectangle doesn't complete a cycle.
395 The path contains a quadratic or cubic.
396 The path contains fewer than four points.
397 The final point isn't equal to the first point.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000398
caryclark@google.comf1316942011-07-26 19:54:45 +0000399It's OK if the path has:
400 Several colinear line segments composing a rectangle side.
401 Single points on the rectangle side.
rmistry@google.comfbfcd562012-08-23 18:09:54 +0000402
caryclark@google.comf1316942011-07-26 19:54:45 +0000403The direction takes advantage of the corners found since opposite sides
404must travel in opposite directions.
405
406FIXME: Allow colinear quads and cubics to be treated like lines.
407FIXME: If the API passes fill-only, return true if the filled stroke
408 is a rectangle, though the caller failed to close the path.
409 */
caryclark@google.comf68154a2012-11-21 15:18:06 +0000410bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr,
411 bool* isClosed, Direction* direction) const {
caryclark@google.comf1316942011-07-26 19:54:45 +0000412 int corners = 0;
413 SkPoint first, last;
caryclark@google.com56f233a2012-11-19 13:06:06 +0000414 const SkPoint* pts = *ptsPtr;
caryclark@google.combfe90372012-11-21 13:56:20 +0000415 const SkPoint* savePts = NULL;
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000416 first.set(0, 0);
417 last.set(0, 0);
418 int firstDirection = 0;
419 int lastDirection = 0;
420 int nextDirection = 0;
421 bool closedOrMoved = false;
caryclark@google.comf1316942011-07-26 19:54:45 +0000422 bool autoClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000423 int verbCnt = fPathRef->countVerbs();
caryclark@google.com56f233a2012-11-19 13:06:06 +0000424 while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
425 switch (fPathRef->atVerb(*currVerb)) {
caryclark@google.comf1316942011-07-26 19:54:45 +0000426 case kClose_Verb:
caryclark@google.com56f233a2012-11-19 13:06:06 +0000427 savePts = pts;
428 pts = *ptsPtr;
caryclark@google.comf1316942011-07-26 19:54:45 +0000429 autoClose = true;
430 case kLine_Verb: {
431 SkScalar left = last.fX;
432 SkScalar top = last.fY;
433 SkScalar right = pts->fX;
434 SkScalar bottom = pts->fY;
435 ++pts;
436 if (left != right && top != bottom) {
437 return false; // diagonal
438 }
439 if (left == right && top == bottom) {
440 break; // single point on side OK
441 }
442 nextDirection = (left != right) << 0 |
443 (left < right || top < bottom) << 1;
444 if (0 == corners) {
445 firstDirection = nextDirection;
446 first = last;
447 last = pts[-1];
448 corners = 1;
449 closedOrMoved = false;
450 break;
451 }
452 if (closedOrMoved) {
453 return false; // closed followed by a line
454 }
caryclark@google.combfe90372012-11-21 13:56:20 +0000455 if (autoClose && nextDirection == firstDirection) {
456 break; // colinear with first
457 }
caryclark@google.comf1316942011-07-26 19:54:45 +0000458 closedOrMoved = autoClose;
459 if (lastDirection != nextDirection) {
460 if (++corners > 4) {
461 return false; // too many direction changes
462 }
463 }
464 last = pts[-1];
465 if (lastDirection == nextDirection) {
466 break; // colinear segment
467 }
468 // Possible values for corners are 2, 3, and 4.
469 // When corners == 3, nextDirection opposes firstDirection.
470 // Otherwise, nextDirection at corner 2 opposes corner 4.
tomhudson@google.com2c2508d2011-07-29 13:44:30 +0000471 int turn = firstDirection ^ (corners - 1);
caryclark@google.comf1316942011-07-26 19:54:45 +0000472 int directionCycle = 3 == corners ? 0 : nextDirection ^ turn;
473 if ((directionCycle ^ turn) != nextDirection) {
474 return false; // direction didn't follow cycle
475 }
476 break;
477 }
478 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000479 case kConic_Verb:
caryclark@google.comf1316942011-07-26 19:54:45 +0000480 case kCubic_Verb:
481 return false; // quadratic, cubic not allowed
482 case kMove_Verb:
483 last = *pts++;
484 closedOrMoved = true;
485 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000486 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000487 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000488 break;
caryclark@google.comf1316942011-07-26 19:54:45 +0000489 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000490 *currVerb += 1;
caryclark@google.comf1316942011-07-26 19:54:45 +0000491 lastDirection = nextDirection;
492 }
493 // Success if 4 corners and first point equals last
caryclark@google.combfe90372012-11-21 13:56:20 +0000494 bool result = 4 == corners && (first == last || autoClose);
495 if (savePts) {
496 *ptsPtr = savePts;
497 }
caryclark@google.comf68154a2012-11-21 15:18:06 +0000498 if (result && isClosed) {
499 *isClosed = autoClose;
500 }
501 if (result && direction) {
sugoi@google.com12b4e272012-12-06 20:13:11 +0000502 *direction = firstDirection == ((lastDirection + 1) & 3) ? kCCW_Direction : kCW_Direction;
caryclark@google.comf68154a2012-11-21 15:18:06 +0000503 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000504 return result;
505}
506
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000507bool SkPath::isRect(SkRect* rect) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000508 SkDEBUGCODE(this->validate();)
509 int currVerb = 0;
510 const SkPoint* pts = fPathRef->points();
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000511 bool result = isRectContour(false, &currVerb, &pts, NULL, NULL);
512 if (result && rect) {
513 *rect = getBounds();
caryclark@google.comf1316942011-07-26 19:54:45 +0000514 }
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000515 return result;
516}
skia.committer@gmail.com020b25b2013-06-22 07:00:58 +0000517
robertphillips@google.com8fd16032013-06-25 15:39:58 +0000518bool SkPath::isRect(bool* isClosed, Direction* direction) const {
519 SkDEBUGCODE(this->validate();)
520 int currVerb = 0;
521 const SkPoint* pts = fPathRef->points();
522 return isRectContour(false, &currVerb, &pts, isClosed, direction);
caryclark@google.comf68154a2012-11-21 15:18:06 +0000523}
524
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000525bool SkPath::isNestedRects(SkRect rects[2], Direction dirs[2]) const {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000526 SkDEBUGCODE(this->validate();)
527 int currVerb = 0;
528 const SkPoint* pts = fPathRef->points();
529 const SkPoint* first = pts;
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000530 Direction testDirs[2];
531 if (!isRectContour(true, &currVerb, &pts, NULL, &testDirs[0])) {
caryclark@google.com56f233a2012-11-19 13:06:06 +0000532 return false;
533 }
534 const SkPoint* last = pts;
535 SkRect testRects[2];
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000536 if (isRectContour(false, &currVerb, &pts, NULL, &testDirs[1])) {
scroggo@google.com614f9e32013-05-09 18:05:32 +0000537 testRects[0].set(first, SkToS32(last - first));
538 testRects[1].set(last, SkToS32(pts - last));
caryclark@google.com56f233a2012-11-19 13:06:06 +0000539 if (testRects[0].contains(testRects[1])) {
540 if (rects) {
541 rects[0] = testRects[0];
542 rects[1] = testRects[1];
543 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000544 if (dirs) {
545 dirs[0] = testDirs[0];
546 dirs[1] = testDirs[1];
547 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000548 return true;
549 }
550 if (testRects[1].contains(testRects[0])) {
551 if (rects) {
552 rects[0] = testRects[1];
553 rects[1] = testRects[0];
554 }
robertphillips@google.com83d1a682013-05-17 12:50:27 +0000555 if (dirs) {
556 dirs[0] = testDirs[1];
557 dirs[1] = testDirs[0];
558 }
caryclark@google.com56f233a2012-11-19 13:06:06 +0000559 return true;
560 }
561 }
562 return false;
563}
564
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000565int SkPath::countPoints() const {
566 return fPathRef->countPoints();
567}
568
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000569int SkPath::getPoints(SkPoint dst[], int max) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000570 SkDEBUGCODE(this->validate();)
571
572 SkASSERT(max >= 0);
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000573 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000574 int count = SkMin32(max, fPathRef->countPoints());
575 memcpy(dst, fPathRef->points(), count * sizeof(SkPoint));
576 return fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000577}
578
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000579SkPoint SkPath::getPoint(int index) const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000580 if ((unsigned)index < (unsigned)fPathRef->countPoints()) {
581 return fPathRef->atPoint(index);
reed@android.comd3aa4ff2010-02-09 16:38:45 +0000582 }
583 return SkPoint::Make(0, 0);
584}
585
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000586int SkPath::countVerbs() const {
587 return fPathRef->countVerbs();
588}
589
590static inline void copy_verbs_reverse(uint8_t* inorderDst,
591 const uint8_t* reversedSrc,
592 int count) {
593 for (int i = 0; i < count; ++i) {
594 inorderDst[i] = reversedSrc[~i];
595 }
596}
597
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000598int SkPath::getVerbs(uint8_t dst[], int max) const {
599 SkDEBUGCODE(this->validate();)
600
601 SkASSERT(max >= 0);
602 SkASSERT(!max || dst);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000603 int count = SkMin32(max, fPathRef->countVerbs());
604 copy_verbs_reverse(dst, fPathRef->verbs(), count);
605 return fPathRef->countVerbs();
bsalomon@google.comdf9d6562012-06-07 21:43:15 +0000606}
607
reed@google.com294dd7b2011-10-11 11:58:32 +0000608bool SkPath::getLastPt(SkPoint* lastPt) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000609 SkDEBUGCODE(this->validate();)
610
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000611 int count = fPathRef->countPoints();
reed@google.com294dd7b2011-10-11 11:58:32 +0000612 if (count > 0) {
613 if (lastPt) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000614 *lastPt = fPathRef->atPoint(count - 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000615 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000616 return true;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000617 }
reed@google.com294dd7b2011-10-11 11:58:32 +0000618 if (lastPt) {
619 lastPt->set(0, 0);
620 }
621 return false;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000622}
623
624void SkPath::setLastPt(SkScalar x, SkScalar y) {
625 SkDEBUGCODE(this->validate();)
626
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000627 int count = fPathRef->countPoints();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000628 if (count == 0) {
629 this->moveTo(x, y);
630 } else {
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000631 fIsOval = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000632 SkPathRef::Editor ed(&fPathRef);
633 ed.atPoint(count-1)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000634 }
635}
636
reed@google.com04863fa2011-05-15 04:08:24 +0000637void SkPath::setConvexity(Convexity c) {
638 if (fConvexity != c) {
639 fConvexity = c;
reed@google.com04863fa2011-05-15 04:08:24 +0000640 }
641}
642
reed@android.com8a1c16f2008-12-17 15:59:43 +0000643//////////////////////////////////////////////////////////////////////////////
644// Construction methods
645
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000646#define DIRTY_AFTER_EDIT \
647 do { \
schenney@chromium.org4da06ab2011-12-20 15:14:18 +0000648 fConvexity = kUnknown_Convexity; \
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000649 fDirection = kUnknown_Direction; \
bsalomon@google.com6aa29652012-04-18 13:29:52 +0000650 fIsOval = false; \
reed@google.comb54455e2011-05-16 14:16:04 +0000651 } while (0)
652
reed@android.com8a1c16f2008-12-17 15:59:43 +0000653void SkPath::incReserve(U16CPU inc) {
654 SkDEBUGCODE(this->validate();)
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000655 SkPathRef::Editor(&fPathRef, inc, inc);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000656 SkDEBUGCODE(this->validate();)
657}
658
659void SkPath::moveTo(SkScalar x, SkScalar y) {
660 SkDEBUGCODE(this->validate();)
661
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000662 SkPathRef::Editor ed(&fPathRef);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000663
reed@google.comd335d1d2012-01-12 18:17:11 +0000664 // remember our index
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000665 fLastMoveToIndex = ed.pathRef()->countPoints();
reed@google.comd335d1d2012-01-12 18:17:11 +0000666
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000667 ed.growForVerb(kMove_Verb)->set(x, y);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000668}
669
670void SkPath::rMoveTo(SkScalar x, SkScalar y) {
671 SkPoint pt;
672 this->getLastPt(&pt);
673 this->moveTo(pt.fX + x, pt.fY + y);
674}
675
reed@google.comd335d1d2012-01-12 18:17:11 +0000676void SkPath::injectMoveToIfNeeded() {
677 if (fLastMoveToIndex < 0) {
678 SkScalar x, y;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000679 if (fPathRef->countVerbs() == 0) {
reed@google.comd335d1d2012-01-12 18:17:11 +0000680 x = y = 0;
681 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000682 const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex);
reed@google.comd335d1d2012-01-12 18:17:11 +0000683 x = pt.fX;
684 y = pt.fY;
685 }
686 this->moveTo(x, y);
687 }
688}
689
reed@android.com8a1c16f2008-12-17 15:59:43 +0000690void SkPath::lineTo(SkScalar x, SkScalar y) {
691 SkDEBUGCODE(this->validate();)
692
reed@google.comd335d1d2012-01-12 18:17:11 +0000693 this->injectMoveToIfNeeded();
694
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000695 SkPathRef::Editor ed(&fPathRef);
696 ed.growForVerb(kLine_Verb)->set(x, y);
reed@google.com10296cc2011-09-21 12:29:05 +0000697 fSegmentMask |= kLine_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000698
reed@google.comb54455e2011-05-16 14:16:04 +0000699 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000700}
701
702void SkPath::rLineTo(SkScalar x, SkScalar y) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000703 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000704 SkPoint pt;
705 this->getLastPt(&pt);
706 this->lineTo(pt.fX + x, pt.fY + y);
707}
708
709void SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
710 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000711
reed@google.comd335d1d2012-01-12 18:17:11 +0000712 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000713
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000714 SkPathRef::Editor ed(&fPathRef);
715 SkPoint* pts = ed.growForVerb(kQuad_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000716 pts[0].set(x1, y1);
717 pts[1].set(x2, y2);
reed@google.com10296cc2011-09-21 12:29:05 +0000718 fSegmentMask |= kQuad_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000719
reed@google.comb54455e2011-05-16 14:16:04 +0000720 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000721}
722
723void SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000724 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000725 SkPoint pt;
726 this->getLastPt(&pt);
727 this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2);
728}
729
reed@google.com277c3f82013-05-31 15:17:50 +0000730void SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
731 SkScalar w) {
732 // check for <= 0 or NaN with this test
733 if (!(w > 0)) {
734 this->lineTo(x2, y2);
735 } else if (!SkScalarIsFinite(w)) {
736 this->lineTo(x1, y1);
737 this->lineTo(x2, y2);
738 } else if (SK_Scalar1 == w) {
739 this->quadTo(x1, y1, x2, y2);
740 } else {
741 SkDEBUGCODE(this->validate();)
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000742
reed@google.com277c3f82013-05-31 15:17:50 +0000743 this->injectMoveToIfNeeded();
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000744
reed@google.com277c3f82013-05-31 15:17:50 +0000745 SkPathRef::Editor ed(&fPathRef);
746 SkPoint* pts = ed.growForConic(w);
747 pts[0].set(x1, y1);
748 pts[1].set(x2, y2);
749 fSegmentMask |= kConic_SegmentMask;
skia.committer@gmail.com26da7f02013-06-01 07:01:39 +0000750
reed@google.com277c3f82013-05-31 15:17:50 +0000751 DIRTY_AFTER_EDIT;
752 }
753}
754
755void SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
756 SkScalar w) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000757 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@google.com277c3f82013-05-31 15:17:50 +0000758 SkPoint pt;
759 this->getLastPt(&pt);
760 this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w);
761}
762
reed@android.com8a1c16f2008-12-17 15:59:43 +0000763void SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
764 SkScalar x3, SkScalar y3) {
765 SkDEBUGCODE(this->validate();)
766
reed@google.comd335d1d2012-01-12 18:17:11 +0000767 this->injectMoveToIfNeeded();
768
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000769 SkPathRef::Editor ed(&fPathRef);
770 SkPoint* pts = ed.growForVerb(kCubic_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000771 pts[0].set(x1, y1);
772 pts[1].set(x2, y2);
773 pts[2].set(x3, y3);
reed@google.com10296cc2011-09-21 12:29:05 +0000774 fSegmentMask |= kCubic_SegmentMask;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000775
reed@google.comb54455e2011-05-16 14:16:04 +0000776 DIRTY_AFTER_EDIT;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000777}
778
779void SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
780 SkScalar x3, SkScalar y3) {
commit-bot@chromium.org9d54aeb2013-08-09 19:48:26 +0000781 this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt().
reed@android.com8a1c16f2008-12-17 15:59:43 +0000782 SkPoint pt;
783 this->getLastPt(&pt);
784 this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2,
785 pt.fX + x3, pt.fY + y3);
786}
787
788void SkPath::close() {
789 SkDEBUGCODE(this->validate();)
790
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000791 int count = fPathRef->countVerbs();
reed@android.com8a1c16f2008-12-17 15:59:43 +0000792 if (count > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000793 switch (fPathRef->atVerb(count - 1)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +0000794 case kLine_Verb:
795 case kQuad_Verb:
reed@google.com277c3f82013-05-31 15:17:50 +0000796 case kConic_Verb:
reed@android.com8a1c16f2008-12-17 15:59:43 +0000797 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000798 case kMove_Verb: {
799 SkPathRef::Editor ed(&fPathRef);
800 ed.growForVerb(kClose_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000801 break;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000802 }
reed@google.com277c3f82013-05-31 15:17:50 +0000803 case kClose_Verb:
reed@google.comfa2f2a42013-05-30 15:29:48 +0000804 // don't add a close if it's the first verb or a repeat
reed@google.com7950a9e2013-05-30 14:57:55 +0000805 break;
reed@google.com277c3f82013-05-31 15:17:50 +0000806 default:
mtklein@google.com330313a2013-08-22 15:37:26 +0000807 SkDEBUGFAIL("unexpected verb");
reed@google.com277c3f82013-05-31 15:17:50 +0000808 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +0000809 }
810 }
reed@google.comd335d1d2012-01-12 18:17:11 +0000811
812 // signal that we need a moveTo to follow us (unless we're done)
813#if 0
814 if (fLastMoveToIndex >= 0) {
815 fLastMoveToIndex = ~fLastMoveToIndex;
816 }
817#else
818 fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1);
819#endif
reed@android.com8a1c16f2008-12-17 15:59:43 +0000820}
821
822///////////////////////////////////////////////////////////////////////////////
reed@google.comabf15c12011-01-18 20:35:51 +0000823
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000824static void assert_known_direction(int dir) {
825 SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
826}
827
reed@android.com8a1c16f2008-12-17 15:59:43 +0000828void SkPath::addRect(const SkRect& rect, Direction dir) {
829 this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
830}
831
832void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
833 SkScalar bottom, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +0000834 assert_known_direction(dir);
bsalomon@google.com30c174b2012-11-13 14:36:42 +0000835 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
836 SkAutoDisableDirectionCheck addc(this);
837
reed@android.com8a1c16f2008-12-17 15:59:43 +0000838 SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
839
840 this->incReserve(5);
841
842 this->moveTo(left, top);
843 if (dir == kCCW_Direction) {
844 this->lineTo(left, bottom);
845 this->lineTo(right, bottom);
846 this->lineTo(right, top);
847 } else {
848 this->lineTo(right, top);
849 this->lineTo(right, bottom);
850 this->lineTo(left, bottom);
851 }
852 this->close();
853}
854
reed@google.com744faba2012-05-29 19:54:52 +0000855void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
856 SkDEBUGCODE(this->validate();)
857 if (count <= 0) {
858 return;
859 }
860
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000861 SkPathRef::Editor ed(&fPathRef);
862 fLastMoveToIndex = ed.pathRef()->countPoints();
863 uint8_t* vb;
864 SkPoint* p;
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000865 // +close makes room for the extra kClose_Verb
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000866 ed.grow(count + close, count, &vb, &p);
bsalomon@google.com6c5418e2012-09-14 20:30:37 +0000867
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000868 memcpy(p, pts, count * sizeof(SkPoint));
869 vb[~0] = kMove_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000870 if (count > 1) {
871 // cast to unsigned, so if MIN_COUNT_FOR_MEMSET_TO_BE_FAST is defined to
872 // be 0, the compiler will remove the test/branch entirely.
873 if ((unsigned)count >= MIN_COUNT_FOR_MEMSET_TO_BE_FAST) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000874 memset(vb - count, kLine_Verb, count - 1);
reed@google.com744faba2012-05-29 19:54:52 +0000875 } else {
876 for (int i = 1; i < count; ++i) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000877 vb[~i] = kLine_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000878 }
879 }
880 fSegmentMask |= kLine_SegmentMask;
881 }
882 if (close) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000883 vb[~count] = kClose_Verb;
reed@google.com744faba2012-05-29 19:54:52 +0000884 }
885
reed@google.com744faba2012-05-29 19:54:52 +0000886 DIRTY_AFTER_EDIT;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +0000887 SkDEBUGCODE(this->validate();)
reed@google.com744faba2012-05-29 19:54:52 +0000888}
889
robertphillips@google.com1cc385b2013-10-17 12:17:27 +0000890#include "SkGeometry.h"
891
892static int build_arc_points(const SkRect& oval, SkScalar startAngle,
893 SkScalar sweepAngle,
894 SkPoint pts[kSkBuildQuadArcStorage]) {
895
896 if (0 == sweepAngle &&
897 (0 == startAngle || SkIntToScalar(360) == startAngle)) {
898 // Chrome uses this path to move into and out of ovals. If not
899 // treated as a special case the moves can distort the oval's
900 // bounding box (and break the circle special case).
901 pts[0].set(oval.fRight, oval.centerY());
902 return 1;
903 } else if (0 == oval.width() && 0 == oval.height()) {
904 // Chrome will sometimes create 0 radius round rects. Having degenerate
905 // quad segments in the path prevents the path from being recognized as
906 // a rect.
907 // TODO: optimizing the case where only one of width or height is zero
908 // should also be considered. This case, however, doesn't seem to be
909 // as common as the single point case.
910 pts[0].set(oval.fRight, oval.fTop);
911 return 1;
912 }
913
914 SkVector start, stop;
915
916 start.fY = SkScalarSinCos(SkDegreesToRadians(startAngle), &start.fX);
917 stop.fY = SkScalarSinCos(SkDegreesToRadians(startAngle + sweepAngle),
918 &stop.fX);
919
920 /* If the sweep angle is nearly (but less than) 360, then due to precision
921 loss in radians-conversion and/or sin/cos, we may end up with coincident
922 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
923 of drawing a nearly complete circle (good).
924 e.g. canvas.drawArc(0, 359.99, ...)
925 -vs- canvas.drawArc(0, 359.9, ...)
926 We try to detect this edge case, and tweak the stop vector
927 */
928 if (start == stop) {
929 SkScalar sw = SkScalarAbs(sweepAngle);
930 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
931 SkScalar stopRad = SkDegreesToRadians(startAngle + sweepAngle);
932 // make a guess at a tiny angle (in radians) to tweak by
933 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
934 // not sure how much will be enough, so we use a loop
935 do {
936 stopRad -= deltaRad;
937 stop.fY = SkScalarSinCos(stopRad, &stop.fX);
938 } while (start == stop);
939 }
940 }
941
942 SkMatrix matrix;
943
944 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
945 matrix.postTranslate(oval.centerX(), oval.centerY());
946
947 return SkBuildQuadArc(start, stop,
948 sweepAngle > 0 ? kCW_SkRotationDirection :
949 kCCW_SkRotationDirection,
950 &matrix, pts);
951}
952
reed@android.com8a1c16f2008-12-17 15:59:43 +0000953static void add_corner_arc(SkPath* path, const SkRect& rect,
954 SkScalar rx, SkScalar ry, int startAngle,
robertphillips@google.com12367192013-10-20 13:11:16 +0000955 SkPath::Direction dir, bool forceMoveTo) {
skia.committer@gmail.com7a03d862012-12-18 02:03:03 +0000956 // These two asserts are not sufficient, since really we want to know
957 // that the pair of radii (e.g. left and right, or top and bottom) sum
958 // to <= dimension, but we don't have that data here, so we just have
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000959 // these conservative asserts.
960 SkASSERT(0 <= rx && rx <= rect.width());
961 SkASSERT(0 <= ry && ry <= rect.height());
reed@google.comabf15c12011-01-18 20:35:51 +0000962
reed@android.com8a1c16f2008-12-17 15:59:43 +0000963 SkRect r;
964 r.set(-rx, -ry, rx, ry);
965
966 switch (startAngle) {
967 case 0:
968 r.offset(rect.fRight - r.fRight, rect.fBottom - r.fBottom);
969 break;
970 case 90:
971 r.offset(rect.fLeft - r.fLeft, rect.fBottom - r.fBottom);
972 break;
973 case 180: r.offset(rect.fLeft - r.fLeft, rect.fTop - r.fTop); break;
974 case 270: r.offset(rect.fRight - r.fRight, rect.fTop - r.fTop); break;
tomhudson@google.com0c00f212011-12-28 14:59:50 +0000975 default: SkDEBUGFAIL("unexpected startAngle in add_corner_arc");
reed@android.com8a1c16f2008-12-17 15:59:43 +0000976 }
reed@google.comabf15c12011-01-18 20:35:51 +0000977
reed@android.com8a1c16f2008-12-17 15:59:43 +0000978 SkScalar start = SkIntToScalar(startAngle);
979 SkScalar sweep = SkIntToScalar(90);
980 if (SkPath::kCCW_Direction == dir) {
981 start += sweep;
982 sweep = -sweep;
983 }
reed@google.comabf15c12011-01-18 20:35:51 +0000984
robertphillips@google.com12367192013-10-20 13:11:16 +0000985 path->arcTo(r, start, sweep, forceMoveTo);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000986}
987
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000988void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000989 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000990 SkRRect rrect;
991 rrect.setRectRadii(rect, (const SkVector*) radii);
992 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000993}
994
reed@google.com4ed0fb72012-12-12 20:48:18 +0000995void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000996 assert_known_direction(dir);
997
998 if (rrect.isEmpty()) {
999 return;
1000 }
1001
reed@google.com4ed0fb72012-12-12 20:48:18 +00001002 const SkRect& bounds = rrect.getBounds();
1003
1004 if (rrect.isRect()) {
1005 this->addRect(bounds, dir);
1006 } else if (rrect.isOval()) {
1007 this->addOval(bounds, dir);
1008 } else if (rrect.isSimple()) {
1009 const SkVector& rad = rrect.getSimpleRadii();
1010 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
1011 } else {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001012 SkAutoPathBoundsUpdate apbu(this, bounds);
1013
1014 if (kCW_Direction == dir) {
robertphillips@google.com12367192013-10-20 13:11:16 +00001015 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1016 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
1017 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1018 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001019 } else {
robertphillips@google.com12367192013-10-20 13:11:16 +00001020 add_corner_arc(this, bounds, rrect.fRadii[0].fX, rrect.fRadii[0].fY, 180, dir, true);
1021 add_corner_arc(this, bounds, rrect.fRadii[3].fX, rrect.fRadii[3].fY, 90, dir, false);
1022 add_corner_arc(this, bounds, rrect.fRadii[2].fX, rrect.fRadii[2].fY, 0, dir, false);
1023 add_corner_arc(this, bounds, rrect.fRadii[1].fX, rrect.fRadii[1].fY, 270, dir, false);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001024 }
1025 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001026 }
1027}
1028
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001029bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001030 int count = fPathRef->countVerbs();
1031 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1032 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001033 if (*verbs == kLine_Verb ||
1034 *verbs == kQuad_Verb ||
1035 *verbs == kCubic_Verb) {
1036 return false;
1037 }
1038 ++verbs;
1039 }
1040 return true;
1041}
1042
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001043#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001044#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001045#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001046
1047void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1048 Direction dir) {
1049 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001050
humper@google.com75e3ca12013-04-08 21:44:11 +00001051 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001052 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001053 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001054 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001055 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1056 return;
1057 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001058
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001059 SkScalar w = rect.width();
1060 SkScalar halfW = SkScalarHalf(w);
1061 SkScalar h = rect.height();
1062 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001063
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001064 if (halfW <= 0 || halfH <= 0) {
1065 return;
1066 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001067
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001068 bool skip_hori = rx >= halfW;
1069 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001070
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001071 if (skip_hori && skip_vert) {
1072 this->addOval(rect, dir);
1073 return;
1074 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001075
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001076 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001077
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001078 SkAutoPathBoundsUpdate apbu(this, rect);
1079 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001080
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001081 if (skip_hori) {
1082 rx = halfW;
1083 } else if (skip_vert) {
1084 ry = halfH;
1085 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001086
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001087#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001088 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1089 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001090
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001091 this->incReserve(17);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001092#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001093 // Please see SkBuildQuadArc for more information but, we need to pull
1094 // the off-curve quadratic points in a little bit to make the round
1095 // rects convex.
1096 static const SkScalar kOffCurveEpsilon = 0.0001f;
1097
1098 SkScalar midPtX = rx * SK_ScalarRoot2Over2;
1099 SkScalar midPtY = ry * SK_ScalarRoot2Over2;
1100
1101 SkScalar offPtX = rx * SK_ScalarTanPIOver8 - kOffCurveEpsilon;
1102 SkScalar offPtY = ry * SK_ScalarTanPIOver8 - kOffCurveEpsilon;
1103
1104 this->incReserve(21);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001105#endif
robertphillips@google.com12367192013-10-20 13:11:16 +00001106 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001107 if (dir == kCCW_Direction) {
1108 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001109 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001110 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001111#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001112 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1113 rect.fLeft, rect.fTop + ry - sy,
1114 rect.fLeft, rect.fTop + ry); // top-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001115#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001116 this->quadTo(rect.fLeft + rx - offPtX, rect.fTop + kOffCurveEpsilon,
1117 rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
1118 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fTop + ry - offPtY,
1119 rect.fLeft, rect.fTop + ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001120#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001121 if (!skip_vert) {
1122 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1123 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001124#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001125 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1126 rect.fLeft + rx - sx, rect.fBottom,
1127 rect.fLeft + rx, rect.fBottom); // bot-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001128#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001129 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1130 rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
1131 this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom - kOffCurveEpsilon,
1132 rect.fLeft + rx, rect.fBottom);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001133#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001134 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001135 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001136 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001137#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001138 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1139 rect.fRight, rect.fBottom - ry + sy,
1140 rect.fRight, rect.fBottom - ry); // bot-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001141#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001142 this->quadTo(rect.fRight - rx + offPtX, rect.fBottom - kOffCurveEpsilon,
1143 rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
1144 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1145 rect.fRight, rect.fBottom - ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001146#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001147 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001148 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001149 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001150#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001151 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1152 rect.fRight - rx + sx, rect.fTop,
1153 rect.fRight - rx, rect.fTop); // top-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001154#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001155 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fTop + ry - offPtY,
1156 rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
1157 this->quadTo(rect.fRight - rx + offPtX, rect.fTop + kOffCurveEpsilon,
1158 rect.fRight - rx, rect.fTop);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001159#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001160 } else {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001161#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001162 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1163 rect.fRight, rect.fTop + ry - sy,
1164 rect.fRight, rect.fTop + ry); // top-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001165#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001166 this->quadTo(rect.fRight - rx + offPtX, rect.fTop + kOffCurveEpsilon,
1167 rect.fRight - rx + midPtX, rect.fTop + ry - midPtY);
1168 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fTop + ry - offPtY,
1169 rect.fRight, rect.fTop + ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001170#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001171 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001172 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001173 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001174#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001175 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1176 rect.fRight - rx + sx, rect.fBottom,
1177 rect.fRight - rx, rect.fBottom); // bot-right
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001178#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001179 this->quadTo(rect.fRight - kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1180 rect.fRight - rx + midPtX, rect.fBottom - ry + midPtY);
1181 this->quadTo(rect.fRight - rx + offPtX, rect.fBottom - kOffCurveEpsilon,
1182 rect.fRight - rx, rect.fBottom);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001183#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001184 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001185 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001186 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001187#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001188 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1189 rect.fLeft, rect.fBottom - ry + sy,
1190 rect.fLeft, rect.fBottom - ry); // bot-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001191#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001192 this->quadTo(rect.fLeft + rx - offPtX, rect.fBottom - kOffCurveEpsilon,
1193 rect.fLeft + rx - midPtX, rect.fBottom - ry + midPtY);
1194 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fBottom - ry + offPtY,
1195 rect.fLeft, rect.fBottom - ry);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001196#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001197 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001198 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001199 }
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001200#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001201 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1202 rect.fLeft + rx - sx, rect.fTop,
1203 rect.fLeft + rx, rect.fTop); // top-left
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001204#else
robertphillips@google.com12367192013-10-20 13:11:16 +00001205 this->quadTo(rect.fLeft + kOffCurveEpsilon, rect.fTop + ry - offPtY,
1206 rect.fLeft + rx - midPtX, rect.fTop + ry - midPtY);
1207 this->quadTo(rect.fLeft + rx - offPtX, rect.fTop + kOffCurveEpsilon,
1208 rect.fLeft + rx, rect.fTop);
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001209#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001210 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001211 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001212 }
1213 }
1214 this->close();
1215}
1216
reed@android.com8a1c16f2008-12-17 15:59:43 +00001217void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001218 assert_known_direction(dir);
1219
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001220 /* If addOval() is called after previous moveTo(),
1221 this path is still marked as an oval. This is used to
1222 fit into WebKit's calling sequences.
1223 We can't simply check isEmpty() in this case, as additional
1224 moveTo() would mark the path non empty.
1225 */
1226 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001227 if (fIsOval) {
1228 fDirection = dir;
1229 } else {
1230 fDirection = kUnknown_Direction;
1231 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001232
1233 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001234 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001235
reed@android.com8a1c16f2008-12-17 15:59:43 +00001236 SkAutoPathBoundsUpdate apbu(this, oval);
1237
1238 SkScalar cx = oval.centerX();
1239 SkScalar cy = oval.centerY();
1240 SkScalar rx = SkScalarHalf(oval.width());
1241 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001242
reed@android.com8a1c16f2008-12-17 15:59:43 +00001243 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1244 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1245 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1246 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1247
1248 /*
1249 To handle imprecision in computing the center and radii, we revert to
1250 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1251 to ensure that we don't exceed the oval's bounds *ever*, since we want
1252 to use oval for our fast-bounds, rather than have to recompute it.
1253 */
1254 const SkScalar L = oval.fLeft; // cx - rx
1255 const SkScalar T = oval.fTop; // cy - ry
1256 const SkScalar R = oval.fRight; // cx + rx
1257 const SkScalar B = oval.fBottom; // cy + ry
1258
1259 this->incReserve(17); // 8 quads + close
1260 this->moveTo(R, cy);
1261 if (dir == kCCW_Direction) {
1262 this->quadTo( R, cy - sy, cx + mx, cy - my);
1263 this->quadTo(cx + sx, T, cx , T);
1264 this->quadTo(cx - sx, T, cx - mx, cy - my);
1265 this->quadTo( L, cy - sy, L, cy );
1266 this->quadTo( L, cy + sy, cx - mx, cy + my);
1267 this->quadTo(cx - sx, B, cx , B);
1268 this->quadTo(cx + sx, B, cx + mx, cy + my);
1269 this->quadTo( R, cy + sy, R, cy );
1270 } else {
1271 this->quadTo( R, cy + sy, cx + mx, cy + my);
1272 this->quadTo(cx + sx, B, cx , B);
1273 this->quadTo(cx - sx, B, cx - mx, cy + my);
1274 this->quadTo( L, cy + sy, L, cy );
1275 this->quadTo( L, cy - sy, cx - mx, cy - my);
1276 this->quadTo(cx - sx, T, cx , T);
1277 this->quadTo(cx + sx, T, cx + mx, cy - my);
1278 this->quadTo( R, cy - sy, R, cy );
1279 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001280 this->close();
1281}
1282
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001283bool SkPath::isOval(SkRect* rect) const {
1284 if (fIsOval && rect) {
1285 *rect = getBounds();
1286 }
1287
1288 return fIsOval;
1289}
1290
reed@android.com8a1c16f2008-12-17 15:59:43 +00001291void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1292 if (r > 0) {
1293 SkRect rect;
1294 rect.set(x - r, y - r, x + r, y + r);
1295 this->addOval(rect, dir);
1296 }
1297}
1298
reed@android.com8a1c16f2008-12-17 15:59:43 +00001299void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1300 bool forceMoveTo) {
1301 if (oval.width() < 0 || oval.height() < 0) {
1302 return;
1303 }
1304
1305 SkPoint pts[kSkBuildQuadArcStorage];
1306 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1307 SkASSERT((count & 1) == 1);
1308
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001309 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001310 forceMoveTo = true;
1311 }
1312 this->incReserve(count);
1313 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1314 for (int i = 1; i < count; i += 2) {
1315 this->quadTo(pts[i], pts[i+1]);
1316 }
1317}
1318
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001319void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001320 if (oval.isEmpty() || 0 == sweepAngle) {
1321 return;
1322 }
1323
1324 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1325
1326 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1327 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1328 return;
1329 }
1330
1331 SkPoint pts[kSkBuildQuadArcStorage];
1332 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1333
1334 this->incReserve(count);
1335 this->moveTo(pts[0]);
1336 for (int i = 1; i < count; i += 2) {
1337 this->quadTo(pts[i], pts[i+1]);
1338 }
1339}
1340
1341/*
1342 Need to handle the case when the angle is sharp, and our computed end-points
1343 for the arc go behind pt1 and/or p2...
1344*/
1345void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1346 SkScalar radius) {
1347 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001348
reed@android.com8a1c16f2008-12-17 15:59:43 +00001349 // need to know our prev pt so we can construct tangent vectors
1350 {
1351 SkPoint start;
1352 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001353 // Handle degenerate cases by adding a line to the first point and
1354 // bailing out.
1355 if ((x1 == start.fX && y1 == start.fY) ||
1356 (x1 == x2 && y1 == y2) ||
1357 radius == 0) {
1358 this->lineTo(x1, y1);
1359 return;
1360 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001361 before.setNormalize(x1 - start.fX, y1 - start.fY);
1362 after.setNormalize(x2 - x1, y2 - y1);
1363 }
reed@google.comabf15c12011-01-18 20:35:51 +00001364
reed@android.com8a1c16f2008-12-17 15:59:43 +00001365 SkScalar cosh = SkPoint::DotProduct(before, after);
1366 SkScalar sinh = SkPoint::CrossProduct(before, after);
1367
1368 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001369 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001370 return;
1371 }
reed@google.comabf15c12011-01-18 20:35:51 +00001372
reed@android.com8a1c16f2008-12-17 15:59:43 +00001373 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1374 if (dist < 0) {
1375 dist = -dist;
1376 }
1377
1378 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1379 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1380 SkRotationDirection arcDir;
1381
1382 // now turn before/after into normals
1383 if (sinh > 0) {
1384 before.rotateCCW();
1385 after.rotateCCW();
1386 arcDir = kCW_SkRotationDirection;
1387 } else {
1388 before.rotateCW();
1389 after.rotateCW();
1390 arcDir = kCCW_SkRotationDirection;
1391 }
1392
1393 SkMatrix matrix;
1394 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001395
reed@android.com8a1c16f2008-12-17 15:59:43 +00001396 matrix.setScale(radius, radius);
1397 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1398 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001399
reed@android.com8a1c16f2008-12-17 15:59:43 +00001400 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001401
reed@android.com8a1c16f2008-12-17 15:59:43 +00001402 this->incReserve(count);
1403 // [xx,yy] == pts[0]
1404 this->lineTo(xx, yy);
1405 for (int i = 1; i < count; i += 2) {
1406 this->quadTo(pts[i], pts[i+1]);
1407 }
1408}
1409
1410///////////////////////////////////////////////////////////////////////////////
1411
1412void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1413 SkMatrix matrix;
1414
1415 matrix.setTranslate(dx, dy);
1416 this->addPath(path, matrix);
1417}
1418
1419void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001420 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001421
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001422 fIsOval = false;
1423
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001424 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001425 SkPoint pts[4];
1426 Verb verb;
1427
1428 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1429
1430 while ((verb = iter.next(pts)) != kDone_Verb) {
1431 switch (verb) {
1432 case kMove_Verb:
1433 proc(matrix, &pts[0], &pts[0], 1);
1434 this->moveTo(pts[0]);
1435 break;
1436 case kLine_Verb:
1437 proc(matrix, &pts[1], &pts[1], 1);
1438 this->lineTo(pts[1]);
1439 break;
1440 case kQuad_Verb:
1441 proc(matrix, &pts[1], &pts[1], 2);
1442 this->quadTo(pts[1], pts[2]);
1443 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001444 case kConic_Verb:
1445 proc(matrix, &pts[1], &pts[1], 2);
1446 this->conicTo(pts[1], pts[2], iter.conicWeight());
1447 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001448 case kCubic_Verb:
1449 proc(matrix, &pts[1], &pts[1], 3);
1450 this->cubicTo(pts[1], pts[2], pts[3]);
1451 break;
1452 case kClose_Verb:
1453 this->close();
1454 break;
1455 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001456 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001457 }
1458 }
1459}
1460
1461///////////////////////////////////////////////////////////////////////////////
1462
reed@google.com277c3f82013-05-31 15:17:50 +00001463static int pts_in_verb(unsigned verb) {
1464 static const uint8_t gPtsInVerb[] = {
1465 1, // kMove
1466 1, // kLine
1467 2, // kQuad
1468 2, // kConic
1469 3, // kCubic
1470 0, // kClose
1471 0 // kDone
1472 };
1473
1474 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1475 return gPtsInVerb[verb];
1476}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001477
1478// ignore the initial moveto, and stop when the 1st contour ends
1479void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001480 int i, vcount = path.fPathRef->countVerbs();
1481 // exit early if the path is empty, or just has a moveTo.
1482 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001483 return;
1484 }
1485
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001486 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001487
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001488 fIsOval = false;
1489
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001490 const uint8_t* verbs = path.fPathRef->verbs();
1491 // skip the initial moveTo
1492 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001493 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001494
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001495 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001496 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001497 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001498 case kLine_Verb:
1499 this->lineTo(pts[0].fX, pts[0].fY);
1500 break;
1501 case kQuad_Verb:
1502 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1503 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001504 case kConic_Verb:
1505 this->conicTo(pts[0], pts[1], *conicWeight++);
1506 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001507 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001508 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 +00001509 break;
1510 case kClose_Verb:
1511 return;
1512 }
reed@google.com277c3f82013-05-31 15:17:50 +00001513 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001514 }
1515}
1516
1517// ignore the last point of the 1st contour
1518void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001519 int i, vcount = path.fPathRef->countVerbs();
1520 // exit early if the path is empty, or just has a moveTo.
1521 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001522 return;
1523 }
1524
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001525 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001526
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001527 fIsOval = false;
1528
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001529 const uint8_t* verbs = path.fPathRef->verbs();
1530 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001531 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001533 SkASSERT(verbs[~0] == kMove_Verb);
1534 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001535 unsigned v = verbs[~i];
1536 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001537 if (n == 0) {
1538 break;
1539 }
1540 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001541 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001542 }
1543
1544 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001545 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001546 case kLine_Verb:
1547 this->lineTo(pts[-1].fX, pts[-1].fY);
1548 break;
1549 case kQuad_Verb:
1550 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1551 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001552 case kConic_Verb:
1553 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1554 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001555 case kCubic_Verb:
1556 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1557 pts[-3].fX, pts[-3].fY);
1558 break;
1559 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001560 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001561 break;
1562 }
reed@google.com277c3f82013-05-31 15:17:50 +00001563 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001564 }
1565}
1566
reed@google.com63d73742012-01-10 15:33:12 +00001567void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001568 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001569
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001570 const SkPoint* pts = src.fPathRef->pointsEnd();
1571 // we will iterator through src's verbs backwards
1572 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1573 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001574 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001575
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001576 fIsOval = false;
1577
reed@google.com63d73742012-01-10 15:33:12 +00001578 bool needMove = true;
1579 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001580 while (verbs < verbsEnd) {
1581 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001582 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001583
1584 if (needMove) {
1585 --pts;
1586 this->moveTo(pts->fX, pts->fY);
1587 needMove = false;
1588 }
1589 pts -= n;
1590 switch (v) {
1591 case kMove_Verb:
1592 if (needClose) {
1593 this->close();
1594 needClose = false;
1595 }
1596 needMove = true;
1597 pts += 1; // so we see the point in "if (needMove)" above
1598 break;
1599 case kLine_Verb:
1600 this->lineTo(pts[0]);
1601 break;
1602 case kQuad_Verb:
1603 this->quadTo(pts[1], pts[0]);
1604 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001605 case kConic_Verb:
1606 this->conicTo(pts[1], pts[0], *--conicWeights);
1607 break;
reed@google.com63d73742012-01-10 15:33:12 +00001608 case kCubic_Verb:
1609 this->cubicTo(pts[2], pts[1], pts[0]);
1610 break;
1611 case kClose_Verb:
1612 needClose = true;
1613 break;
1614 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001615 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001616 }
1617 }
1618}
1619
reed@android.com8a1c16f2008-12-17 15:59:43 +00001620///////////////////////////////////////////////////////////////////////////////
1621
1622void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1623 SkMatrix matrix;
1624
1625 matrix.setTranslate(dx, dy);
1626 this->transform(matrix, dst);
1627}
1628
1629#include "SkGeometry.h"
1630
1631static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1632 int level = 2) {
1633 if (--level >= 0) {
1634 SkPoint tmp[5];
1635
1636 SkChopQuadAtHalf(pts, tmp);
1637 subdivide_quad_to(path, &tmp[0], level);
1638 subdivide_quad_to(path, &tmp[2], level);
1639 } else {
1640 path->quadTo(pts[1], pts[2]);
1641 }
1642}
1643
1644static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1645 int level = 2) {
1646 if (--level >= 0) {
1647 SkPoint tmp[7];
1648
1649 SkChopCubicAtHalf(pts, tmp);
1650 subdivide_cubic_to(path, &tmp[0], level);
1651 subdivide_cubic_to(path, &tmp[3], level);
1652 } else {
1653 path->cubicTo(pts[1], pts[2], pts[3]);
1654 }
1655}
1656
1657void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1658 SkDEBUGCODE(this->validate();)
1659 if (dst == NULL) {
1660 dst = (SkPath*)this;
1661 }
1662
tomhudson@google.com8d430182011-06-06 19:11:19 +00001663 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001664 SkPath tmp;
1665 tmp.fFillType = fFillType;
1666
1667 SkPath::Iter iter(*this, false);
1668 SkPoint pts[4];
1669 SkPath::Verb verb;
1670
reed@google.com4a3b7142012-05-16 17:16:46 +00001671 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001672 switch (verb) {
1673 case kMove_Verb:
1674 tmp.moveTo(pts[0]);
1675 break;
1676 case kLine_Verb:
1677 tmp.lineTo(pts[1]);
1678 break;
1679 case kQuad_Verb:
1680 subdivide_quad_to(&tmp, pts);
1681 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001682 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001683 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001684 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1685 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001686 case kCubic_Verb:
1687 subdivide_cubic_to(&tmp, pts);
1688 break;
1689 case kClose_Verb:
1690 tmp.close();
1691 break;
1692 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001693 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001694 break;
1695 }
1696 }
1697
1698 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001699 SkPathRef::Editor ed(&dst->fPathRef);
1700 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001701 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001702 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001703 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1704
reed@android.com8a1c16f2008-12-17 15:59:43 +00001705 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001707 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001708 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001709 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001710
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001711 if (kUnknown_Direction == fDirection) {
1712 dst->fDirection = kUnknown_Direction;
1713 } else {
1714 SkScalar det2x2 =
1715 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1716 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1717 if (det2x2 < 0) {
1718 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1719 } else if (det2x2 > 0) {
1720 dst->fDirection = fDirection;
1721 } else {
1722 dst->fDirection = kUnknown_Direction;
1723 }
1724 }
1725
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001726 // It's an oval only if it stays a rect.
1727 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001728
reed@android.com8a1c16f2008-12-17 15:59:43 +00001729 SkDEBUGCODE(dst->validate();)
1730 }
1731}
1732
reed@android.com8a1c16f2008-12-17 15:59:43 +00001733///////////////////////////////////////////////////////////////////////////////
1734///////////////////////////////////////////////////////////////////////////////
1735
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001736enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001737 kEmptyContour_SegmentState, // The current contour is empty. We may be
1738 // starting processing or we may have just
1739 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001740 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1741 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1742 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743};
1744
1745SkPath::Iter::Iter() {
1746#ifdef SK_DEBUG
1747 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001748 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001749 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001750 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001751 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001752#endif
1753 // need to init enough to make next() harmlessly return kDone_Verb
1754 fVerbs = NULL;
1755 fVerbStop = NULL;
1756 fNeedClose = false;
1757}
1758
1759SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1760 this->setPath(path, forceClose);
1761}
1762
1763void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001764 fPts = path.fPathRef->points();
1765 fVerbs = path.fPathRef->verbs();
1766 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001767 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001768 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001769 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001770 fForceClose = SkToU8(forceClose);
1771 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001772 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001773}
1774
1775bool SkPath::Iter::isClosedContour() const {
1776 if (fVerbs == NULL || fVerbs == fVerbStop) {
1777 return false;
1778 }
1779 if (fForceClose) {
1780 return true;
1781 }
1782
1783 const uint8_t* verbs = fVerbs;
1784 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001785
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001786 if (kMove_Verb == *(verbs - 1)) {
1787 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001788 }
1789
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001790 while (verbs > stop) {
1791 // verbs points one beyond the current verb, decrement first.
1792 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001793 if (kMove_Verb == v) {
1794 break;
1795 }
1796 if (kClose_Verb == v) {
1797 return true;
1798 }
1799 }
1800 return false;
1801}
1802
1803SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001804 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001805 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001806 // A special case: if both points are NaN, SkPoint::operation== returns
1807 // false, but the iterator expects that they are treated as the same.
1808 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001809 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1810 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001811 return kClose_Verb;
1812 }
1813
reed@google.com9e25dbf2012-05-15 17:05:38 +00001814 pts[0] = fLastPt;
1815 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001816 fLastPt = fMoveTo;
1817 fCloseLine = true;
1818 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001819 } else {
1820 pts[0] = fMoveTo;
1821 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001823}
1824
reed@google.com9e25dbf2012-05-15 17:05:38 +00001825const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001826 if (fSegmentState == kAfterMove_SegmentState) {
1827 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001828 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001829 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001830 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001831 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1832 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001833 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001834 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001835}
1836
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001837void SkPath::Iter::consumeDegenerateSegments() {
1838 // We need to step over anything that will not move the current draw point
1839 // forward before the next move is seen
1840 const uint8_t* lastMoveVerb = 0;
1841 const SkPoint* lastMovePt = 0;
1842 SkPoint lastPt = fLastPt;
1843 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001844 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001845 switch (verb) {
1846 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001847 // Keep a record of this most recent move
1848 lastMoveVerb = fVerbs;
1849 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001850 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001851 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001852 fPts++;
1853 break;
1854
1855 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001856 // A close when we are in a segment is always valid except when it
1857 // follows a move which follows a segment.
1858 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001859 return;
1860 }
1861 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001862 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001863 break;
1864
1865 case kLine_Verb:
1866 if (!IsLineDegenerate(lastPt, fPts[0])) {
1867 if (lastMoveVerb) {
1868 fVerbs = lastMoveVerb;
1869 fPts = lastMovePt;
1870 return;
1871 }
1872 return;
1873 }
1874 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001875 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001876 fPts++;
1877 break;
1878
reed@google.com277c3f82013-05-31 15:17:50 +00001879 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001880 case kQuad_Verb:
1881 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1882 if (lastMoveVerb) {
1883 fVerbs = lastMoveVerb;
1884 fPts = lastMovePt;
1885 return;
1886 }
1887 return;
1888 }
1889 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001890 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001891 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001892 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001893 break;
1894
1895 case kCubic_Verb:
1896 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1897 if (lastMoveVerb) {
1898 fVerbs = lastMoveVerb;
1899 fPts = lastMovePt;
1900 return;
1901 }
1902 return;
1903 }
1904 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001905 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001906 fPts += 3;
1907 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001908
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001909 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001910 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001911 }
1912 }
1913}
1914
reed@google.com4a3b7142012-05-16 17:16:46 +00001915SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001916 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001917
reed@android.com8a1c16f2008-12-17 15:59:43 +00001918 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001919 // Close the curve if requested and if there is some curve to close
1920 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001921 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001922 return kLine_Verb;
1923 }
1924 fNeedClose = false;
1925 return kClose_Verb;
1926 }
1927 return kDone_Verb;
1928 }
1929
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001930 // fVerbs is one beyond the current verb, decrement first
1931 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001932 const SkPoint* SK_RESTRICT srcPts = fPts;
1933 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001934
1935 switch (verb) {
1936 case kMove_Verb:
1937 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001938 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001939 verb = this->autoClose(pts);
1940 if (verb == kClose_Verb) {
1941 fNeedClose = false;
1942 }
1943 return (Verb)verb;
1944 }
1945 if (fVerbs == fVerbStop) { // might be a trailing moveto
1946 return kDone_Verb;
1947 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001948 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001949 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001950 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001951 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001952 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001953 fNeedClose = fForceClose;
1954 break;
1955 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001956 pts[0] = this->cons_moveTo();
1957 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001958 fLastPt = srcPts[0];
1959 fCloseLine = false;
1960 srcPts += 1;
1961 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001962 case kConic_Verb:
1963 fConicWeights += 1;
1964 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001965 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001966 pts[0] = this->cons_moveTo();
1967 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001968 fLastPt = srcPts[1];
1969 srcPts += 2;
1970 break;
1971 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001972 pts[0] = this->cons_moveTo();
1973 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00001974 fLastPt = srcPts[2];
1975 srcPts += 3;
1976 break;
1977 case kClose_Verb:
1978 verb = this->autoClose(pts);
1979 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001980 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001981 } else {
1982 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001983 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001985 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001986 break;
1987 }
1988 fPts = srcPts;
1989 return (Verb)verb;
1990}
1991
1992///////////////////////////////////////////////////////////////////////////////
1993
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001994SkPath::RawIter::RawIter() {
1995#ifdef SK_DEBUG
1996 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001997 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001998 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
1999#endif
2000 // need to init enough to make next() harmlessly return kDone_Verb
2001 fVerbs = NULL;
2002 fVerbStop = NULL;
2003}
2004
2005SkPath::RawIter::RawIter(const SkPath& path) {
2006 this->setPath(path);
2007}
2008
2009void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002010 fPts = path.fPathRef->points();
2011 fVerbs = path.fPathRef->verbs();
2012 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002013 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002014 fMoveTo.fX = fMoveTo.fY = 0;
2015 fLastPt.fX = fLastPt.fY = 0;
2016}
2017
2018SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002019 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002020 if (fVerbs == fVerbStop) {
2021 return kDone_Verb;
2022 }
2023
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002024 // fVerbs points one beyond next verb so decrement first.
2025 unsigned verb = *(--fVerbs);
2026 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002027
2028 switch (verb) {
2029 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002030 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002031 fMoveTo = srcPts[0];
2032 fLastPt = fMoveTo;
2033 srcPts += 1;
2034 break;
2035 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002036 pts[0] = fLastPt;
2037 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002038 fLastPt = srcPts[0];
2039 srcPts += 1;
2040 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002041 case kConic_Verb:
2042 fConicWeights += 1;
2043 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002044 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002045 pts[0] = fLastPt;
2046 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002047 fLastPt = srcPts[1];
2048 srcPts += 2;
2049 break;
2050 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002051 pts[0] = fLastPt;
2052 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002053 fLastPt = srcPts[2];
2054 srcPts += 3;
2055 break;
2056 case kClose_Verb:
2057 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002058 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002059 break;
2060 }
2061 fPts = srcPts;
2062 return (Verb)verb;
2063}
2064
2065///////////////////////////////////////////////////////////////////////////////
2066
reed@android.com8a1c16f2008-12-17 15:59:43 +00002067/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002068 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002069*/
2070
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002071uint32_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002072 SkDEBUGCODE(this->validate();)
2073
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002074 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002075 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002076 return SkAlign4(byteCount);
2077 }
2078
2079 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002080
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002081 int32_t packed = ((fIsOval & 1) << kIsOval_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002082 (fConvexity << kConvexity_SerializationShift) |
2083 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002084 (fSegmentMask << kSegmentMask_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002085 (fDirection << kDirection_SerializationShift)
2086#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2087 | (0x1 << kNewFormat_SerializationShift);
2088#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002089
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002090 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002091
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002092 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002093
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002094 buffer.padToAlign4();
scroggo@google.com614f9e32013-05-09 18:05:32 +00002095 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002096}
2097
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002098uint32_t SkPath::readFromMemory(const void* storage) {
2099 SkRBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002100
reed@google.com98b11f12011-09-21 18:40:27 +00002101 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002102 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2103 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2104 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002105 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002106 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002107#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2108 bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
2109#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002110
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002111 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer
2112#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2113 , newFormat, packed)
2114#endif
2115 );
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002116
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002117 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002118
reed@android.com8a1c16f2008-12-17 15:59:43 +00002119 SkDEBUGCODE(this->validate();)
scroggo@google.com614f9e32013-05-09 18:05:32 +00002120 return SkToU32(buffer.pos());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002121}
2122
2123///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002124
reed@google.com51bbe752013-01-17 22:07:50 +00002125#include "SkString.h"
2126
2127static void append_scalar(SkString* str, SkScalar value) {
2128 SkString tmp;
2129 tmp.printf("%g", value);
2130 if (tmp.contains('.')) {
2131 tmp.appendUnichar('f');
2132 }
2133 str->append(tmp);
2134}
2135
2136static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002137 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002138 str->append(label);
2139 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002140
reed@google.com51bbe752013-01-17 22:07:50 +00002141 const SkScalar* values = &pts[0].fX;
2142 count *= 2;
2143
2144 for (int i = 0; i < count; ++i) {
2145 append_scalar(str, values[i]);
2146 if (i < count - 1) {
2147 str->append(", ");
2148 }
2149 }
reed@google.com277c3f82013-05-31 15:17:50 +00002150 if (conicWeight >= 0) {
2151 str->append(", ");
2152 append_scalar(str, conicWeight);
2153 }
reed@google.com51bbe752013-01-17 22:07:50 +00002154 str->append(");\n");
2155}
2156
reed@android.com8a1c16f2008-12-17 15:59:43 +00002157void SkPath::dump(bool forceClose, const char title[]) const {
2158 Iter iter(*this, forceClose);
2159 SkPoint pts[4];
2160 Verb verb;
2161
2162 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2163 title ? title : "");
2164
reed@google.com51bbe752013-01-17 22:07:50 +00002165 SkString builder;
2166
reed@google.com4a3b7142012-05-16 17:16:46 +00002167 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002168 switch (verb) {
2169 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002170 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002171 break;
2172 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002173 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002174 break;
2175 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002176 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002177 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002178 case kConic_Verb:
2179 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2180 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002181 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002182 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002183 break;
2184 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002185 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002186 break;
2187 default:
2188 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2189 verb = kDone_Verb; // stop the loop
2190 break;
2191 }
2192 }
reed@google.com51bbe752013-01-17 22:07:50 +00002193 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002194}
2195
reed@android.come522ca52009-11-23 20:10:41 +00002196void SkPath::dump() const {
2197 this->dump(false);
2198}
2199
2200#ifdef SK_DEBUG
2201void SkPath::validate() const {
2202 SkASSERT(this != NULL);
2203 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002204
djsollen@google.com077348c2012-10-22 20:23:32 +00002205#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002206 if (!fBoundsIsDirty) {
2207 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002208
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002209 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002210 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002211
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002212 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002213 // if we're empty, fBounds may be empty but translated, so we can't
2214 // necessarily compare to bounds directly
2215 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2216 // be [2, 2, 2, 2]
2217 SkASSERT(bounds.isEmpty());
2218 SkASSERT(fBounds.isEmpty());
2219 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002220 if (bounds.isEmpty()) {
2221 SkASSERT(fBounds.isEmpty());
2222 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002223 if (!fBounds.isEmpty()) {
2224 SkASSERT(fBounds.contains(bounds));
2225 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002226 }
reed@android.come522ca52009-11-23 20:10:41 +00002227 }
2228 }
reed@google.com10296cc2011-09-21 12:29:05 +00002229
2230 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002231 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2232 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2233 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002234 case kLine_Verb:
2235 mask |= kLine_SegmentMask;
2236 break;
2237 case kQuad_Verb:
2238 mask |= kQuad_SegmentMask;
2239 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002240 case kConic_Verb:
2241 mask |= kConic_SegmentMask;
2242 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002243 case kCubic_Verb:
2244 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002245 case kMove_Verb: // these verbs aren't included in the segment mask.
2246 case kClose_Verb:
2247 break;
2248 case kDone_Verb:
2249 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2250 break;
2251 default:
2252 SkDEBUGFAIL("Unknown Verb");
2253 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002254 }
2255 }
2256 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002257#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002258}
djsollen@google.com077348c2012-10-22 20:23:32 +00002259#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002260
reed@google.com04863fa2011-05-15 04:08:24 +00002261///////////////////////////////////////////////////////////////////////////////
2262
reed@google.com0b7b9822011-05-16 12:29:27 +00002263static int sign(SkScalar x) { return x < 0; }
2264#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002265
reed@google.com04863fa2011-05-15 04:08:24 +00002266static int CrossProductSign(const SkVector& a, const SkVector& b) {
bsalomon@google.com647a8042011-08-23 14:39:01 +00002267 return SkScalarSignAsInt(SkPoint::CrossProduct(a, b));
reed@google.com04863fa2011-05-15 04:08:24 +00002268}
2269
2270// only valid for a single contour
2271struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002272 Convexicator()
2273 : fPtCount(0)
2274 , fConvexity(SkPath::kConvex_Convexity)
2275 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002276 fSign = 0;
2277 // warnings
2278 fCurrPt.set(0, 0);
2279 fVec0.set(0, 0);
2280 fVec1.set(0, 0);
2281 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002282
2283 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002284 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002285 }
2286
2287 SkPath::Convexity getConvexity() const { return fConvexity; }
2288
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002289 /** The direction returned is only valid if the path is determined convex */
2290 SkPath::Direction getDirection() const { return fDirection; }
2291
reed@google.com04863fa2011-05-15 04:08:24 +00002292 void addPt(const SkPoint& pt) {
2293 if (SkPath::kConcave_Convexity == fConvexity) {
2294 return;
2295 }
2296
2297 if (0 == fPtCount) {
2298 fCurrPt = pt;
2299 ++fPtCount;
2300 } else {
2301 SkVector vec = pt - fCurrPt;
2302 if (vec.fX || vec.fY) {
2303 fCurrPt = pt;
2304 if (++fPtCount == 2) {
2305 fFirstVec = fVec1 = vec;
2306 } else {
2307 SkASSERT(fPtCount > 2);
2308 this->addVec(vec);
2309 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002310
reed@google.com85b6e392011-05-15 20:25:17 +00002311 int sx = sign(vec.fX);
2312 int sy = sign(vec.fY);
2313 fDx += (sx != fSx);
2314 fDy += (sy != fSy);
2315 fSx = sx;
2316 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002317
reed@google.com85b6e392011-05-15 20:25:17 +00002318 if (fDx > 3 || fDy > 3) {
2319 fConvexity = SkPath::kConcave_Convexity;
2320 }
reed@google.com04863fa2011-05-15 04:08:24 +00002321 }
2322 }
2323 }
2324
2325 void close() {
2326 if (fPtCount > 2) {
2327 this->addVec(fFirstVec);
2328 }
2329 }
2330
2331private:
2332 void addVec(const SkVector& vec) {
2333 SkASSERT(vec.fX || vec.fY);
2334 fVec0 = fVec1;
2335 fVec1 = vec;
2336 int sign = CrossProductSign(fVec0, fVec1);
2337 if (0 == fSign) {
2338 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002339 if (1 == sign) {
2340 fDirection = SkPath::kCW_Direction;
2341 } else if (-1 == sign) {
2342 fDirection = SkPath::kCCW_Direction;
2343 }
reed@google.com04863fa2011-05-15 04:08:24 +00002344 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002345 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002346 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002347 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002348 }
2349 }
2350 }
2351
2352 SkPoint fCurrPt;
2353 SkVector fVec0, fVec1, fFirstVec;
2354 int fPtCount; // non-degenerate points
2355 int fSign;
2356 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002357 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002358 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002359};
2360
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002361SkPath::Convexity SkPath::internalGetConvexity() const {
2362 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002363 SkPoint pts[4];
2364 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002365 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002366
2367 int contourCount = 0;
2368 int count;
2369 Convexicator state;
2370
2371 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2372 switch (verb) {
2373 case kMove_Verb:
2374 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002375 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002376 return kConcave_Convexity;
2377 }
2378 pts[1] = pts[0];
2379 count = 1;
2380 break;
2381 case kLine_Verb: count = 1; break;
2382 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002383 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002384 case kCubic_Verb: count = 3; break;
2385 case kClose_Verb:
2386 state.close();
2387 count = 0;
2388 break;
2389 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002390 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002391 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002392 return kConcave_Convexity;
2393 }
2394
2395 for (int i = 1; i <= count; i++) {
2396 state.addPt(pts[i]);
2397 }
2398 // early exit
2399 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002400 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002401 return kConcave_Convexity;
2402 }
2403 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002404 fConvexity = state.getConvexity();
2405 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2406 fDirection = state.getDirection();
2407 }
2408 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002409}
reed@google.com69a99432012-01-10 18:00:10 +00002410
2411///////////////////////////////////////////////////////////////////////////////
2412
2413class ContourIter {
2414public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002415 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002416
2417 bool done() const { return fDone; }
2418 // if !done() then these may be called
2419 int count() const { return fCurrPtCount; }
2420 const SkPoint* pts() const { return fCurrPt; }
2421 void next();
2422
2423private:
2424 int fCurrPtCount;
2425 const SkPoint* fCurrPt;
2426 const uint8_t* fCurrVerb;
2427 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002428 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002429 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002430 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002431};
2432
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002433ContourIter::ContourIter(const SkPathRef& pathRef) {
2434 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002435 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002436 fCurrPt = pathRef.points();
2437 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002438 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002439 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002440 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002441 this->next();
2442}
2443
2444void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002445 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002446 fDone = true;
2447 }
2448 if (fDone) {
2449 return;
2450 }
2451
2452 // skip pts of prev contour
2453 fCurrPt += fCurrPtCount;
2454
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002455 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002456 int ptCount = 1; // moveTo
2457 const uint8_t* verbs = fCurrVerb;
2458
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002459 for (--verbs; verbs > fStopVerbs; --verbs) {
2460 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002461 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002462 goto CONTOUR_END;
2463 case SkPath::kLine_Verb:
2464 ptCount += 1;
2465 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002466 case SkPath::kConic_Verb:
2467 fCurrConicWeight += 1;
2468 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002469 case SkPath::kQuad_Verb:
2470 ptCount += 2;
2471 break;
2472 case SkPath::kCubic_Verb:
2473 ptCount += 3;
2474 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002475 case SkPath::kClose_Verb:
2476 break;
2477 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002478 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002479 break;
2480 }
2481 }
2482CONTOUR_END:
2483 fCurrPtCount = ptCount;
2484 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002485 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002486}
2487
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002488// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002489static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002490 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2491 // We may get 0 when the above subtracts underflow. We expect this to be
2492 // very rare and lazily promote to double.
2493 if (0 == cross) {
2494 double p0x = SkScalarToDouble(p0.fX);
2495 double p0y = SkScalarToDouble(p0.fY);
2496
2497 double p1x = SkScalarToDouble(p1.fX);
2498 double p1y = SkScalarToDouble(p1.fY);
2499
2500 double p2x = SkScalarToDouble(p2.fX);
2501 double p2y = SkScalarToDouble(p2.fY);
2502
2503 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2504 (p1y - p0y) * (p2x - p0x));
2505
2506 }
2507 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002508}
2509
reed@google.comc1ea60a2012-01-31 15:15:36 +00002510// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002511static int find_max_y(const SkPoint pts[], int count) {
2512 SkASSERT(count > 0);
2513 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002514 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002515 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002516 SkScalar y = pts[i].fY;
2517 if (y > max) {
2518 max = y;
2519 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002520 }
2521 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002522 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002523}
2524
reed@google.comcabaf1d2012-01-11 21:03:05 +00002525static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2526 int i = index;
2527 for (;;) {
2528 i = (i + inc) % n;
2529 if (i == index) { // we wrapped around, so abort
2530 break;
2531 }
2532 if (pts[index] != pts[i]) { // found a different point, success!
2533 break;
2534 }
2535 }
2536 return i;
2537}
2538
reed@google.comc1ea60a2012-01-31 15:15:36 +00002539/**
2540 * Starting at index, and moving forward (incrementing), find the xmin and
2541 * xmax of the contiguous points that have the same Y.
2542 */
2543static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2544 int* maxIndexPtr) {
2545 const SkScalar y = pts[index].fY;
2546 SkScalar min = pts[index].fX;
2547 SkScalar max = min;
2548 int minIndex = index;
2549 int maxIndex = index;
2550 for (int i = index + 1; i < n; ++i) {
2551 if (pts[i].fY != y) {
2552 break;
2553 }
2554 SkScalar x = pts[i].fX;
2555 if (x < min) {
2556 min = x;
2557 minIndex = i;
2558 } else if (x > max) {
2559 max = x;
2560 maxIndex = i;
2561 }
2562 }
2563 *maxIndexPtr = maxIndex;
2564 return minIndex;
2565}
2566
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002567static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002568 if (dir) {
2569 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2570 }
reed@google.comac8543f2012-01-30 20:51:25 +00002571}
2572
reed@google.comc1ea60a2012-01-31 15:15:36 +00002573#if 0
2574#include "SkString.h"
2575#include "../utils/SkParsePath.h"
2576static void dumpPath(const SkPath& path) {
2577 SkString str;
2578 SkParsePath::ToSVGString(path, &str);
2579 SkDebugf("%s\n", str.c_str());
2580}
2581#endif
2582
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002583namespace {
2584// for use with convex_dir_test
2585double mul(double a, double b) { return a * b; }
2586SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2587double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2588SkScalar toScalar(SkScalar a) { return a; }
2589
2590// determines the winding direction of a convex polygon with the precision
2591// of T. CAST_SCALAR casts an SkScalar to T.
2592template <typename T, T (CAST_SCALAR)(SkScalar)>
2593bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2594 // we find the first three points that form a non-degenerate
2595 // triangle. If there are no such points then the path is
2596 // degenerate. The first is always point 0. Now we find the second
2597 // point.
2598 int i = 0;
2599 enum { kX = 0, kY = 1 };
2600 T v0[2];
2601 while (1) {
2602 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2603 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2604 if (v0[kX] || v0[kY]) {
2605 break;
2606 }
2607 if (++i == n - 1) {
2608 return false;
2609 }
2610 }
2611 // now find a third point that is not colinear with the first two
2612 // points and check the orientation of the triangle (which will be
2613 // the same as the orientation of the path).
2614 for (++i; i < n; ++i) {
2615 T v1[2];
2616 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2617 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2618 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2619 if (0 != cross) {
2620 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2621 return true;
2622 }
2623 }
2624 return false;
2625}
2626}
2627
reed@google.comac8543f2012-01-30 20:51:25 +00002628/*
2629 * We loop through all contours, and keep the computed cross-product of the
2630 * contour that contained the global y-max. If we just look at the first
2631 * contour, we may find one that is wound the opposite way (correctly) since
2632 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2633 * that is outer most (or at least has the global y-max) before we can consider
2634 * its cross product.
2635 */
reed@google.com69a99432012-01-10 18:00:10 +00002636bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002637// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002638 // don't want to pay the cost for computing this if it
2639 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002640
2641 if (kUnknown_Direction != fDirection) {
2642 *dir = static_cast<Direction>(fDirection);
2643 return true;
2644 }
reed@google.com69a99432012-01-10 18:00:10 +00002645 const Convexity conv = this->getConvexityOrUnknown();
2646
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002647 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002648
reed@google.comac8543f2012-01-30 20:51:25 +00002649 // initialize with our logical y-min
2650 SkScalar ymax = this->getBounds().fTop;
2651 SkScalar ymaxCross = 0;
2652
reed@google.com69a99432012-01-10 18:00:10 +00002653 for (; !iter.done(); iter.next()) {
2654 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002655 if (n < 3) {
2656 continue;
2657 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002658
reed@google.comcabaf1d2012-01-11 21:03:05 +00002659 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002660 SkScalar cross = 0;
2661 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002662 // We try first at scalar precision, and then again at double
2663 // precision. This is because the vectors computed between distant
2664 // points may lose too much precision.
2665 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002666 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002667 return true;
2668 }
2669 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002670 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002671 return true;
2672 } else {
2673 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002674 }
2675 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002676 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002677 if (pts[index].fY < ymax) {
2678 continue;
2679 }
2680
reed@google.comc1ea60a2012-01-31 15:15:36 +00002681 // If there is more than 1 distinct point at the y-max, we take the
2682 // x-min and x-max of them and just subtract to compute the dir.
2683 if (pts[(index + 1) % n].fY == pts[index].fY) {
2684 int maxIndex;
2685 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002686 if (minIndex == maxIndex) {
2687 goto TRY_CROSSPROD;
2688 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002689 SkASSERT(pts[minIndex].fY == pts[index].fY);
2690 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2691 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2692 // we just subtract the indices, and let that auto-convert to
2693 // SkScalar, since we just want - or + to signal the direction.
2694 cross = minIndex - maxIndex;
2695 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002696 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002697 // Find a next and prev index to use for the cross-product test,
2698 // but we try to find pts that form non-zero vectors from pts[index]
2699 //
2700 // Its possible that we can't find two non-degenerate vectors, so
2701 // we have to guard our search (e.g. all the pts could be in the
2702 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002703
reed@google.comc1ea60a2012-01-31 15:15:36 +00002704 // we pass n - 1 instead of -1 so we don't foul up % operator by
2705 // passing it a negative LH argument.
2706 int prev = find_diff_pt(pts, index, n, n - 1);
2707 if (prev == index) {
2708 // completely degenerate, skip to next contour
2709 continue;
2710 }
2711 int next = find_diff_pt(pts, index, n, 1);
2712 SkASSERT(next != index);
2713 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002714 // 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 +00002715 // x-direction. We really should continue to walk away from the degeneracy until
2716 // there is a divergence.
2717 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002718 // construct the subtract so we get the correct Direction below
2719 cross = pts[index].fX - pts[next].fX;
2720 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002721 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002722
reed@google.comac8543f2012-01-30 20:51:25 +00002723 if (cross) {
2724 // record our best guess so far
2725 ymax = pts[index].fY;
2726 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002727 }
reed@google.com69a99432012-01-10 18:00:10 +00002728 }
2729 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002730 if (ymaxCross) {
2731 crossToDir(ymaxCross, dir);
2732 fDirection = *dir;
2733 return true;
2734 } else {
2735 return false;
2736 }
reed@google.comac8543f2012-01-30 20:51:25 +00002737}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002738
2739///////////////////////////////////////////////////////////////////////////////
2740
2741static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2742 SkScalar D, SkScalar t) {
2743 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2744}
2745
2746static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2747 SkScalar t) {
2748 SkScalar A = c3 + 3*(c1 - c2) - c0;
2749 SkScalar B = 3*(c2 - c1 - c1 + c0);
2750 SkScalar C = 3*(c1 - c0);
2751 SkScalar D = c0;
2752 return eval_cubic_coeff(A, B, C, D, t);
2753}
2754
2755/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2756 t value such that cubic(t) = target
2757 */
2758static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2759 SkScalar target, SkScalar* t) {
2760 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2761 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002762
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002763 SkScalar D = c0 - target;
2764 SkScalar A = c3 + 3*(c1 - c2) - c0;
2765 SkScalar B = 3*(c2 - c1 - c1 + c0);
2766 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002767
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002768 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2769 SkScalar minT = 0;
2770 SkScalar maxT = SK_Scalar1;
2771 SkScalar mid;
2772 int i;
2773 for (i = 0; i < 16; i++) {
2774 mid = SkScalarAve(minT, maxT);
2775 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2776 if (delta < 0) {
2777 minT = mid;
2778 delta = -delta;
2779 } else {
2780 maxT = mid;
2781 }
2782 if (delta < TOLERANCE) {
2783 break;
2784 }
2785 }
2786 *t = mid;
2787 return true;
2788}
2789
2790template <size_t N> static void find_minmax(const SkPoint pts[],
2791 SkScalar* minPtr, SkScalar* maxPtr) {
2792 SkScalar min, max;
2793 min = max = pts[0].fX;
2794 for (size_t i = 1; i < N; ++i) {
2795 min = SkMinScalar(min, pts[i].fX);
2796 max = SkMaxScalar(max, pts[i].fX);
2797 }
2798 *minPtr = min;
2799 *maxPtr = max;
2800}
2801
2802static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2803 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002804
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002805 int dir = 1;
2806 if (pts[0].fY > pts[3].fY) {
2807 storage[0] = pts[3];
2808 storage[1] = pts[2];
2809 storage[2] = pts[1];
2810 storage[3] = pts[0];
2811 pts = storage;
2812 dir = -1;
2813 }
2814 if (y < pts[0].fY || y >= pts[3].fY) {
2815 return 0;
2816 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002817
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002818 // quickreject or quickaccept
2819 SkScalar min, max;
2820 find_minmax<4>(pts, &min, &max);
2821 if (x < min) {
2822 return 0;
2823 }
2824 if (x > max) {
2825 return dir;
2826 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002827
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002828 // compute the actual x(t) value
2829 SkScalar t, xt;
2830 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2831 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2832 } else {
2833 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2834 xt = y < mid ? pts[0].fX : pts[3].fX;
2835 }
2836 return xt < x ? dir : 0;
2837}
2838
2839static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2840 SkPoint dst[10];
2841 int n = SkChopCubicAtYExtrema(pts, dst);
2842 int w = 0;
2843 for (int i = 0; i <= n; ++i) {
2844 w += winding_mono_cubic(&dst[i * 3], x, y);
2845 }
2846 return w;
2847}
2848
2849static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2850 SkScalar y0 = pts[0].fY;
2851 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002852
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002853 int dir = 1;
2854 if (y0 > y2) {
2855 SkTSwap(y0, y2);
2856 dir = -1;
2857 }
2858 if (y < y0 || y >= y2) {
2859 return 0;
2860 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002861
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002862 // bounds check on X (not required. is it faster?)
2863#if 0
2864 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2865 return 0;
2866 }
2867#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002868
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002869 SkScalar roots[2];
2870 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2871 2 * (pts[1].fY - pts[0].fY),
2872 pts[0].fY - y,
2873 roots);
2874 SkASSERT(n <= 1);
2875 SkScalar xt;
2876 if (0 == n) {
2877 SkScalar mid = SkScalarAve(y0, y2);
2878 // Need [0] and [2] if dir == 1
2879 // and [2] and [0] if dir == -1
2880 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2881 } else {
2882 SkScalar t = roots[0];
2883 SkScalar C = pts[0].fX;
2884 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2885 SkScalar B = 2 * (pts[1].fX - C);
2886 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2887 }
2888 return xt < x ? dir : 0;
2889}
2890
2891static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2892 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2893 if (y0 == y1) {
2894 return true;
2895 }
2896 if (y0 < y1) {
2897 return y1 <= y2;
2898 } else {
2899 return y1 >= y2;
2900 }
2901}
2902
2903static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2904 SkPoint dst[5];
2905 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002906
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002907 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2908 n = SkChopQuadAtYExtrema(pts, dst);
2909 pts = dst;
2910 }
2911 int w = winding_mono_quad(pts, x, y);
2912 if (n > 0) {
2913 w += winding_mono_quad(&pts[2], x, y);
2914 }
2915 return w;
2916}
2917
2918static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2919 SkScalar x0 = pts[0].fX;
2920 SkScalar y0 = pts[0].fY;
2921 SkScalar x1 = pts[1].fX;
2922 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002923
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002924 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002925
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002926 int dir = 1;
2927 if (y0 > y1) {
2928 SkTSwap(y0, y1);
2929 dir = -1;
2930 }
2931 if (y < y0 || y >= y1) {
2932 return 0;
2933 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002934
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002935 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2936 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002937
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002938 if (SkScalarSignAsInt(cross) == dir) {
2939 dir = 0;
2940 }
2941 return dir;
2942}
2943
reed@google.com4db592c2013-10-30 17:39:43 +00002944static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
2945 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
2946}
2947
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002948bool SkPath::contains(SkScalar x, SkScalar y) const {
2949 bool isInverse = this->isInverseFillType();
2950 if (this->isEmpty()) {
2951 return isInverse;
2952 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002953
reed@google.com4db592c2013-10-30 17:39:43 +00002954 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002955 return isInverse;
2956 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002957
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002958 SkPath::Iter iter(*this, true);
2959 bool done = false;
2960 int w = 0;
2961 do {
2962 SkPoint pts[4];
2963 switch (iter.next(pts, false)) {
2964 case SkPath::kMove_Verb:
2965 case SkPath::kClose_Verb:
2966 break;
2967 case SkPath::kLine_Verb:
2968 w += winding_line(pts, x, y);
2969 break;
2970 case SkPath::kQuad_Verb:
2971 w += winding_quad(pts, x, y);
2972 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002973 case SkPath::kConic_Verb:
2974 SkASSERT(0);
2975 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002976 case SkPath::kCubic_Verb:
2977 w += winding_cubic(pts, x, y);
2978 break;
2979 case SkPath::kDone_Verb:
2980 done = true;
2981 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002982 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002983 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002984
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002985 switch (this->getFillType()) {
2986 case SkPath::kEvenOdd_FillType:
2987 case SkPath::kInverseEvenOdd_FillType:
2988 w &= 1;
2989 break;
reed@google.come9bb6232012-07-11 18:56:10 +00002990 default:
2991 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002992 }
2993 return SkToBool(w);
2994}