blob: 873f43336405f58386f8f3542093b93eccb0e70c [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
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000953void SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[],
reed@android.com8a1c16f2008-12-17 15:59:43 +0000954 Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +0000955 SkRRect rrect;
956 rrect.setRectRadii(rect, (const SkVector*) radii);
957 this->addRRect(rrect, dir);
reed@android.com8a1c16f2008-12-17 15:59:43 +0000958}
959
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +0000960/* The inline clockwise and counterclockwise round rect quad approximations
961 make it easier to see the symmetry patterns used by add corner quads.
962Clockwise corner value
963 path->lineTo(rect.fLeft, rect.fTop + ry); 0 upper left
964 path->quadTo(rect.fLeft, rect.fTop + offPtY,
965 rect.fLeft + midPtX, rect.fTop + midPtY);
966 path->quadTo(rect.fLeft + offPtX, rect.fTop,
967 rect.fLeft + rx, rect.fTop);
968
969 path->lineTo(rect.fRight - rx, rect.fTop); 1 upper right
970 path->quadTo(rect.fRight - offPtX, rect.fTop,
971 rect.fRight - midPtX, rect.fTop + midPtY);
972 path->quadTo(rect.fRight, rect.fTop + offPtY,
973 rect.fRight, rect.fTop + ry);
974
975 path->lineTo(rect.fRight, rect.fBottom - ry); 2 lower right
976 path->quadTo(rect.fRight, rect.fBottom - offPtY,
977 rect.fRight - midPtX, rect.fBottom - midPtY);
978 path->quadTo(rect.fRight - offPtX, rect.fBottom,
979 rect.fRight - rx, rect.fBottom);
980
981 path->lineTo(rect.fLeft + rx, rect.fBottom); 3 lower left
982 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
983 rect.fLeft + midPtX, rect.fBottom - midPtY);
984 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
985 rect.fLeft, rect.fBottom - ry);
986
987Counterclockwise
988 path->lineTo(rect.fLeft, rect.fBottom - ry); 3 lower left
989 path->quadTo(rect.fLeft, rect.fBottom - offPtY,
990 rect.fLeft + midPtX, rect.fBottom - midPtY);
991 path->quadTo(rect.fLeft + offPtX, rect.fBottom,
992 rect.fLeft + rx, rect.fBottom);
993
994 path->lineTo(rect.fRight - rx, rect.fBottom); 2 lower right
995 path->quadTo(rect.fRight - offPtX, rect.fBottom,
996 rect.fRight - midPtX, rect.fBottom - midPtY);
997 path->quadTo(rect.fRight, rect.fBottom - offPtY,
998 rect.fRight, rect.fBottom - ry);
999
1000 path->lineTo(rect.fRight, rect.fTop + ry); 1 upper right
1001 path->quadTo(rect.fRight, rect.fTop + offPtY,
1002 rect.fRight - midPtX, rect.fTop + midPtY);
1003 path->quadTo(rect.fRight - offPtX, rect.fTop,
1004 rect.fRight - rx, rect.fTop);
1005
1006 path->lineTo(rect.fLeft + rx, rect.fTop); 0 upper left
1007 path->quadTo(rect.fLeft + offPtX, rect.fTop,
1008 rect.fLeft + midPtX, rect.fTop + midPtY);
1009 path->quadTo(rect.fLeft, rect.fTop + offPtY,
1010 rect.fLeft, rect.fTop + ry);
1011*/
1012static void add_corner_quads(SkPath* path, const SkRRect& rrect,
1013 SkRRect::Corner corner, SkPath::Direction dir) {
1014 const SkRect& rect = rrect.rect();
1015 const SkVector& radii = rrect.radii(corner);
1016 SkScalar rx = radii.fX;
1017 SkScalar ry = radii.fY;
1018 // The mid point of the quadratic arc approximation is half way between the two
1019 // control points.
1020 SkScalar midPtX = rx - rx * (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1021 SkScalar midPtY = ry - ry * (SK_Scalar1 + SK_ScalarTanPIOver8) / 2;
1022 SkScalar offPtX = rx - rx * SK_ScalarTanPIOver8;
1023 SkScalar offPtY = ry - ry * SK_ScalarTanPIOver8;
1024 static const int kCornerPts = 5;
1025 SkScalar xOff[kCornerPts];
1026 SkScalar yOff[kCornerPts];
1027
1028 if ((corner & 1) == (dir == SkPath::kCCW_Direction)) { // corners always alternate direction
1029 SkASSERT(dir == SkPath::kCCW_Direction
1030 ? corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperRight_Corner
1031 : corner == SkRRect::kUpperLeft_Corner || corner == SkRRect::kLowerRight_Corner);
1032 xOff[0] = xOff[1] = 0;
1033 xOff[2] = midPtX;
1034 xOff[3] = offPtX;
1035 xOff[4] = rx;
1036 yOff[0] = ry;
1037 yOff[1] = offPtY;
1038 yOff[2] = midPtY;
1039 yOff[3] = yOff[4] = 0;
1040 } else {
1041 xOff[0] = rx;
1042 xOff[1] = offPtX;
1043 xOff[2] = midPtX;
1044 xOff[3] = xOff[4] = 0;
1045 yOff[0] = yOff[1] = 0;
1046 yOff[2] = midPtY;
1047 yOff[3] = offPtY;
1048 yOff[4] = ry;
1049 }
1050 if ((corner - 1) & 2) {
1051 SkASSERT(corner == SkRRect::kLowerLeft_Corner || corner == SkRRect::kUpperLeft_Corner);
1052 for (int i = 0; i < kCornerPts; ++i) {
1053 xOff[i] = rect.fLeft + xOff[i];
1054 }
1055 } else {
1056 SkASSERT(corner == SkRRect::kLowerRight_Corner || corner == SkRRect::kUpperRight_Corner);
1057 for (int i = 0; i < kCornerPts; ++i) {
1058 xOff[i] = rect.fRight - xOff[i];
1059 }
1060 }
1061 if (corner < SkRRect::kLowerRight_Corner) {
1062 for (int i = 0; i < kCornerPts; ++i) {
1063 yOff[i] = rect.fTop + yOff[i];
1064 }
1065 } else {
1066 for (int i = 0; i < kCornerPts; ++i) {
1067 yOff[i] = rect.fBottom - yOff[i];
1068 }
1069 }
1070
1071 SkPoint lastPt;
1072 SkAssertResult(path->getLastPt(&lastPt));
1073 if (lastPt.fX != xOff[0] || lastPt.fY != yOff[0]) {
1074 path->lineTo(xOff[0], yOff[0]);
1075 }
1076 if (rx || ry) {
1077 path->quadTo(xOff[1], yOff[1], xOff[2], yOff[2]);
1078 path->quadTo(xOff[3], yOff[3], xOff[4], yOff[4]);
1079 } else {
1080 path->lineTo(xOff[2], yOff[2]);
1081 path->lineTo(xOff[4], yOff[4]);
1082 }
1083}
1084
reed@google.com4ed0fb72012-12-12 20:48:18 +00001085void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001086 assert_known_direction(dir);
1087
1088 if (rrect.isEmpty()) {
1089 return;
1090 }
1091
reed@google.com4ed0fb72012-12-12 20:48:18 +00001092 const SkRect& bounds = rrect.getBounds();
1093
1094 if (rrect.isRect()) {
1095 this->addRect(bounds, dir);
1096 } else if (rrect.isOval()) {
1097 this->addOval(bounds, dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001098#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
reed@google.com4ed0fb72012-12-12 20:48:18 +00001099 } else if (rrect.isSimple()) {
1100 const SkVector& rad = rrect.getSimpleRadii();
1101 this->addRoundRect(bounds, rad.x(), rad.y(), dir);
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001102#endif
reed@google.com4ed0fb72012-12-12 20:48:18 +00001103 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001104 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001105
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001106 SkAutoPathBoundsUpdate apbu(this, bounds);
1107 SkAutoDisableDirectionCheck(this);
1108
1109 this->incReserve(21);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001110 if (kCW_Direction == dir) {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001111 this->moveTo(bounds.fLeft,
1112 bounds.fBottom - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
1113 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
1114 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1115 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1116 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001117 } else {
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001118 this->moveTo(bounds.fLeft,
1119 bounds.fTop + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
1120 add_corner_quads(this, rrect, SkRRect::kLowerLeft_Corner, dir);
1121 add_corner_quads(this, rrect, SkRRect::kLowerRight_Corner, dir);
1122 add_corner_quads(this, rrect, SkRRect::kUpperRight_Corner, dir);
1123 add_corner_quads(this, rrect, SkRRect::kUpperLeft_Corner, dir);
robertphillips@google.com4e18c7a2012-12-17 21:48:19 +00001124 }
1125 this->close();
reed@google.com4ed0fb72012-12-12 20:48:18 +00001126 }
1127}
1128
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001129bool SkPath::hasOnlyMoveTos() const {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001130 int count = fPathRef->countVerbs();
1131 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbsMemBegin();
1132 for (int i = 0; i < count; ++i) {
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001133 if (*verbs == kLine_Verb ||
1134 *verbs == kQuad_Verb ||
1135 *verbs == kCubic_Verb) {
1136 return false;
1137 }
1138 ++verbs;
1139 }
1140 return true;
1141}
1142
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001143#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001144#define CUBIC_ARC_FACTOR ((SK_ScalarSqrt2 - SK_Scalar1) * 4 / 3)
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001145#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001146
1147void SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
1148 Direction dir) {
1149 assert_known_direction(dir);
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001150
humper@google.com75e3ca12013-04-08 21:44:11 +00001151 if (rx < 0 || ry < 0) {
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001152 SkErrorInternals::SetError( kInvalidArgument_SkError,
humper@google.com75e3ca12013-04-08 21:44:11 +00001153 "I got %f and %f as radii to SkPath::AddRoundRect, "
skia.committer@gmail.com32840172013-04-09 07:01:27 +00001154 "but negative radii are not allowed.",
humper@google.com75e3ca12013-04-08 21:44:11 +00001155 SkScalarToDouble(rx), SkScalarToDouble(ry) );
1156 return;
1157 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001158
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001159#ifdef SK_IGNORE_QUAD_RR_CORNERS_OPT
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001160 SkScalar w = rect.width();
1161 SkScalar halfW = SkScalarHalf(w);
1162 SkScalar h = rect.height();
1163 SkScalar halfH = SkScalarHalf(h);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001164
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001165 if (halfW <= 0 || halfH <= 0) {
1166 return;
1167 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001168
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001169 bool skip_hori = rx >= halfW;
1170 bool skip_vert = ry >= halfH;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001171
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001172 if (skip_hori && skip_vert) {
1173 this->addOval(rect, dir);
1174 return;
1175 }
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001176
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001177 fDirection = this->hasOnlyMoveTos() ? dir : kUnknown_Direction;
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001178
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001179 SkAutoPathBoundsUpdate apbu(this, rect);
1180 SkAutoDisableDirectionCheck(this);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001181
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001182 if (skip_hori) {
1183 rx = halfW;
1184 } else if (skip_vert) {
1185 ry = halfH;
1186 }
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001187 SkScalar sx = SkScalarMul(rx, CUBIC_ARC_FACTOR);
1188 SkScalar sy = SkScalarMul(ry, CUBIC_ARC_FACTOR);
skia.committer@gmail.comd9f65e32013-01-04 12:07:46 +00001189
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001190 this->incReserve(17);
robertphillips@google.com12367192013-10-20 13:11:16 +00001191 this->moveTo(rect.fRight - rx, rect.fTop); // top-right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001192 if (dir == kCCW_Direction) {
1193 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001194 this->lineTo(rect.fLeft + rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001195 }
1196 this->cubicTo(rect.fLeft + rx - sx, rect.fTop,
1197 rect.fLeft, rect.fTop + ry - sy,
1198 rect.fLeft, rect.fTop + ry); // top-left
1199 if (!skip_vert) {
1200 this->lineTo(rect.fLeft, rect.fBottom - ry); // left
1201 }
1202 this->cubicTo(rect.fLeft, rect.fBottom - ry + sy,
1203 rect.fLeft + rx - sx, rect.fBottom,
1204 rect.fLeft + rx, rect.fBottom); // bot-left
1205 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001206 this->lineTo(rect.fRight - rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001207 }
1208 this->cubicTo(rect.fRight - rx + sx, rect.fBottom,
1209 rect.fRight, rect.fBottom - ry + sy,
1210 rect.fRight, rect.fBottom - ry); // bot-right
1211 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001212 this->lineTo(rect.fRight, rect.fTop + ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001213 }
1214 this->cubicTo(rect.fRight, rect.fTop + ry - sy,
1215 rect.fRight - rx + sx, rect.fTop,
1216 rect.fRight - rx, rect.fTop); // top-right
1217 } else {
1218 this->cubicTo(rect.fRight - rx + sx, rect.fTop,
1219 rect.fRight, rect.fTop + ry - sy,
1220 rect.fRight, rect.fTop + ry); // top-right
1221 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001222 this->lineTo(rect.fRight, rect.fBottom - ry); // right
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001223 }
1224 this->cubicTo(rect.fRight, rect.fBottom - ry + sy,
1225 rect.fRight - rx + sx, rect.fBottom,
1226 rect.fRight - rx, rect.fBottom); // bot-right
1227 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001228 this->lineTo(rect.fLeft + rx, rect.fBottom); // bottom
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001229 }
1230 this->cubicTo(rect.fLeft + rx - sx, rect.fBottom,
1231 rect.fLeft, rect.fBottom - ry + sy,
1232 rect.fLeft, rect.fBottom - ry); // bot-left
1233 if (!skip_vert) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001234 this->lineTo(rect.fLeft, rect.fTop + ry); // left
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001235 }
1236 this->cubicTo(rect.fLeft, rect.fTop + ry - sy,
1237 rect.fLeft + rx - sx, rect.fTop,
1238 rect.fLeft + rx, rect.fTop); // top-left
1239 if (!skip_hori) {
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001240 this->lineTo(rect.fRight - rx, rect.fTop); // top
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001241 }
1242 }
1243 this->close();
commit-bot@chromium.org42feaaf2013-11-08 15:51:12 +00001244#else
1245 SkRRect rrect;
1246 rrect.setRectXY(rect, rx, ry);
1247 this->addRRect(rrect, dir);
1248#endif
mike@reedtribe.orgb16033a2013-01-04 03:16:52 +00001249}
1250
reed@android.com8a1c16f2008-12-17 15:59:43 +00001251void SkPath::addOval(const SkRect& oval, Direction dir) {
reed@google.coma8a3b3d2012-11-26 18:16:27 +00001252 assert_known_direction(dir);
1253
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001254 /* If addOval() is called after previous moveTo(),
1255 this path is still marked as an oval. This is used to
1256 fit into WebKit's calling sequences.
1257 We can't simply check isEmpty() in this case, as additional
1258 moveTo() would mark the path non empty.
1259 */
1260 fIsOval = hasOnlyMoveTos();
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001261 if (fIsOval) {
1262 fDirection = dir;
1263 } else {
1264 fDirection = kUnknown_Direction;
1265 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001266
1267 SkAutoDisableOvalCheck adoc(this);
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001268 SkAutoDisableDirectionCheck addc(this);
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001269
reed@android.com8a1c16f2008-12-17 15:59:43 +00001270 SkAutoPathBoundsUpdate apbu(this, oval);
1271
1272 SkScalar cx = oval.centerX();
1273 SkScalar cy = oval.centerY();
1274 SkScalar rx = SkScalarHalf(oval.width());
1275 SkScalar ry = SkScalarHalf(oval.height());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001276
reed@android.com8a1c16f2008-12-17 15:59:43 +00001277 SkScalar sx = SkScalarMul(rx, SK_ScalarTanPIOver8);
1278 SkScalar sy = SkScalarMul(ry, SK_ScalarTanPIOver8);
1279 SkScalar mx = SkScalarMul(rx, SK_ScalarRoot2Over2);
1280 SkScalar my = SkScalarMul(ry, SK_ScalarRoot2Over2);
1281
1282 /*
1283 To handle imprecision in computing the center and radii, we revert to
1284 the provided bounds when we can (i.e. use oval.fLeft instead of cx-rx)
1285 to ensure that we don't exceed the oval's bounds *ever*, since we want
1286 to use oval for our fast-bounds, rather than have to recompute it.
1287 */
1288 const SkScalar L = oval.fLeft; // cx - rx
1289 const SkScalar T = oval.fTop; // cy - ry
1290 const SkScalar R = oval.fRight; // cx + rx
1291 const SkScalar B = oval.fBottom; // cy + ry
1292
1293 this->incReserve(17); // 8 quads + close
1294 this->moveTo(R, cy);
1295 if (dir == kCCW_Direction) {
1296 this->quadTo( R, cy - sy, cx + mx, cy - my);
1297 this->quadTo(cx + sx, T, cx , T);
1298 this->quadTo(cx - sx, T, cx - mx, cy - my);
1299 this->quadTo( L, cy - sy, L, cy );
1300 this->quadTo( L, cy + sy, cx - mx, cy + my);
1301 this->quadTo(cx - sx, B, cx , B);
1302 this->quadTo(cx + sx, B, cx + mx, cy + my);
1303 this->quadTo( R, cy + sy, R, cy );
1304 } else {
1305 this->quadTo( R, cy + sy, cx + mx, cy + my);
1306 this->quadTo(cx + sx, B, cx , B);
1307 this->quadTo(cx - sx, B, cx - mx, cy + my);
1308 this->quadTo( L, cy + sy, L, cy );
1309 this->quadTo( L, cy - sy, cx - mx, cy - my);
1310 this->quadTo(cx - sx, T, cx , T);
1311 this->quadTo(cx + sx, T, cx + mx, cy - my);
1312 this->quadTo( R, cy - sy, R, cy );
1313 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001314 this->close();
1315}
1316
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001317bool SkPath::isOval(SkRect* rect) const {
1318 if (fIsOval && rect) {
1319 *rect = getBounds();
1320 }
1321
1322 return fIsOval;
1323}
1324
reed@android.com8a1c16f2008-12-17 15:59:43 +00001325void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
1326 if (r > 0) {
1327 SkRect rect;
1328 rect.set(x - r, y - r, x + r, y + r);
1329 this->addOval(rect, dir);
1330 }
1331}
1332
reed@android.com8a1c16f2008-12-17 15:59:43 +00001333void SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
1334 bool forceMoveTo) {
1335 if (oval.width() < 0 || oval.height() < 0) {
1336 return;
1337 }
1338
1339 SkPoint pts[kSkBuildQuadArcStorage];
1340 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1341 SkASSERT((count & 1) == 1);
1342
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001343 if (fPathRef->countVerbs() == 0) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001344 forceMoveTo = true;
1345 }
1346 this->incReserve(count);
1347 forceMoveTo ? this->moveTo(pts[0]) : this->lineTo(pts[0]);
1348 for (int i = 1; i < count; i += 2) {
1349 this->quadTo(pts[i], pts[i+1]);
1350 }
1351}
1352
robertphillips@google.com1cc385b2013-10-17 12:17:27 +00001353void SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001354 if (oval.isEmpty() || 0 == sweepAngle) {
1355 return;
1356 }
1357
1358 const SkScalar kFullCircleAngle = SkIntToScalar(360);
1359
1360 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
1361 this->addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction);
1362 return;
1363 }
1364
1365 SkPoint pts[kSkBuildQuadArcStorage];
1366 int count = build_arc_points(oval, startAngle, sweepAngle, pts);
1367
1368 this->incReserve(count);
1369 this->moveTo(pts[0]);
1370 for (int i = 1; i < count; i += 2) {
1371 this->quadTo(pts[i], pts[i+1]);
1372 }
1373}
1374
1375/*
1376 Need to handle the case when the angle is sharp, and our computed end-points
1377 for the arc go behind pt1 and/or p2...
1378*/
1379void SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
1380 SkScalar radius) {
1381 SkVector before, after;
reed@google.comabf15c12011-01-18 20:35:51 +00001382
reed@android.com8a1c16f2008-12-17 15:59:43 +00001383 // need to know our prev pt so we can construct tangent vectors
1384 {
1385 SkPoint start;
1386 this->getLastPt(&start);
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001387 // Handle degenerate cases by adding a line to the first point and
1388 // bailing out.
1389 if ((x1 == start.fX && y1 == start.fY) ||
1390 (x1 == x2 && y1 == y2) ||
1391 radius == 0) {
1392 this->lineTo(x1, y1);
1393 return;
1394 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001395 before.setNormalize(x1 - start.fX, y1 - start.fY);
1396 after.setNormalize(x2 - x1, y2 - y1);
1397 }
reed@google.comabf15c12011-01-18 20:35:51 +00001398
reed@android.com8a1c16f2008-12-17 15:59:43 +00001399 SkScalar cosh = SkPoint::DotProduct(before, after);
1400 SkScalar sinh = SkPoint::CrossProduct(before, after);
1401
1402 if (SkScalarNearlyZero(sinh)) { // angle is too tight
senorblanco@chromium.org60eaa392010-10-13 18:47:00 +00001403 this->lineTo(x1, y1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001404 return;
1405 }
reed@google.comabf15c12011-01-18 20:35:51 +00001406
reed@android.com8a1c16f2008-12-17 15:59:43 +00001407 SkScalar dist = SkScalarMulDiv(radius, SK_Scalar1 - cosh, sinh);
1408 if (dist < 0) {
1409 dist = -dist;
1410 }
1411
1412 SkScalar xx = x1 - SkScalarMul(dist, before.fX);
1413 SkScalar yy = y1 - SkScalarMul(dist, before.fY);
1414 SkRotationDirection arcDir;
1415
1416 // now turn before/after into normals
1417 if (sinh > 0) {
1418 before.rotateCCW();
1419 after.rotateCCW();
1420 arcDir = kCW_SkRotationDirection;
1421 } else {
1422 before.rotateCW();
1423 after.rotateCW();
1424 arcDir = kCCW_SkRotationDirection;
1425 }
1426
1427 SkMatrix matrix;
1428 SkPoint pts[kSkBuildQuadArcStorage];
reed@google.comabf15c12011-01-18 20:35:51 +00001429
reed@android.com8a1c16f2008-12-17 15:59:43 +00001430 matrix.setScale(radius, radius);
1431 matrix.postTranslate(xx - SkScalarMul(radius, before.fX),
1432 yy - SkScalarMul(radius, before.fY));
reed@google.comabf15c12011-01-18 20:35:51 +00001433
reed@android.com8a1c16f2008-12-17 15:59:43 +00001434 int count = SkBuildQuadArc(before, after, arcDir, &matrix, pts);
reed@google.comabf15c12011-01-18 20:35:51 +00001435
reed@android.com8a1c16f2008-12-17 15:59:43 +00001436 this->incReserve(count);
1437 // [xx,yy] == pts[0]
1438 this->lineTo(xx, yy);
1439 for (int i = 1; i < count; i += 2) {
1440 this->quadTo(pts[i], pts[i+1]);
1441 }
1442}
1443
1444///////////////////////////////////////////////////////////////////////////////
1445
1446void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
1447 SkMatrix matrix;
1448
1449 matrix.setTranslate(dx, dy);
1450 this->addPath(path, matrix);
1451}
1452
1453void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001454 SkPathRef::Editor(&fPathRef, path.countVerbs(), path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001455
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001456 fIsOval = false;
1457
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00001458 RawIter iter(path);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001459 SkPoint pts[4];
1460 Verb verb;
1461
1462 SkMatrix::MapPtsProc proc = matrix.getMapPtsProc();
1463
1464 while ((verb = iter.next(pts)) != kDone_Verb) {
1465 switch (verb) {
1466 case kMove_Verb:
1467 proc(matrix, &pts[0], &pts[0], 1);
1468 this->moveTo(pts[0]);
1469 break;
1470 case kLine_Verb:
1471 proc(matrix, &pts[1], &pts[1], 1);
1472 this->lineTo(pts[1]);
1473 break;
1474 case kQuad_Verb:
1475 proc(matrix, &pts[1], &pts[1], 2);
1476 this->quadTo(pts[1], pts[2]);
1477 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001478 case kConic_Verb:
1479 proc(matrix, &pts[1], &pts[1], 2);
1480 this->conicTo(pts[1], pts[2], iter.conicWeight());
1481 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001482 case kCubic_Verb:
1483 proc(matrix, &pts[1], &pts[1], 3);
1484 this->cubicTo(pts[1], pts[2], pts[3]);
1485 break;
1486 case kClose_Verb:
1487 this->close();
1488 break;
1489 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001490 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001491 }
1492 }
1493}
1494
1495///////////////////////////////////////////////////////////////////////////////
1496
reed@google.com277c3f82013-05-31 15:17:50 +00001497static int pts_in_verb(unsigned verb) {
1498 static const uint8_t gPtsInVerb[] = {
1499 1, // kMove
1500 1, // kLine
1501 2, // kQuad
1502 2, // kConic
1503 3, // kCubic
1504 0, // kClose
1505 0 // kDone
1506 };
1507
1508 SkASSERT(verb < SK_ARRAY_COUNT(gPtsInVerb));
1509 return gPtsInVerb[verb];
1510}
reed@android.com8a1c16f2008-12-17 15:59:43 +00001511
1512// ignore the initial moveto, and stop when the 1st contour ends
1513void SkPath::pathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001514 int i, vcount = path.fPathRef->countVerbs();
1515 // exit early if the path is empty, or just has a moveTo.
1516 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001517 return;
1518 }
1519
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001520 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001521
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001522 fIsOval = false;
1523
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001524 const uint8_t* verbs = path.fPathRef->verbs();
1525 // skip the initial moveTo
1526 const SkPoint* pts = path.fPathRef->points() + 1;
reed@google.com277c3f82013-05-31 15:17:50 +00001527 const SkScalar* conicWeight = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001528
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001529 SkASSERT(verbs[~0] == kMove_Verb);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001530 for (i = 1; i < vcount; i++) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001531 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001532 case kLine_Verb:
1533 this->lineTo(pts[0].fX, pts[0].fY);
1534 break;
1535 case kQuad_Verb:
1536 this->quadTo(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY);
1537 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001538 case kConic_Verb:
1539 this->conicTo(pts[0], pts[1], *conicWeight++);
1540 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001541 case kCubic_Verb:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001542 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 +00001543 break;
1544 case kClose_Verb:
1545 return;
1546 }
reed@google.com277c3f82013-05-31 15:17:50 +00001547 pts += pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001548 }
1549}
1550
1551// ignore the last point of the 1st contour
1552void SkPath::reversePathTo(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001553 int i, vcount = path.fPathRef->countVerbs();
1554 // exit early if the path is empty, or just has a moveTo.
1555 if (vcount < 2) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001556 return;
1557 }
1558
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001559 SkPathRef::Editor(&fPathRef, vcount, path.countPoints());
reed@android.com8a1c16f2008-12-17 15:59:43 +00001560
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001561 fIsOval = false;
1562
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001563 const uint8_t* verbs = path.fPathRef->verbs();
1564 const SkPoint* pts = path.fPathRef->points();
reed@google.com277c3f82013-05-31 15:17:50 +00001565 const SkScalar* conicWeights = path.fPathRef->conicWeights();
reed@android.com8a1c16f2008-12-17 15:59:43 +00001566
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001567 SkASSERT(verbs[~0] == kMove_Verb);
1568 for (i = 1; i < vcount; ++i) {
reed@google.com277c3f82013-05-31 15:17:50 +00001569 unsigned v = verbs[~i];
1570 int n = pts_in_verb(v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001571 if (n == 0) {
1572 break;
1573 }
1574 pts += n;
reed@google.com277c3f82013-05-31 15:17:50 +00001575 conicWeights += (SkPath::kConic_Verb == v);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001576 }
1577
1578 while (--i > 0) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001579 switch (verbs[~i]) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001580 case kLine_Verb:
1581 this->lineTo(pts[-1].fX, pts[-1].fY);
1582 break;
1583 case kQuad_Verb:
1584 this->quadTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY);
1585 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001586 case kConic_Verb:
1587 this->conicTo(pts[-1], pts[-2], *--conicWeights);
1588 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001589 case kCubic_Verb:
1590 this->cubicTo(pts[-1].fX, pts[-1].fY, pts[-2].fX, pts[-2].fY,
1591 pts[-3].fX, pts[-3].fY);
1592 break;
1593 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001594 SkDEBUGFAIL("bad verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001595 break;
1596 }
reed@google.com277c3f82013-05-31 15:17:50 +00001597 pts -= pts_in_verb(verbs[~i]);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001598 }
1599}
1600
reed@google.com63d73742012-01-10 15:33:12 +00001601void SkPath::reverseAddPath(const SkPath& src) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001602 SkPathRef::Editor ed(&fPathRef, src.fPathRef->countPoints(), src.fPathRef->countVerbs());
reed@google.com63d73742012-01-10 15:33:12 +00001603
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001604 const SkPoint* pts = src.fPathRef->pointsEnd();
1605 // we will iterator through src's verbs backwards
1606 const uint8_t* verbs = src.fPathRef->verbsMemBegin(); // points at the last verb
1607 const uint8_t* verbsEnd = src.fPathRef->verbs(); // points just past the first verb
reed@google.com277c3f82013-05-31 15:17:50 +00001608 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
reed@google.com63d73742012-01-10 15:33:12 +00001609
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001610 fIsOval = false;
1611
reed@google.com63d73742012-01-10 15:33:12 +00001612 bool needMove = true;
1613 bool needClose = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001614 while (verbs < verbsEnd) {
1615 uint8_t v = *(verbs++);
reed@google.com277c3f82013-05-31 15:17:50 +00001616 int n = pts_in_verb(v);
reed@google.com63d73742012-01-10 15:33:12 +00001617
1618 if (needMove) {
1619 --pts;
1620 this->moveTo(pts->fX, pts->fY);
1621 needMove = false;
1622 }
1623 pts -= n;
1624 switch (v) {
1625 case kMove_Verb:
1626 if (needClose) {
1627 this->close();
1628 needClose = false;
1629 }
1630 needMove = true;
1631 pts += 1; // so we see the point in "if (needMove)" above
1632 break;
1633 case kLine_Verb:
1634 this->lineTo(pts[0]);
1635 break;
1636 case kQuad_Verb:
1637 this->quadTo(pts[1], pts[0]);
1638 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001639 case kConic_Verb:
1640 this->conicTo(pts[1], pts[0], *--conicWeights);
1641 break;
reed@google.com63d73742012-01-10 15:33:12 +00001642 case kCubic_Verb:
1643 this->cubicTo(pts[2], pts[1], pts[0]);
1644 break;
1645 case kClose_Verb:
1646 needClose = true;
1647 break;
1648 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00001649 SkDEBUGFAIL("unexpected verb");
reed@google.com63d73742012-01-10 15:33:12 +00001650 }
1651 }
1652}
1653
reed@android.com8a1c16f2008-12-17 15:59:43 +00001654///////////////////////////////////////////////////////////////////////////////
1655
1656void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const {
1657 SkMatrix matrix;
1658
1659 matrix.setTranslate(dx, dy);
1660 this->transform(matrix, dst);
1661}
1662
1663#include "SkGeometry.h"
1664
1665static void subdivide_quad_to(SkPath* path, const SkPoint pts[3],
1666 int level = 2) {
1667 if (--level >= 0) {
1668 SkPoint tmp[5];
1669
1670 SkChopQuadAtHalf(pts, tmp);
1671 subdivide_quad_to(path, &tmp[0], level);
1672 subdivide_quad_to(path, &tmp[2], level);
1673 } else {
1674 path->quadTo(pts[1], pts[2]);
1675 }
1676}
1677
1678static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4],
1679 int level = 2) {
1680 if (--level >= 0) {
1681 SkPoint tmp[7];
1682
1683 SkChopCubicAtHalf(pts, tmp);
1684 subdivide_cubic_to(path, &tmp[0], level);
1685 subdivide_cubic_to(path, &tmp[3], level);
1686 } else {
1687 path->cubicTo(pts[1], pts[2], pts[3]);
1688 }
1689}
1690
1691void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
1692 SkDEBUGCODE(this->validate();)
1693 if (dst == NULL) {
1694 dst = (SkPath*)this;
1695 }
1696
tomhudson@google.com8d430182011-06-06 19:11:19 +00001697 if (matrix.hasPerspective()) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001698 SkPath tmp;
1699 tmp.fFillType = fFillType;
1700
1701 SkPath::Iter iter(*this, false);
1702 SkPoint pts[4];
1703 SkPath::Verb verb;
1704
reed@google.com4a3b7142012-05-16 17:16:46 +00001705 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001706 switch (verb) {
1707 case kMove_Verb:
1708 tmp.moveTo(pts[0]);
1709 break;
1710 case kLine_Verb:
1711 tmp.lineTo(pts[1]);
1712 break;
1713 case kQuad_Verb:
1714 subdivide_quad_to(&tmp, pts);
1715 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001716 case kConic_Verb:
mtklein@google.com330313a2013-08-22 15:37:26 +00001717 SkDEBUGFAIL("TODO: compute new weight");
reed@google.com277c3f82013-05-31 15:17:50 +00001718 tmp.conicTo(pts[1], pts[2], iter.conicWeight());
1719 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001720 case kCubic_Verb:
1721 subdivide_cubic_to(&tmp, pts);
1722 break;
1723 case kClose_Verb:
1724 tmp.close();
1725 break;
1726 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001727 SkDEBUGFAIL("unknown verb");
reed@android.com8a1c16f2008-12-17 15:59:43 +00001728 break;
1729 }
1730 }
1731
1732 dst->swap(tmp);
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001733 SkPathRef::Editor ed(&dst->fPathRef);
1734 matrix.mapPoints(ed.points(), ed.pathRef()->countPoints());
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001735 dst->fDirection = kUnknown_Direction;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001736 } else {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001737 SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef.get(), matrix);
1738
reed@android.com8a1c16f2008-12-17 15:59:43 +00001739 if (this != dst) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001740 dst->fFillType = fFillType;
reed@google.comb3b0a5b2011-09-21 12:58:51 +00001741 dst->fSegmentMask = fSegmentMask;
reed@google.com2a6f8ab2011-10-25 18:41:23 +00001742 dst->fConvexity = fConvexity;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001743 }
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001744
bsalomon@google.com30c174b2012-11-13 14:36:42 +00001745 if (kUnknown_Direction == fDirection) {
1746 dst->fDirection = kUnknown_Direction;
1747 } else {
1748 SkScalar det2x2 =
1749 SkScalarMul(matrix.get(SkMatrix::kMScaleX), matrix.get(SkMatrix::kMScaleY)) -
1750 SkScalarMul(matrix.get(SkMatrix::kMSkewX), matrix.get(SkMatrix::kMSkewY));
1751 if (det2x2 < 0) {
1752 dst->fDirection = SkPath::OppositeDirection(static_cast<Direction>(fDirection));
1753 } else if (det2x2 > 0) {
1754 dst->fDirection = fDirection;
1755 } else {
1756 dst->fDirection = kUnknown_Direction;
1757 }
1758 }
1759
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001760 // It's an oval only if it stays a rect.
1761 dst->fIsOval = fIsOval && matrix.rectStaysRect();
bsalomon@google.com6aa29652012-04-18 13:29:52 +00001762
reed@android.com8a1c16f2008-12-17 15:59:43 +00001763 SkDEBUGCODE(dst->validate();)
1764 }
1765}
1766
reed@android.com8a1c16f2008-12-17 15:59:43 +00001767///////////////////////////////////////////////////////////////////////////////
1768///////////////////////////////////////////////////////////////////////////////
1769
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001770enum SegmentState {
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001771 kEmptyContour_SegmentState, // The current contour is empty. We may be
1772 // starting processing or we may have just
1773 // closed a contour.
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001774 kAfterMove_SegmentState, // We have seen a move, but nothing else.
1775 kAfterPrimitive_SegmentState // We have seen a primitive but not yet
1776 // closed the path. Also the initial state.
reed@android.com8a1c16f2008-12-17 15:59:43 +00001777};
1778
1779SkPath::Iter::Iter() {
1780#ifdef SK_DEBUG
1781 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00001782 fConicWeights = NULL;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001783 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001784 fForceClose = fCloseLine = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001785 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001786#endif
1787 // need to init enough to make next() harmlessly return kDone_Verb
1788 fVerbs = NULL;
1789 fVerbStop = NULL;
1790 fNeedClose = false;
1791}
1792
1793SkPath::Iter::Iter(const SkPath& path, bool forceClose) {
1794 this->setPath(path, forceClose);
1795}
1796
1797void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001798 fPts = path.fPathRef->points();
1799 fVerbs = path.fPathRef->verbs();
1800 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00001801 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001802 fLastPt.fX = fLastPt.fY = 0;
schenney@chromium.org72785c42011-12-29 21:03:28 +00001803 fMoveTo.fX = fMoveTo.fY = 0;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001804 fForceClose = SkToU8(forceClose);
1805 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00001806 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001807}
1808
1809bool SkPath::Iter::isClosedContour() const {
1810 if (fVerbs == NULL || fVerbs == fVerbStop) {
1811 return false;
1812 }
1813 if (fForceClose) {
1814 return true;
1815 }
1816
1817 const uint8_t* verbs = fVerbs;
1818 const uint8_t* stop = fVerbStop;
reed@google.comabf15c12011-01-18 20:35:51 +00001819
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001820 if (kMove_Verb == *(verbs - 1)) {
1821 verbs -= 1; // skip the initial moveto
reed@android.com8a1c16f2008-12-17 15:59:43 +00001822 }
1823
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001824 while (verbs > stop) {
1825 // verbs points one beyond the current verb, decrement first.
1826 unsigned v = *(--verbs);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001827 if (kMove_Verb == v) {
1828 break;
1829 }
1830 if (kClose_Verb == v) {
1831 return true;
1832 }
1833 }
1834 return false;
1835}
1836
1837SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001838 SkASSERT(pts);
reed@android.com8a1c16f2008-12-17 15:59:43 +00001839 if (fLastPt != fMoveTo) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001840 // A special case: if both points are NaN, SkPoint::operation== returns
1841 // false, but the iterator expects that they are treated as the same.
1842 // (consider SkPoint is a 2-dimension float point).
reed@android.com9da1ae32009-07-22 17:06:15 +00001843 if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) ||
1844 SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) {
reed@android.com4ddfe352009-03-20 12:16:09 +00001845 return kClose_Verb;
1846 }
1847
reed@google.com9e25dbf2012-05-15 17:05:38 +00001848 pts[0] = fLastPt;
1849 pts[1] = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001850 fLastPt = fMoveTo;
1851 fCloseLine = true;
1852 return kLine_Verb;
bsalomon@google.comb3b8dfa2011-07-13 17:44:36 +00001853 } else {
1854 pts[0] = fMoveTo;
1855 return kClose_Verb;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001856 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001857}
1858
reed@google.com9e25dbf2012-05-15 17:05:38 +00001859const SkPoint& SkPath::Iter::cons_moveTo() {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001860 if (fSegmentState == kAfterMove_SegmentState) {
1861 // Set the first return pt to the move pt
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001862 fSegmentState = kAfterPrimitive_SegmentState;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001863 return fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001864 } else {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001865 SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
1866 // Set the first return pt to the last pt of the previous primitive.
reed@google.com9e25dbf2012-05-15 17:05:38 +00001867 return fPts[-1];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001868 }
reed@android.com8a1c16f2008-12-17 15:59:43 +00001869}
1870
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001871void SkPath::Iter::consumeDegenerateSegments() {
1872 // We need to step over anything that will not move the current draw point
1873 // forward before the next move is seen
1874 const uint8_t* lastMoveVerb = 0;
1875 const SkPoint* lastMovePt = 0;
1876 SkPoint lastPt = fLastPt;
1877 while (fVerbs != fVerbStop) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001878 unsigned verb = *(fVerbs - 1); // fVerbs is one beyond the current verb
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001879 switch (verb) {
1880 case kMove_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001881 // Keep a record of this most recent move
1882 lastMoveVerb = fVerbs;
1883 lastMovePt = fPts;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001884 lastPt = fPts[0];
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001885 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001886 fPts++;
1887 break;
1888
1889 case kClose_Verb:
schenney@chromium.org7e963602012-06-13 17:05:43 +00001890 // A close when we are in a segment is always valid except when it
1891 // follows a move which follows a segment.
1892 if (fSegmentState == kAfterPrimitive_SegmentState && !lastMoveVerb) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001893 return;
1894 }
1895 // A close at any other time must be ignored
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001896 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001897 break;
1898
1899 case kLine_Verb:
1900 if (!IsLineDegenerate(lastPt, fPts[0])) {
1901 if (lastMoveVerb) {
1902 fVerbs = lastMoveVerb;
1903 fPts = lastMovePt;
1904 return;
1905 }
1906 return;
1907 }
1908 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001909 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001910 fPts++;
1911 break;
1912
reed@google.com277c3f82013-05-31 15:17:50 +00001913 case kConic_Verb:
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001914 case kQuad_Verb:
1915 if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
1916 if (lastMoveVerb) {
1917 fVerbs = lastMoveVerb;
1918 fPts = lastMovePt;
1919 return;
1920 }
1921 return;
1922 }
1923 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001924 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001925 fPts += 2;
reed@google.com277c3f82013-05-31 15:17:50 +00001926 fConicWeights += (kConic_Verb == verb);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001927 break;
1928
1929 case kCubic_Verb:
1930 if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
1931 if (lastMoveVerb) {
1932 fVerbs = lastMoveVerb;
1933 fPts = lastMovePt;
1934 return;
1935 }
1936 return;
1937 }
1938 // Ignore this line and continue
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001939 fVerbs--;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001940 fPts += 3;
1941 break;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001942
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001943 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00001944 SkDEBUGFAIL("Should never see kDone_Verb");
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001945 }
1946 }
1947}
1948
reed@google.com4a3b7142012-05-16 17:16:46 +00001949SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001950 SkASSERT(ptsParam);
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001951
reed@android.com8a1c16f2008-12-17 15:59:43 +00001952 if (fVerbs == fVerbStop) {
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001953 // Close the curve if requested and if there is some curve to close
1954 if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
reed@google.com9e25dbf2012-05-15 17:05:38 +00001955 if (kLine_Verb == this->autoClose(ptsParam)) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00001956 return kLine_Verb;
1957 }
1958 fNeedClose = false;
1959 return kClose_Verb;
1960 }
1961 return kDone_Verb;
1962 }
1963
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001964 // fVerbs is one beyond the current verb, decrement first
1965 unsigned verb = *(--fVerbs);
reed@google.com9e25dbf2012-05-15 17:05:38 +00001966 const SkPoint* SK_RESTRICT srcPts = fPts;
1967 SkPoint* SK_RESTRICT pts = ptsParam;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001968
1969 switch (verb) {
1970 case kMove_Verb:
1971 if (fNeedClose) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00001972 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00001973 verb = this->autoClose(pts);
1974 if (verb == kClose_Verb) {
1975 fNeedClose = false;
1976 }
1977 return (Verb)verb;
1978 }
1979 if (fVerbs == fVerbStop) { // might be a trailing moveto
1980 return kDone_Verb;
1981 }
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001982 fMoveTo = *srcPts;
reed@google.com9e25dbf2012-05-15 17:05:38 +00001983 pts[0] = *srcPts;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001984 srcPts += 1;
schenney@chromium.orgb0af6da2011-12-21 20:43:13 +00001985 fSegmentState = kAfterMove_SegmentState;
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00001986 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00001987 fNeedClose = fForceClose;
1988 break;
1989 case kLine_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00001990 pts[0] = this->cons_moveTo();
1991 pts[1] = srcPts[0];
reed@android.com8a1c16f2008-12-17 15:59:43 +00001992 fLastPt = srcPts[0];
1993 fCloseLine = false;
1994 srcPts += 1;
1995 break;
reed@google.com277c3f82013-05-31 15:17:50 +00001996 case kConic_Verb:
1997 fConicWeights += 1;
1998 // fall-through
reed@android.com8a1c16f2008-12-17 15:59:43 +00001999 case kQuad_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002000 pts[0] = this->cons_moveTo();
2001 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002002 fLastPt = srcPts[1];
2003 srcPts += 2;
2004 break;
2005 case kCubic_Verb:
reed@google.com9e25dbf2012-05-15 17:05:38 +00002006 pts[0] = this->cons_moveTo();
2007 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
reed@android.com8a1c16f2008-12-17 15:59:43 +00002008 fLastPt = srcPts[2];
2009 srcPts += 3;
2010 break;
2011 case kClose_Verb:
2012 verb = this->autoClose(pts);
2013 if (verb == kLine_Verb) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002014 fVerbs++; // move back one verb
reed@android.com8a1c16f2008-12-17 15:59:43 +00002015 } else {
2016 fNeedClose = false;
schenney@chromium.orgfde6b412012-01-19 15:31:01 +00002017 fSegmentState = kEmptyContour_SegmentState;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002018 }
schenney@chromium.org4da06ab2011-12-20 15:14:18 +00002019 fLastPt = fMoveTo;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002020 break;
2021 }
2022 fPts = srcPts;
2023 return (Verb)verb;
2024}
2025
2026///////////////////////////////////////////////////////////////////////////////
2027
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002028SkPath::RawIter::RawIter() {
2029#ifdef SK_DEBUG
2030 fPts = NULL;
reed@google.com277c3f82013-05-31 15:17:50 +00002031 fConicWeights = NULL;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002032 fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
2033#endif
2034 // need to init enough to make next() harmlessly return kDone_Verb
2035 fVerbs = NULL;
2036 fVerbStop = NULL;
2037}
2038
2039SkPath::RawIter::RawIter(const SkPath& path) {
2040 this->setPath(path);
2041}
2042
2043void SkPath::RawIter::setPath(const SkPath& path) {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002044 fPts = path.fPathRef->points();
2045 fVerbs = path.fPathRef->verbs();
2046 fVerbStop = path.fPathRef->verbsMemBegin();
reed@google.com277c3f82013-05-31 15:17:50 +00002047 fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002048 fMoveTo.fX = fMoveTo.fY = 0;
2049 fLastPt.fX = fLastPt.fY = 0;
2050}
2051
2052SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002053 SkASSERT(NULL != pts);
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002054 if (fVerbs == fVerbStop) {
2055 return kDone_Verb;
2056 }
2057
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002058 // fVerbs points one beyond next verb so decrement first.
2059 unsigned verb = *(--fVerbs);
2060 const SkPoint* srcPts = fPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002061
2062 switch (verb) {
2063 case kMove_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002064 pts[0] = *srcPts;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002065 fMoveTo = srcPts[0];
2066 fLastPt = fMoveTo;
2067 srcPts += 1;
2068 break;
2069 case kLine_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002070 pts[0] = fLastPt;
2071 pts[1] = srcPts[0];
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002072 fLastPt = srcPts[0];
2073 srcPts += 1;
2074 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002075 case kConic_Verb:
2076 fConicWeights += 1;
2077 // fall-through
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002078 case kQuad_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002079 pts[0] = fLastPt;
2080 memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002081 fLastPt = srcPts[1];
2082 srcPts += 2;
2083 break;
2084 case kCubic_Verb:
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002085 pts[0] = fLastPt;
2086 memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint));
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002087 fLastPt = srcPts[2];
2088 srcPts += 3;
2089 break;
2090 case kClose_Verb:
2091 fLastPt = fMoveTo;
bsalomon@google.comf6d3c5a2012-06-07 17:47:33 +00002092 pts[0] = fMoveTo;
schenney@chromium.org6630d8d2012-01-04 21:05:51 +00002093 break;
2094 }
2095 fPts = srcPts;
2096 return (Verb)verb;
2097}
2098
2099///////////////////////////////////////////////////////////////////////////////
2100
reed@android.com8a1c16f2008-12-17 15:59:43 +00002101/*
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002102 Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]]
reed@android.com8a1c16f2008-12-17 15:59:43 +00002103*/
2104
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002105size_t SkPath::writeToMemory(void* storage) const {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002106 SkDEBUGCODE(this->validate();)
2107
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002108 if (NULL == storage) {
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002109 const int byteCount = sizeof(int32_t) + fPathRef->writeSize();
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002110 return SkAlign4(byteCount);
2111 }
2112
2113 SkWBuffer buffer(storage);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002114
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002115 int32_t packed = ((fIsOval & 1) << kIsOval_SerializationShift) |
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002116 (fConvexity << kConvexity_SerializationShift) |
2117 (fFillType << kFillType_SerializationShift) |
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002118 (fSegmentMask << kSegmentMask_SerializationShift) |
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002119 (fDirection << kDirection_SerializationShift)
2120#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2121 | (0x1 << kNewFormat_SerializationShift);
2122#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002123
robertphillips@google.com2972bb52012-08-07 17:32:51 +00002124 buffer.write32(packed);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002125
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002126 fPathRef->writeToBuffer(&buffer);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002127
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002128 buffer.padToAlign4();
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002129 return buffer.pos();
reed@android.com8a1c16f2008-12-17 15:59:43 +00002130}
2131
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002132size_t SkPath::readFromMemory(const void* storage, size_t length) {
2133 SkRBufferWithSizeCheck buffer(storage, length);
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002134
reed@google.com98b11f12011-09-21 18:40:27 +00002135 uint32_t packed = buffer.readS32();
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002136 fIsOval = (packed >> kIsOval_SerializationShift) & 1;
2137 fConvexity = (packed >> kConvexity_SerializationShift) & 0xFF;
2138 fFillType = (packed >> kFillType_SerializationShift) & 0xFF;
reed@google.com277c3f82013-05-31 15:17:50 +00002139 fSegmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002140 fDirection = (packed >> kDirection_SerializationShift) & 0x3;
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002141#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
2142 bool newFormat = (packed >> kNewFormat_SerializationShift) & 1;
2143#endif
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002144
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002145 fPathRef.reset(SkPathRef::CreateFromBuffer(&buffer
2146#ifndef DELETE_THIS_CODE_WHEN_SKPS_ARE_REBUILT_AT_V14_AND_ALL_OTHER_INSTANCES_TOO
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002147 , newFormat, packed
robertphillips@google.comca0c8382013-09-26 12:18:23 +00002148#endif
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002149 ));
robertphillips@google.com01ec2eb2012-08-17 10:58:49 +00002150
djsollen@google.com94e75ee2012-06-08 18:30:46 +00002151 buffer.skipToAlign4();
reed@google.comabf15c12011-01-18 20:35:51 +00002152
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +00002153 size_t sizeRead = 0;
2154 if (buffer.isValid()) {
2155 SkDEBUGCODE(this->validate();)
2156 sizeRead = buffer.pos();
2157 }
2158 return sizeRead;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002159}
2160
2161///////////////////////////////////////////////////////////////////////////////
reed@android.com8a1c16f2008-12-17 15:59:43 +00002162
reed@google.com51bbe752013-01-17 22:07:50 +00002163#include "SkString.h"
2164
2165static void append_scalar(SkString* str, SkScalar value) {
2166 SkString tmp;
2167 tmp.printf("%g", value);
2168 if (tmp.contains('.')) {
2169 tmp.appendUnichar('f');
2170 }
2171 str->append(tmp);
2172}
2173
2174static void append_params(SkString* str, const char label[], const SkPoint pts[],
reed@google.com277c3f82013-05-31 15:17:50 +00002175 int count, SkScalar conicWeight = -1) {
reed@google.com51bbe752013-01-17 22:07:50 +00002176 str->append(label);
2177 str->append("(");
skia.committer@gmail.com15dd3002013-01-18 07:07:28 +00002178
reed@google.com51bbe752013-01-17 22:07:50 +00002179 const SkScalar* values = &pts[0].fX;
2180 count *= 2;
2181
2182 for (int i = 0; i < count; ++i) {
2183 append_scalar(str, values[i]);
2184 if (i < count - 1) {
2185 str->append(", ");
2186 }
2187 }
reed@google.com277c3f82013-05-31 15:17:50 +00002188 if (conicWeight >= 0) {
2189 str->append(", ");
2190 append_scalar(str, conicWeight);
2191 }
reed@google.com51bbe752013-01-17 22:07:50 +00002192 str->append(");\n");
2193}
2194
reed@android.com8a1c16f2008-12-17 15:59:43 +00002195void SkPath::dump(bool forceClose, const char title[]) const {
2196 Iter iter(*this, forceClose);
2197 SkPoint pts[4];
2198 Verb verb;
2199
2200 SkDebugf("path: forceClose=%s %s\n", forceClose ? "true" : "false",
2201 title ? title : "");
2202
reed@google.com51bbe752013-01-17 22:07:50 +00002203 SkString builder;
2204
reed@google.com4a3b7142012-05-16 17:16:46 +00002205 while ((verb = iter.next(pts, false)) != kDone_Verb) {
reed@android.com8a1c16f2008-12-17 15:59:43 +00002206 switch (verb) {
2207 case kMove_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002208 append_params(&builder, "path.moveTo", &pts[0], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002209 break;
2210 case kLine_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002211 append_params(&builder, "path.lineTo", &pts[1], 1);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002212 break;
2213 case kQuad_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002214 append_params(&builder, "path.quadTo", &pts[1], 2);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002215 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002216 case kConic_Verb:
2217 append_params(&builder, "path.conicTo", &pts[1], 2, iter.conicWeight());
2218 break;
reed@android.com8a1c16f2008-12-17 15:59:43 +00002219 case kCubic_Verb:
reed@google.com51bbe752013-01-17 22:07:50 +00002220 append_params(&builder, "path.cubicTo", &pts[1], 3);
reed@android.com8a1c16f2008-12-17 15:59:43 +00002221 break;
2222 case kClose_Verb:
reed@google.comf919b722013-01-18 17:53:36 +00002223 builder.append("path.close();\n");
reed@android.com8a1c16f2008-12-17 15:59:43 +00002224 break;
2225 default:
2226 SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb);
2227 verb = kDone_Verb; // stop the loop
2228 break;
2229 }
2230 }
reed@google.com51bbe752013-01-17 22:07:50 +00002231 SkDebugf("%s\n", builder.c_str());
reed@android.com8a1c16f2008-12-17 15:59:43 +00002232}
2233
reed@android.come522ca52009-11-23 20:10:41 +00002234void SkPath::dump() const {
2235 this->dump(false);
2236}
2237
2238#ifdef SK_DEBUG
2239void SkPath::validate() const {
2240 SkASSERT(this != NULL);
2241 SkASSERT((fFillType & ~3) == 0);
reed@google.comabf15c12011-01-18 20:35:51 +00002242
djsollen@google.com077348c2012-10-22 20:23:32 +00002243#ifdef SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002244 if (!fBoundsIsDirty) {
2245 SkRect bounds;
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002246
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002247 bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get());
robertphillips@google.com5d8d1862012-08-15 14:36:41 +00002248 SkASSERT(SkToBool(fIsFinite) == isFinite);
tomhudson@google.comed02c4d2012-08-10 14:10:45 +00002249
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002250 if (fPathRef->countPoints() <= 1) {
reed@android.come522ca52009-11-23 20:10:41 +00002251 // if we're empty, fBounds may be empty but translated, so we can't
2252 // necessarily compare to bounds directly
2253 // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will
2254 // be [2, 2, 2, 2]
2255 SkASSERT(bounds.isEmpty());
2256 SkASSERT(fBounds.isEmpty());
2257 } else {
reed@google.comeac52bd2011-11-14 18:13:59 +00002258 if (bounds.isEmpty()) {
2259 SkASSERT(fBounds.isEmpty());
2260 } else {
reed@google.com3563c9e2011-11-14 19:34:57 +00002261 if (!fBounds.isEmpty()) {
2262 SkASSERT(fBounds.contains(bounds));
2263 }
reed@google.comeac52bd2011-11-14 18:13:59 +00002264 }
reed@android.come522ca52009-11-23 20:10:41 +00002265 }
2266 }
reed@google.com10296cc2011-09-21 12:29:05 +00002267
2268 uint32_t mask = 0;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002269 const uint8_t* verbs = const_cast<const SkPathRef*>(fPathRef.get())->verbs();
2270 for (int i = 0; i < fPathRef->countVerbs(); i++) {
2271 switch (verbs[~i]) {
reed@google.com10296cc2011-09-21 12:29:05 +00002272 case kLine_Verb:
2273 mask |= kLine_SegmentMask;
2274 break;
2275 case kQuad_Verb:
2276 mask |= kQuad_SegmentMask;
2277 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002278 case kConic_Verb:
2279 mask |= kConic_SegmentMask;
2280 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002281 case kCubic_Verb:
2282 mask |= kCubic_SegmentMask;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002283 case kMove_Verb: // these verbs aren't included in the segment mask.
2284 case kClose_Verb:
2285 break;
2286 case kDone_Verb:
2287 SkDEBUGFAIL("Done verb shouldn't be recorded.");
2288 break;
2289 default:
2290 SkDEBUGFAIL("Unknown Verb");
2291 break;
reed@google.com10296cc2011-09-21 12:29:05 +00002292 }
2293 }
2294 SkASSERT(mask == fSegmentMask);
djsollen@google.com077348c2012-10-22 20:23:32 +00002295#endif // SK_DEBUG_PATH
reed@android.come522ca52009-11-23 20:10:41 +00002296}
djsollen@google.com077348c2012-10-22 20:23:32 +00002297#endif // SK_DEBUG
reed@android.come522ca52009-11-23 20:10:41 +00002298
reed@google.com04863fa2011-05-15 04:08:24 +00002299///////////////////////////////////////////////////////////////////////////////
2300
reed@google.com0b7b9822011-05-16 12:29:27 +00002301static int sign(SkScalar x) { return x < 0; }
2302#define kValueNeverReturnedBySign 2
reed@google.com85b6e392011-05-15 20:25:17 +00002303
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002304static bool AlmostEqual(SkScalar compA, SkScalar compB) {
2305 // The error epsilon was empirically derived; worse case round rects
2306 // with a mid point outset by 2x float epsilon in tests had an error
2307 // of 12.
2308 const int epsilon = 16;
2309 if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
2310 return false;
2311 }
2312 if (sk_float_abs(compA) <= FLT_EPSILON && sk_float_abs(compB) <= FLT_EPSILON) {
2313 return true;
2314 }
2315 int aBits = SkFloatAs2sCompliment(compA);
2316 int bBits = SkFloatAs2sCompliment(compB);
2317 return aBits < bBits + epsilon && bBits < aBits + epsilon;
reed@google.com04863fa2011-05-15 04:08:24 +00002318}
2319
2320// only valid for a single contour
2321struct Convexicator {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002322 Convexicator()
2323 : fPtCount(0)
2324 , fConvexity(SkPath::kConvex_Convexity)
2325 , fDirection(SkPath::kUnknown_Direction) {
reed@google.com04863fa2011-05-15 04:08:24 +00002326 fSign = 0;
2327 // warnings
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002328 fLastPt.set(0, 0);
reed@google.com04863fa2011-05-15 04:08:24 +00002329 fCurrPt.set(0, 0);
2330 fVec0.set(0, 0);
2331 fVec1.set(0, 0);
2332 fFirstVec.set(0, 0);
reed@google.com85b6e392011-05-15 20:25:17 +00002333
2334 fDx = fDy = 0;
reed@google.com0b7b9822011-05-16 12:29:27 +00002335 fSx = fSy = kValueNeverReturnedBySign;
reed@google.com04863fa2011-05-15 04:08:24 +00002336 }
2337
2338 SkPath::Convexity getConvexity() const { return fConvexity; }
2339
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002340 /** The direction returned is only valid if the path is determined convex */
2341 SkPath::Direction getDirection() const { return fDirection; }
2342
reed@google.com04863fa2011-05-15 04:08:24 +00002343 void addPt(const SkPoint& pt) {
2344 if (SkPath::kConcave_Convexity == fConvexity) {
2345 return;
2346 }
2347
2348 if (0 == fPtCount) {
2349 fCurrPt = pt;
2350 ++fPtCount;
2351 } else {
2352 SkVector vec = pt - fCurrPt;
2353 if (vec.fX || vec.fY) {
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002354 fLastPt = fCurrPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002355 fCurrPt = pt;
2356 if (++fPtCount == 2) {
2357 fFirstVec = fVec1 = vec;
2358 } else {
2359 SkASSERT(fPtCount > 2);
2360 this->addVec(vec);
2361 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002362
reed@google.com85b6e392011-05-15 20:25:17 +00002363 int sx = sign(vec.fX);
2364 int sy = sign(vec.fY);
2365 fDx += (sx != fSx);
2366 fDy += (sy != fSy);
2367 fSx = sx;
2368 fSy = sy;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002369
reed@google.com85b6e392011-05-15 20:25:17 +00002370 if (fDx > 3 || fDy > 3) {
2371 fConvexity = SkPath::kConcave_Convexity;
2372 }
reed@google.com04863fa2011-05-15 04:08:24 +00002373 }
2374 }
2375 }
2376
2377 void close() {
2378 if (fPtCount > 2) {
2379 this->addVec(fFirstVec);
2380 }
2381 }
2382
2383private:
2384 void addVec(const SkVector& vec) {
2385 SkASSERT(vec.fX || vec.fY);
2386 fVec0 = fVec1;
2387 fVec1 = vec;
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002388 SkScalar cross = SkPoint::CrossProduct(fVec0, fVec1);
2389 SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
2390 SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
2391 largest = SkTMax(largest, -smallest);
2392 int sign = AlmostEqual(largest, largest + cross) ? 0 : SkScalarSignAsInt(cross);
reed@google.com04863fa2011-05-15 04:08:24 +00002393 if (0 == fSign) {
2394 fSign = sign;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002395 if (1 == sign) {
2396 fDirection = SkPath::kCW_Direction;
2397 } else if (-1 == sign) {
2398 fDirection = SkPath::kCCW_Direction;
2399 }
reed@google.com04863fa2011-05-15 04:08:24 +00002400 } else if (sign) {
reed@google.comb54455e2011-05-16 14:16:04 +00002401 if (fSign != sign) {
reed@google.com04863fa2011-05-15 04:08:24 +00002402 fConvexity = SkPath::kConcave_Convexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002403 fDirection = SkPath::kUnknown_Direction;
reed@google.com04863fa2011-05-15 04:08:24 +00002404 }
2405 }
2406 }
2407
commit-bot@chromium.orgf91aaec2013-11-01 15:24:55 +00002408 SkPoint fLastPt;
reed@google.com04863fa2011-05-15 04:08:24 +00002409 SkPoint fCurrPt;
2410 SkVector fVec0, fVec1, fFirstVec;
2411 int fPtCount; // non-degenerate points
2412 int fSign;
2413 SkPath::Convexity fConvexity;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002414 SkPath::Direction fDirection;
reed@google.com0b7b9822011-05-16 12:29:27 +00002415 int fDx, fDy, fSx, fSy;
reed@google.com04863fa2011-05-15 04:08:24 +00002416};
2417
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002418SkPath::Convexity SkPath::internalGetConvexity() const {
2419 SkASSERT(kUnknown_Convexity == fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002420 SkPoint pts[4];
2421 SkPath::Verb verb;
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002422 SkPath::Iter iter(*this, true);
reed@google.com04863fa2011-05-15 04:08:24 +00002423
2424 int contourCount = 0;
2425 int count;
2426 Convexicator state;
2427
2428 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
2429 switch (verb) {
2430 case kMove_Verb:
2431 if (++contourCount > 1) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002432 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002433 return kConcave_Convexity;
2434 }
2435 pts[1] = pts[0];
2436 count = 1;
2437 break;
2438 case kLine_Verb: count = 1; break;
2439 case kQuad_Verb: count = 2; break;
reed@google.com277c3f82013-05-31 15:17:50 +00002440 case kConic_Verb: count = 2; break;
reed@google.com04863fa2011-05-15 04:08:24 +00002441 case kCubic_Verb: count = 3; break;
2442 case kClose_Verb:
2443 state.close();
2444 count = 0;
2445 break;
2446 default:
tomhudson@google.com0c00f212011-12-28 14:59:50 +00002447 SkDEBUGFAIL("bad verb");
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002448 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002449 return kConcave_Convexity;
2450 }
2451
2452 for (int i = 1; i <= count; i++) {
2453 state.addPt(pts[i]);
2454 }
2455 // early exit
2456 if (kConcave_Convexity == state.getConvexity()) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002457 fConvexity = kConcave_Convexity;
reed@google.com04863fa2011-05-15 04:08:24 +00002458 return kConcave_Convexity;
2459 }
2460 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002461 fConvexity = state.getConvexity();
2462 if (kConvex_Convexity == fConvexity && kUnknown_Direction == fDirection) {
2463 fDirection = state.getDirection();
2464 }
2465 return static_cast<Convexity>(fConvexity);
reed@google.com04863fa2011-05-15 04:08:24 +00002466}
reed@google.com69a99432012-01-10 18:00:10 +00002467
2468///////////////////////////////////////////////////////////////////////////////
2469
2470class ContourIter {
2471public:
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002472 ContourIter(const SkPathRef& pathRef);
reed@google.com69a99432012-01-10 18:00:10 +00002473
2474 bool done() const { return fDone; }
2475 // if !done() then these may be called
2476 int count() const { return fCurrPtCount; }
2477 const SkPoint* pts() const { return fCurrPt; }
2478 void next();
2479
2480private:
2481 int fCurrPtCount;
2482 const SkPoint* fCurrPt;
2483 const uint8_t* fCurrVerb;
2484 const uint8_t* fStopVerbs;
reed@google.com277c3f82013-05-31 15:17:50 +00002485 const SkScalar* fCurrConicWeight;
reed@google.com69a99432012-01-10 18:00:10 +00002486 bool fDone;
reed@google.comd1ab9322012-01-10 18:40:03 +00002487 SkDEBUGCODE(int fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002488};
2489
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002490ContourIter::ContourIter(const SkPathRef& pathRef) {
2491 fStopVerbs = pathRef.verbsMemBegin();
reed@google.com69a99432012-01-10 18:00:10 +00002492 fDone = false;
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002493 fCurrPt = pathRef.points();
2494 fCurrVerb = pathRef.verbs();
reed@google.com277c3f82013-05-31 15:17:50 +00002495 fCurrConicWeight = pathRef.conicWeights();
reed@google.com69a99432012-01-10 18:00:10 +00002496 fCurrPtCount = 0;
reed@google.comd1ab9322012-01-10 18:40:03 +00002497 SkDEBUGCODE(fContourCounter = 0;)
reed@google.com69a99432012-01-10 18:00:10 +00002498 this->next();
2499}
2500
2501void ContourIter::next() {
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002502 if (fCurrVerb <= fStopVerbs) {
reed@google.com69a99432012-01-10 18:00:10 +00002503 fDone = true;
2504 }
2505 if (fDone) {
2506 return;
2507 }
2508
2509 // skip pts of prev contour
2510 fCurrPt += fCurrPtCount;
2511
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002512 SkASSERT(SkPath::kMove_Verb == fCurrVerb[~0]);
reed@google.com69a99432012-01-10 18:00:10 +00002513 int ptCount = 1; // moveTo
2514 const uint8_t* verbs = fCurrVerb;
2515
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002516 for (--verbs; verbs > fStopVerbs; --verbs) {
2517 switch (verbs[~0]) {
reed@google.com69a99432012-01-10 18:00:10 +00002518 case SkPath::kMove_Verb:
reed@google.com69a99432012-01-10 18:00:10 +00002519 goto CONTOUR_END;
2520 case SkPath::kLine_Verb:
2521 ptCount += 1;
2522 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002523 case SkPath::kConic_Verb:
2524 fCurrConicWeight += 1;
2525 // fall-through
reed@google.com69a99432012-01-10 18:00:10 +00002526 case SkPath::kQuad_Verb:
2527 ptCount += 2;
2528 break;
2529 case SkPath::kCubic_Verb:
2530 ptCount += 3;
2531 break;
reed@google.com277c3f82013-05-31 15:17:50 +00002532 case SkPath::kClose_Verb:
2533 break;
2534 default:
mtklein@google.com330313a2013-08-22 15:37:26 +00002535 SkDEBUGFAIL("unexpected verb");
reed@google.com69a99432012-01-10 18:00:10 +00002536 break;
2537 }
2538 }
2539CONTOUR_END:
2540 fCurrPtCount = ptCount;
2541 fCurrVerb = verbs;
reed@google.comd1ab9322012-01-10 18:40:03 +00002542 SkDEBUGCODE(++fContourCounter;)
reed@google.com69a99432012-01-10 18:00:10 +00002543}
2544
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002545// returns cross product of (p1 - p0) and (p2 - p0)
reed@google.com69a99432012-01-10 18:00:10 +00002546static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) {
bsalomon@google.comf0ed80a2012-02-17 13:38:26 +00002547 SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0);
2548 // We may get 0 when the above subtracts underflow. We expect this to be
2549 // very rare and lazily promote to double.
2550 if (0 == cross) {
2551 double p0x = SkScalarToDouble(p0.fX);
2552 double p0y = SkScalarToDouble(p0.fY);
2553
2554 double p1x = SkScalarToDouble(p1.fX);
2555 double p1y = SkScalarToDouble(p1.fY);
2556
2557 double p2x = SkScalarToDouble(p2.fX);
2558 double p2y = SkScalarToDouble(p2.fY);
2559
2560 cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) -
2561 (p1y - p0y) * (p2x - p0x));
2562
2563 }
2564 return cross;
reed@google.com69a99432012-01-10 18:00:10 +00002565}
2566
reed@google.comc1ea60a2012-01-31 15:15:36 +00002567// Returns the first pt with the maximum Y coordinate
reed@google.com69a99432012-01-10 18:00:10 +00002568static int find_max_y(const SkPoint pts[], int count) {
2569 SkASSERT(count > 0);
2570 SkScalar max = pts[0].fY;
reed@google.comc1ea60a2012-01-31 15:15:36 +00002571 int firstIndex = 0;
reed@google.com69a99432012-01-10 18:00:10 +00002572 for (int i = 1; i < count; ++i) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002573 SkScalar y = pts[i].fY;
2574 if (y > max) {
2575 max = y;
2576 firstIndex = i;
reed@google.com69a99432012-01-10 18:00:10 +00002577 }
2578 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002579 return firstIndex;
reed@google.com69a99432012-01-10 18:00:10 +00002580}
2581
reed@google.comcabaf1d2012-01-11 21:03:05 +00002582static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) {
2583 int i = index;
2584 for (;;) {
2585 i = (i + inc) % n;
2586 if (i == index) { // we wrapped around, so abort
2587 break;
2588 }
2589 if (pts[index] != pts[i]) { // found a different point, success!
2590 break;
2591 }
2592 }
2593 return i;
2594}
2595
reed@google.comc1ea60a2012-01-31 15:15:36 +00002596/**
2597 * Starting at index, and moving forward (incrementing), find the xmin and
2598 * xmax of the contiguous points that have the same Y.
2599 */
2600static int find_min_max_x_at_y(const SkPoint pts[], int index, int n,
2601 int* maxIndexPtr) {
2602 const SkScalar y = pts[index].fY;
2603 SkScalar min = pts[index].fX;
2604 SkScalar max = min;
2605 int minIndex = index;
2606 int maxIndex = index;
2607 for (int i = index + 1; i < n; ++i) {
2608 if (pts[i].fY != y) {
2609 break;
2610 }
2611 SkScalar x = pts[i].fX;
2612 if (x < min) {
2613 min = x;
2614 minIndex = i;
2615 } else if (x > max) {
2616 max = x;
2617 maxIndex = i;
2618 }
2619 }
2620 *maxIndexPtr = maxIndex;
2621 return minIndex;
2622}
2623
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002624static void crossToDir(SkScalar cross, SkPath::Direction* dir) {
reed@google.comac8543f2012-01-30 20:51:25 +00002625 if (dir) {
2626 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2627 }
reed@google.comac8543f2012-01-30 20:51:25 +00002628}
2629
reed@google.comc1ea60a2012-01-31 15:15:36 +00002630#if 0
2631#include "SkString.h"
2632#include "../utils/SkParsePath.h"
2633static void dumpPath(const SkPath& path) {
2634 SkString str;
2635 SkParsePath::ToSVGString(path, &str);
2636 SkDebugf("%s\n", str.c_str());
2637}
2638#endif
2639
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002640namespace {
2641// for use with convex_dir_test
2642double mul(double a, double b) { return a * b; }
2643SkScalar mul(SkScalar a, SkScalar b) { return SkScalarMul(a, b); }
2644double toDouble(SkScalar a) { return SkScalarToDouble(a); }
2645SkScalar toScalar(SkScalar a) { return a; }
2646
2647// determines the winding direction of a convex polygon with the precision
2648// of T. CAST_SCALAR casts an SkScalar to T.
2649template <typename T, T (CAST_SCALAR)(SkScalar)>
2650bool convex_dir_test(int n, const SkPoint pts[], SkPath::Direction* dir) {
2651 // we find the first three points that form a non-degenerate
2652 // triangle. If there are no such points then the path is
2653 // degenerate. The first is always point 0. Now we find the second
2654 // point.
2655 int i = 0;
2656 enum { kX = 0, kY = 1 };
2657 T v0[2];
2658 while (1) {
2659 v0[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2660 v0[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2661 if (v0[kX] || v0[kY]) {
2662 break;
2663 }
2664 if (++i == n - 1) {
2665 return false;
2666 }
2667 }
2668 // now find a third point that is not colinear with the first two
2669 // points and check the orientation of the triangle (which will be
2670 // the same as the orientation of the path).
2671 for (++i; i < n; ++i) {
2672 T v1[2];
2673 v1[kX] = CAST_SCALAR(pts[i].fX) - CAST_SCALAR(pts[0].fX);
2674 v1[kY] = CAST_SCALAR(pts[i].fY) - CAST_SCALAR(pts[0].fY);
2675 T cross = mul(v0[kX], v1[kY]) - mul(v0[kY], v1[kX]);
2676 if (0 != cross) {
2677 *dir = cross > 0 ? SkPath::kCW_Direction : SkPath::kCCW_Direction;
2678 return true;
2679 }
2680 }
2681 return false;
2682}
2683}
2684
reed@google.comac8543f2012-01-30 20:51:25 +00002685/*
2686 * We loop through all contours, and keep the computed cross-product of the
2687 * contour that contained the global y-max. If we just look at the first
2688 * contour, we may find one that is wound the opposite way (correctly) since
2689 * it is the interior of a hole (e.g. 'o'). Thus we must find the contour
2690 * that is outer most (or at least has the global y-max) before we can consider
2691 * its cross product.
2692 */
reed@google.com69a99432012-01-10 18:00:10 +00002693bool SkPath::cheapComputeDirection(Direction* dir) const {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002694// dumpPath(*this);
reed@google.com69a99432012-01-10 18:00:10 +00002695 // don't want to pay the cost for computing this if it
2696 // is unknown, so we don't call isConvex()
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002697
2698 if (kUnknown_Direction != fDirection) {
2699 *dir = static_cast<Direction>(fDirection);
2700 return true;
2701 }
reed@google.com69a99432012-01-10 18:00:10 +00002702 const Convexity conv = this->getConvexityOrUnknown();
2703
bsalomon@google.com1dfe88e2012-10-03 13:46:20 +00002704 ContourIter iter(*fPathRef.get());
reed@google.com69a99432012-01-10 18:00:10 +00002705
reed@google.comac8543f2012-01-30 20:51:25 +00002706 // initialize with our logical y-min
2707 SkScalar ymax = this->getBounds().fTop;
2708 SkScalar ymaxCross = 0;
2709
reed@google.com69a99432012-01-10 18:00:10 +00002710 for (; !iter.done(); iter.next()) {
2711 int n = iter.count();
reed@google.comcabaf1d2012-01-11 21:03:05 +00002712 if (n < 3) {
2713 continue;
2714 }
djsollen@google.come63793a2012-03-21 15:39:03 +00002715
reed@google.comcabaf1d2012-01-11 21:03:05 +00002716 const SkPoint* pts = iter.pts();
reed@google.com69a99432012-01-10 18:00:10 +00002717 SkScalar cross = 0;
2718 if (kConvex_Convexity == conv) {
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002719 // We try first at scalar precision, and then again at double
2720 // precision. This is because the vectors computed between distant
2721 // points may lose too much precision.
2722 if (convex_dir_test<SkScalar, toScalar>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002723 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002724 return true;
2725 }
2726 if (convex_dir_test<double, toDouble>(n, pts, dir)) {
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002727 fDirection = *dir;
bsalomon@google.com4eefe612012-07-10 18:28:12 +00002728 return true;
2729 } else {
2730 return false;
reed@google.com69a99432012-01-10 18:00:10 +00002731 }
2732 } else {
reed@google.comcabaf1d2012-01-11 21:03:05 +00002733 int index = find_max_y(pts, n);
reed@google.comac8543f2012-01-30 20:51:25 +00002734 if (pts[index].fY < ymax) {
2735 continue;
2736 }
2737
reed@google.comc1ea60a2012-01-31 15:15:36 +00002738 // If there is more than 1 distinct point at the y-max, we take the
2739 // x-min and x-max of them and just subtract to compute the dir.
2740 if (pts[(index + 1) % n].fY == pts[index].fY) {
2741 int maxIndex;
2742 int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex);
reed@google.com3e44e4d2012-01-31 15:25:22 +00002743 if (minIndex == maxIndex) {
2744 goto TRY_CROSSPROD;
2745 }
reed@google.comc1ea60a2012-01-31 15:15:36 +00002746 SkASSERT(pts[minIndex].fY == pts[index].fY);
2747 SkASSERT(pts[maxIndex].fY == pts[index].fY);
2748 SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX);
2749 // we just subtract the indices, and let that auto-convert to
2750 // SkScalar, since we just want - or + to signal the direction.
2751 cross = minIndex - maxIndex;
2752 } else {
reed@google.com3e44e4d2012-01-31 15:25:22 +00002753 TRY_CROSSPROD:
reed@google.comc1ea60a2012-01-31 15:15:36 +00002754 // Find a next and prev index to use for the cross-product test,
2755 // but we try to find pts that form non-zero vectors from pts[index]
2756 //
2757 // Its possible that we can't find two non-degenerate vectors, so
2758 // we have to guard our search (e.g. all the pts could be in the
2759 // same place).
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002760
reed@google.comc1ea60a2012-01-31 15:15:36 +00002761 // we pass n - 1 instead of -1 so we don't foul up % operator by
2762 // passing it a negative LH argument.
2763 int prev = find_diff_pt(pts, index, n, n - 1);
2764 if (prev == index) {
2765 // completely degenerate, skip to next contour
2766 continue;
2767 }
2768 int next = find_diff_pt(pts, index, n, 1);
2769 SkASSERT(next != index);
2770 cross = cross_prod(pts[prev], pts[index], pts[next]);
skia.committer@gmail.comfbb0ed92012-11-13 21:46:06 +00002771 // 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 +00002772 // x-direction. We really should continue to walk away from the degeneracy until
2773 // there is a divergence.
2774 if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) {
reed@google.comc1ea60a2012-01-31 15:15:36 +00002775 // construct the subtract so we get the correct Direction below
2776 cross = pts[index].fX - pts[next].fX;
2777 }
reed@google.com188bfcf2012-01-17 18:26:38 +00002778 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002779
reed@google.comac8543f2012-01-30 20:51:25 +00002780 if (cross) {
2781 // record our best guess so far
2782 ymax = pts[index].fY;
2783 ymaxCross = cross;
reed@google.com69a99432012-01-10 18:00:10 +00002784 }
reed@google.com69a99432012-01-10 18:00:10 +00002785 }
2786 }
bsalomon@google.com30c174b2012-11-13 14:36:42 +00002787 if (ymaxCross) {
2788 crossToDir(ymaxCross, dir);
2789 fDirection = *dir;
2790 return true;
2791 } else {
2792 return false;
2793 }
reed@google.comac8543f2012-01-30 20:51:25 +00002794}
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002795
2796///////////////////////////////////////////////////////////////////////////////
2797
2798static SkScalar eval_cubic_coeff(SkScalar A, SkScalar B, SkScalar C,
2799 SkScalar D, SkScalar t) {
2800 return SkScalarMulAdd(SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C), t, D);
2801}
2802
2803static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2804 SkScalar t) {
2805 SkScalar A = c3 + 3*(c1 - c2) - c0;
2806 SkScalar B = 3*(c2 - c1 - c1 + c0);
2807 SkScalar C = 3*(c1 - c0);
2808 SkScalar D = c0;
2809 return eval_cubic_coeff(A, B, C, D, t);
2810}
2811
2812/* Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
2813 t value such that cubic(t) = target
2814 */
2815static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
2816 SkScalar target, SkScalar* t) {
2817 // SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
2818 SkASSERT(c0 < target && target < c3);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002819
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002820 SkScalar D = c0 - target;
2821 SkScalar A = c3 + 3*(c1 - c2) - c0;
2822 SkScalar B = 3*(c2 - c1 - c1 + c0);
2823 SkScalar C = 3*(c1 - c0);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002824
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002825 const SkScalar TOLERANCE = SK_Scalar1 / 4096;
2826 SkScalar minT = 0;
2827 SkScalar maxT = SK_Scalar1;
2828 SkScalar mid;
2829 int i;
2830 for (i = 0; i < 16; i++) {
2831 mid = SkScalarAve(minT, maxT);
2832 SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
2833 if (delta < 0) {
2834 minT = mid;
2835 delta = -delta;
2836 } else {
2837 maxT = mid;
2838 }
2839 if (delta < TOLERANCE) {
2840 break;
2841 }
2842 }
2843 *t = mid;
2844 return true;
2845}
2846
2847template <size_t N> static void find_minmax(const SkPoint pts[],
2848 SkScalar* minPtr, SkScalar* maxPtr) {
2849 SkScalar min, max;
2850 min = max = pts[0].fX;
2851 for (size_t i = 1; i < N; ++i) {
2852 min = SkMinScalar(min, pts[i].fX);
2853 max = SkMaxScalar(max, pts[i].fX);
2854 }
2855 *minPtr = min;
2856 *maxPtr = max;
2857}
2858
2859static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2860 SkPoint storage[4];
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002861
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002862 int dir = 1;
2863 if (pts[0].fY > pts[3].fY) {
2864 storage[0] = pts[3];
2865 storage[1] = pts[2];
2866 storage[2] = pts[1];
2867 storage[3] = pts[0];
2868 pts = storage;
2869 dir = -1;
2870 }
2871 if (y < pts[0].fY || y >= pts[3].fY) {
2872 return 0;
2873 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002874
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002875 // quickreject or quickaccept
2876 SkScalar min, max;
2877 find_minmax<4>(pts, &min, &max);
2878 if (x < min) {
2879 return 0;
2880 }
2881 if (x > max) {
2882 return dir;
2883 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002884
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002885 // compute the actual x(t) value
2886 SkScalar t, xt;
2887 if (chopMonoCubicAt(pts[0].fY, pts[1].fY, pts[2].fY, pts[3].fY, y, &t)) {
2888 xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t);
2889 } else {
2890 SkScalar mid = SkScalarAve(pts[0].fY, pts[3].fY);
2891 xt = y < mid ? pts[0].fX : pts[3].fX;
2892 }
2893 return xt < x ? dir : 0;
2894}
2895
2896static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y) {
2897 SkPoint dst[10];
2898 int n = SkChopCubicAtYExtrema(pts, dst);
2899 int w = 0;
2900 for (int i = 0; i <= n; ++i) {
2901 w += winding_mono_cubic(&dst[i * 3], x, y);
2902 }
2903 return w;
2904}
2905
2906static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2907 SkScalar y0 = pts[0].fY;
2908 SkScalar y2 = pts[2].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002909
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002910 int dir = 1;
2911 if (y0 > y2) {
2912 SkTSwap(y0, y2);
2913 dir = -1;
2914 }
2915 if (y < y0 || y >= y2) {
2916 return 0;
2917 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002918
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002919 // bounds check on X (not required. is it faster?)
2920#if 0
2921 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) {
2922 return 0;
2923 }
2924#endif
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002925
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002926 SkScalar roots[2];
2927 int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY,
2928 2 * (pts[1].fY - pts[0].fY),
2929 pts[0].fY - y,
2930 roots);
2931 SkASSERT(n <= 1);
2932 SkScalar xt;
2933 if (0 == n) {
2934 SkScalar mid = SkScalarAve(y0, y2);
2935 // Need [0] and [2] if dir == 1
2936 // and [2] and [0] if dir == -1
2937 xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].fX;
2938 } else {
2939 SkScalar t = roots[0];
2940 SkScalar C = pts[0].fX;
2941 SkScalar A = pts[2].fX - 2 * pts[1].fX + C;
2942 SkScalar B = 2 * (pts[1].fX - C);
2943 xt = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
2944 }
2945 return xt < x ? dir : 0;
2946}
2947
2948static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) {
2949 // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0;
2950 if (y0 == y1) {
2951 return true;
2952 }
2953 if (y0 < y1) {
2954 return y1 <= y2;
2955 } else {
2956 return y1 >= y2;
2957 }
2958}
2959
2960static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
2961 SkPoint dst[5];
2962 int n = 0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002963
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002964 if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) {
2965 n = SkChopQuadAtYExtrema(pts, dst);
2966 pts = dst;
2967 }
2968 int w = winding_mono_quad(pts, x, y);
2969 if (n > 0) {
2970 w += winding_mono_quad(&pts[2], x, y);
2971 }
2972 return w;
2973}
2974
2975static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y) {
2976 SkScalar x0 = pts[0].fX;
2977 SkScalar y0 = pts[0].fY;
2978 SkScalar x1 = pts[1].fX;
2979 SkScalar y1 = pts[1].fY;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002980
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002981 SkScalar dy = y1 - y0;
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002982
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002983 int dir = 1;
2984 if (y0 > y1) {
2985 SkTSwap(y0, y1);
2986 dir = -1;
2987 }
2988 if (y < y0 || y >= y1) {
2989 return 0;
2990 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002991
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002992 SkScalar cross = SkScalarMul(x1 - x0, y - pts[0].fY) -
2993 SkScalarMul(dy, x - pts[0].fX);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00002994
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00002995 if (SkScalarSignAsInt(cross) == dir) {
2996 dir = 0;
2997 }
2998 return dir;
2999}
3000
reed@google.com4db592c2013-10-30 17:39:43 +00003001static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) {
3002 return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom;
3003}
3004
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003005bool SkPath::contains(SkScalar x, SkScalar y) const {
3006 bool isInverse = this->isInverseFillType();
3007 if (this->isEmpty()) {
3008 return isInverse;
3009 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003010
reed@google.com4db592c2013-10-30 17:39:43 +00003011 if (!contains_inclusive(this->getBounds(), x, y)) {
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003012 return isInverse;
3013 }
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003014
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003015 SkPath::Iter iter(*this, true);
3016 bool done = false;
3017 int w = 0;
3018 do {
3019 SkPoint pts[4];
3020 switch (iter.next(pts, false)) {
3021 case SkPath::kMove_Verb:
3022 case SkPath::kClose_Verb:
3023 break;
3024 case SkPath::kLine_Verb:
3025 w += winding_line(pts, x, y);
3026 break;
3027 case SkPath::kQuad_Verb:
3028 w += winding_quad(pts, x, y);
3029 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003030 case SkPath::kConic_Verb:
3031 SkASSERT(0);
3032 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003033 case SkPath::kCubic_Verb:
3034 w += winding_cubic(pts, x, y);
3035 break;
3036 case SkPath::kDone_Verb:
3037 done = true;
3038 break;
reed@google.com277c3f82013-05-31 15:17:50 +00003039 }
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003040 } while (!done);
rmistry@google.comfbfcd562012-08-23 18:09:54 +00003041
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003042 switch (this->getFillType()) {
3043 case SkPath::kEvenOdd_FillType:
3044 case SkPath::kInverseEvenOdd_FillType:
3045 w &= 1;
3046 break;
reed@google.come9bb6232012-07-11 18:56:10 +00003047 default:
3048 break;
mike@reedtribe.orgbad1b2f2012-07-11 01:51:33 +00003049 }
3050 return SkToBool(w);
3051}